# ppp-client-lib.pl
# Common functions for configuring WV-Dial
# XXX what about redhat connect process?
# XXX what about SuSE connect process?

BEGIN { push(@INC, ".."); };
use WebminCore;
&init_config();
$details_file = "$module_config_directory/connect";
$resolv_conf = "/etc/resolv.conf";
$ppp_resolv_conf = "/etc/ppp/resolv.conf";
$save_resolv_conf = $resolv_conf.".save";

# get_config()
# Returns a list of all configuration settings
sub get_config
{
local (@rv, $sect);
local $lnum = 0;
open(FILE, "<".$config{'file'});
while(<FILE>) {
	s/^\s*;.*//;
	s/\r|\n//g;
	if (/^\s*\[(.*)\]/) {
		# Start of a section
		$sect = { 'name' => $1,
			  'index' => scalar(@rv),
			  'line' => $lnum,
			  'eline' => $lnum,
			  'values' => { },
			  'onames' => { } };
		push(@rv, $sect);
		}
	elsif (/^\s*([^=]+\S)\s*=\s*(.*)/ && $sect) {
		# A directive within a section
		$sect->{'values'}->{lc($1)} = $2;
		$sect->{'onames'}->{lc($1)} = $1;
		$sect->{'eline'} = $lnum;
		}
	$lnum++;
	}
close(FILE);
return \@rv;
}

# create_dialer(&dialer)
# Add a dialer to the configuration
sub create_dialer
{
local $lref = &read_file_lines($config{'file'});
push(@$lref, "", &dialer_lines($_[0]));
&flush_file_lines();
}

# update_dialer(&dialer)
sub update_dialer
{
local $lref = &read_file_lines($config{'file'});
splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1,
       &dialer_lines($_[0]));
&flush_file_lines();
}

# delete_dialer(&dialer)
sub delete_dialer
{
local $lref = &read_file_lines($config{'file'});
splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1);
if ($_[0]->{'line'} && $lref->[$_[0]->{'line'}-1] eq "") {
	splice(@$lref, $_[0]->{'line'}-1, 1);
	}
&flush_file_lines();
}

# dialer_lines(&dialer)
sub dialer_lines
{
local @rv = "[$_[0]->{'name'}]";
local $k;
foreach $k (keys %{$_[0]->{'values'}}) {
	local $pk = $_[0]->{'onames'}->{$k} || $k;
	push(@rv, $pk." = ".$_[0]->{'values'}->{$k});
	}
return @rv;
}

# device_name(device)
# Returns a human-readable modem device name
sub device_name
{
if ($_[0] =~ /^\/dev\/ttyS(\d+)$/) {
	return &text('device_serial', $1+1);
	}
else {
	return "<tt>$_[0]</tt>";
	}
}

# save_connect_details(ip, pid, section)
sub save_connect_details
{
&open_tempfile(DETAILS, ">$details_file");
&print_tempfile(DETAILS, $_[0],"\n",$_[1],"\n",$_[2],"\n");
&close_tempfile(DETAILS);
}

# get_connect_details()
sub get_connect_details
{
local ($ip, $pid, $sect);
open(DETAILS, "<".$details_file) || return ();
chop($ip = <DETAILS>);
chop($pid = <DETAILS>);
chop($sect = <DETAILS>);
close(DETAILS);
return ($ip, $pid, $sect);
}

# get_wvdial_pid()
# Returns the PID of a running wvdial process
sub get_wvdial_pid
{
local $p;
&foreign_require("proc", "proc-lib.pl");
foreach $p (&proc::list_processes()) {
	if ($p->{'args'} =~ /^\S*wvdial($|\s)/ && $p->{'args'} !~ /\[/) {
		return $p->{'pid'};
		}
	}
return undef;
}

sub dialer_name
{
return $_[0] =~ /^Dialer Defaults$/i ?
		$text{'index_defaults'} :
       $_[0] =~ /^Dialer\s+(.*)$/i ?
		&text('index_dialer', "$1") : $_[0];
}

# ppp_connect(section-name, textmode)
sub ppp_connect
{
# Get this dialer's configuration
local $conf = &get_config();
local ($dialer) = grep { lc($_->{'name'}) eq lc($_[0]) } @$conf;
local ($ddialer) = grep { lc($_->{'name'}) eq 'dialer defaults' } @$conf;
local ($inherits, $parent, $autodns);
if ($inherits = $dialer->{'values'}->{'inherits'}) {
	($parent) = grep { lc($_->{'name'}) eq lc($inherits) } @$conf;
	}
$autodns = $dialer->{'values'}->{'auto dns'} ?
		($dialer->{'values'}->{'auto dns'} =~ /on|yes|1/ ? 1 : 0) :
	   $parent->{'values'}->{'auto dns'} ?
		($parent->{'values'}->{'auto dns'} =~ /on|yes|1/ ? 1 : 0) :
	   $ddialer->{'values'}->{'auto dns'} ?
		($ddialer->{'values'}->{'auto dns'} =~ /on|yes|1/ ? 1 : 0) :
		1;

# Run wvdial, writing to a temp file in the background
local $stime = time();
local $sect = $_[0];
$sect =~ s/^Dialer\s+//;
local $temp = &tempname();
if (!($pid = fork())) {
	untie(*STDIN);
	untie(*STDOUT);
	untie(*STDERR);
	open(STDOUT, ">$temp");
	open(STDERR, ">&STDOUT");
	open(STDIN, "/dev/null");
	exec($config{'wvdial'}, $sect);
	exit(1);
	}
while (!open(TEMP, "<".$temp)) { }
&additional_log("exec", undef, "$config{'wvdial'} $sect");
unlink($temp);

# Read output until success or failure
if ($_[1] == 1) {
	print &text('connect_cmd', "$config{'wvdial'} $sect"),"\n\n";
	}
elsif ($_[1] == 0) {
	print "<b>",&text('connect_cmd',
			     "<tt>$config{'wvdial'} $sect</tt>"),"</b><br>\n";
	print "<pre>";
	}
local ($connected, $failed);
while(1) {
	local $line = &wait_for_line();
	if ($_[1] == 1) {
		print $line;
		}
	elsif ($_[1] == 0) {
		print &html_escape($line);
		}
	if ($line =~ /IP\s+address\s+is\s+(\d+\.\d+\.\d+\.\d+)/i) {
		# Connected OK!
		$connected = $1 eq "0.0.0.0" ? "*" : $1;
		last;
		}
	elsif ($line =~ /starting\s+ppp/i) {
		# Connected in stupid mode
		$connected = "*";
		last;
		}
	elsif (!$line) {
		# Program terminated .. must have failed!
		$failed++;
		last;
		}
	}
print "</pre>\n" if ($_[1] == 0);

# If OK, save the PID for later
if ($connected) {
	if ($_[1] == 0) {
		print "<b>",$connected eq '*' ? $text{'connect_noip'} :
			&text('connect_ip', "<tt>$connected</tt>"),"</b><p>\n";
		}
	elsif ($_[1] == 1) {
		print $connected eq '*' ? $text{'connect_noip'} :
			&text('connect_ip', $connected),"\n";
		}
	&save_connect_details($connected, $pid, $_[0]);
	}
else {
	if ($_[1] == 0) {
		print "<b>$text{'connect_failed'}</b><p>\n";
		}
	elsif ($_[1] == 1) {
		print "$text{'connect_failed'}\n\n";
		}
	}
$config{'dialer'} = $_[0];
&lock_file("$module_config_directory/config");
&write_file("$module_config_directory/config", \%config);
&unlock_file("$module_config_directory/config");

if ($connected && $autodns) {
	# If the resolv.conf file has not been modified, and the PPP
	# resolv.conf has, copy it into place
	while(1) {
		sleep(3);
		local ($ip, $pid, $sect) = &get_connect_details();
		if (!$pid || !kill(0, $pid)) {
			# Connection is down .. DNS will never be updated
			if ($_[1] == 0) {
				print "<b>$text{'connect_dnsdown'}</b><p>\n";
				}
			elsif ($_[1] == 1) {
				print "$text{'connect_dnsdown'}\n\n";
				}
			last;
			}
		$now = time();
		if ($now > $stime+60) {
			# Took too long to update DNS
			if ($_[1] == 0) {
				print "<b>$text{'connect_dnsto'}</b><p>\n";
				}
			elsif ($_[1] == 1) {
				print "$text{'connect_dnsto'}\n\n";
				}
			last;
			}
		local @pst = stat($ppp_resolv_conf);
		local @ost = stat($resolv_conf);
		if ($ost[9] >= $stime) {
			# Something else has update the DNS config ..
			if ($_[1] == 0) {
				print "<b>$text{'connect_dns2'}</b><p>\n";
				}
			elsif ($_[1] == 1) {
				print "$text{'connect_dns2'}\n\n";
				}
			last;
			}
		if ($pst[9] >= $stime) {
			# A PPP DNS config has been created .. use it
			&system_logged("cp $resolv_conf $save_resolv_conf")
				if (!-l $resolv_conf);
			unlink($resolv_conf);
			&system_logged("cp $ppp_resolv_conf $resolv_conf");
			if ($_[1] == 0) {
				print "<b>$text{'connect_dns'}</b><p>\n";
				}
			elsif ($_[1] == 1) {
				print "$text{'connect_dns'}\n\n";
				}
			last;
			}
		}
	}
return $connected;
}

# wait_for_line()
# Reads a line from the temp file, or waits if there is no more to read. Only
# returns undef if the process has died
sub wait_for_line
{
local $line = <TEMP>;
return $line if ($line);
waitpid($pid, 1);
return undef if (!kill(0, $pid));
sleep(1);
return &wait_for_line();
}

# ppp_disconnect(mode, text-mode)
# Shuts down the active PPP connection
sub ppp_disconnect
{
local ($ip, $pid, $sect);
if ($_[0] == 0) {
	($ip, $pid, $sect) = &get_connect_details();
	}
else {
	$pid = &get_wvdial_pid();
	}
if ($pid && &kill_logged('TERM', $pid)) {
	# Tell the user that the connection is now down
	if ($ip) {
		if ($_[1] == 0) {
			print &text('disc_ok1', "<tt>$ip</tt>", &dialer_name($sect)),"<p>\n";
			}
		elsif ($_[1] == 1) {
			print &text('disc_ok1', $ip, &dialer_name($sect)),"\n\n";
			}
		}
	else {
		if ($_[1] == 0) {
			print $text{'disc_ok2'},"<p>\n";
			}
		elsif ($_[1] == 1) {
			print $text{'disc_ok2'},"\n\n";
			}
		}

	# Restore the saved DNS config file, if it hasn't been done
	sleep(3);
	local @ost = stat($resolv_conf);
	if (!-l $resolv_conf && $ost[9] < time()-5 && -r $save_resolv_conf) {
		&system_logged("mv $save_resolv_conf $resolv_conf");
		if ($_[1] == 0) {
			print "$text{'disc_dns'}<p>\n";
			}
		elsif ($_[1] == 1) {
			print "$text{'disc_dns'}\n\n";
			}
		}
	&system_logged("rm -f $ppp_resolv_conf");
	return 1;
	}
else {
	# Wasn't even active .. tell the user
	if ($_[1] == 0) {
		print "$text{'disc_edown'}<p>\n";
		}
	elsif ($_[1] == 1) {
		print "$text{'disc_edown'}\n\n";
		}
	return 0;
	}
}

1;

