code: 9ferno

ref: 6bfe4cf8107de7a01ff8a0667db7f28f08e4d238
dir: /appl/cmd/ip/ppp/pppdial.b/

View raw version
implement PPPdial;

#
#	Module:		ispservice
#	Purpose:		Simple PPP Dial-on-Demand
#	Author:		Eric Van Hensbergen (ericvh@lucent.com)
#
# Copyright © 1998-1999 Lucent Technologies Inc.  All rights reserved.
# Revisions copyright © 2000-2003 Vita Nuova Holdings Limited.  All rights reserved.
#

include "sys.m";
	sys: Sys;
include "draw.m";
	draw: Draw;

include "cfgfile.m";
	cfg:	CfgFile;
	ConfigFile: import cfg;

include "lock.m";
include "modem.m";
include "script.m";
include "pppclient.m";
	ppp: PPPClient;
include "pppgui.m";

PPPdial: module
{
	init:	fn(nil: ref Draw->Context): string;
	connect:	fn(): string;
};

context:		ref Draw->Context;
modeminfo:		ref Modem->ModemInfo;
pppinfo:		ref PPPClient->PPPInfo;
scriptinfo:		ref Script->ScriptInfo;
isp_number:		string;						# should be part of pppinfo
lastCdir:		ref Sys->Dir;	# state of file when last read

DEFAULT_ISP_DB_PATH:	con "/services/ppp/isp.cfg";	# contains pppinfo & scriptinfo
DEFAULT_MODEM_DB_PATH:	con	"/services/ppp/modem.cfg";			# contains modeminfo
MODEM_DB_PATH:	con	"/usr/inferno/config/modem.cfg";			# contains modeminfo
ISP_DB_PATH:	con "/usr/inferno/config/isp.cfg";		# contains pppinfo & scriptinfo
ISP_RETRIES:	con 5;

getcfgstring(c: ref ConfigFile, key: string) :string
{
	l := c.getcfg(key);
	if (l == nil)
		return nil;
	for(ret := ""; l != nil; l = tl l)
		ret+= " " + hd l;
	
	return ret[1:];		# trim the first space
}

configinit()
{
	mi:	Modem->ModemInfo;
	pppi: PPPClient->PPPInfo;
	info: list of string;

	cfg = load CfgFile CfgFile->PATH;
	if (cfg == nil)
		raise "fail: load CfgFile";

	# Modem Configuration
	
	cfg->verify(DEFAULT_MODEM_DB_PATH, MODEM_DB_PATH);
	modemcfg := cfg->init(MODEM_DB_PATH);
	if (modemcfg == nil)
		raise "fail: read: "+MODEM_DB_PATH;
	modeminfo = ref mi;

	modeminfo.path = getcfgstring(modemcfg, "PATH");
	modeminfo.init = getcfgstring(modemcfg, "INIT");
	modeminfo.country = getcfgstring(modemcfg, "COUNTRY");
	modeminfo.other = getcfgstring(modemcfg, "OTHER");
	modeminfo.errorcorrection = getcfgstring(modemcfg,"CORRECT");
	modeminfo.compression = getcfgstring(modemcfg,"COMPRESS");
	modeminfo.flowctl = getcfgstring(modemcfg,"FLOWCTL");
	modeminfo.rateadjust = getcfgstring(modemcfg,"RATEADJ");
	modeminfo.mnponly = getcfgstring(modemcfg,"MNPONLY");
	modeminfo.dialtype = getcfgstring(modemcfg,"DIALING");
	if(modeminfo.dialtype!="ATDP")
		modeminfo.dialtype="ATDT";

	cfg->verify(DEFAULT_ISP_DB_PATH, ISP_DB_PATH);
	(ok, stat) := sys->stat(ISP_DB_PATH);
	if(ok >= 0)
		lastCdir = ref stat;
	sys->print("cfg->init(%s)\n", ISP_DB_PATH);

	# ISP Configuration
	pppcfg := cfg->init(ISP_DB_PATH);
	if (pppcfg == nil)
		raise "fail: Couldn't load ISP configuration file: "+ISP_DB_PATH;
	pppinfo = ref pppi;
	isp_number = getcfgstring(pppcfg, "NUMBER");
	pppinfo.ipaddr = getcfgstring(pppcfg,"IPADDR");
	pppinfo.ipmask = getcfgstring(pppcfg,"IPMASK");
	pppinfo.peeraddr = getcfgstring(pppcfg,"PEERADDR");
	pppinfo.maxmtu = getcfgstring(pppcfg,"MAXMTU");
	pppinfo.username = getcfgstring(pppcfg,"USERNAME");
	pppinfo.password = getcfgstring(pppcfg,"PASSWORD");

	info = pppcfg.getcfg("SCRIPT");
	if (info != nil) {
		scriptinfo = ref Script->ScriptInfo;
		scriptinfo.path = hd info;
		scriptinfo.username = pppinfo.username;
		scriptinfo.password = pppinfo.password;
	} else
		scriptinfo = nil;

	info = pppcfg.getcfg("TIMEOUT");
	if (info != nil)
		scriptinfo.timeout = int (hd info);

	cfg = nil;	# might as well unload it
}

#
# Parts of the following two functions could be generalized
#

isipaddr(a: string): int
{
	i, c, ac, np: int = 0;
 
	for(i = 0; i < len a; i++) {
		c = a[i];
		if(c >= '0' && c <= '9') {
			np = 10*np + c - '0';
			continue;
		}
		if (c == '.' && np) {
			ac++;
	 		if (np > 255)
				return 0;
			np = 0;
			continue;
		}
		return 0;
	}
	return np && np < 256 && ac == 3;
}

# check if there is an existing PPP connection
connected(): int
{
	ifd := sys->open("/net/ipifc", Sys->OREAD);
	if(ifd == nil)
		return 0;

	buf := array[1024] of byte;

	for(;;) {
		(n, d) := sys->dirread(ifd);
		if (n <= 0)
			return 0;
		for(i := 0; i < n; i++)
			if(d[i].name[0] <= '9') {
				sfd := sys->open("/net/ipifc/"+d[i].name+"/status", Sys->OREAD);
				if (sfd == nil)
					continue;
				ns := sys->read(sfd, buf, len buf);
				if (ns <= 0)
					continue;
				(nflds, flds) := sys->tokenize(string buf[0:ns], " \t\r\n");
				if(nflds < 4)
					continue;
				if (isipaddr(hd tl tl flds))
					return 1;
			}
	}
}

#
# called once when loaded
#
init(ctxt: ref Draw->Context): string
{
	sys = load Sys Sys->PATH;
	{
		ppp = load PPPClient PPPClient->PATH;
		if (ppp == nil)
			raise "fail: Couldn't load ppp module";

		# Contruct Config Tables During Init - may want to change later
		#	for multiple configs (Software Download Server versus ISP)
		configinit();	
		context = ctxt;
	}exception e {
	"fail:*" =>
		return e;
	}
	return nil;
}

dialup_cancelled := 0;
connecting := 0;

#
# called each time a translation is needed, to check that we're on line(!)
# eventually this will be replaced by a packet interface that does dial-on-demand
#
connect(): string
{
	{
		dialup_cancelled = 0;
		(ok, stat) := sys->stat(ISP_DB_PATH);
		if (ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat))
			configinit();
		errc := chan of string;
		while(!connected()){
			if(!connecting) {
				connecting = 1;
				sync := chan of int;
				spawn pppconnect(errc, sync);
				<- sync;
				return <-errc;
			}else{
				sys->sleep(2500);
				if (dialup_cancelled)
					return "fail: dialup cancelled";	
			}
		}
	}exception e{
	"fail:*" =>
		return e;
	"*" =>
		sys->print("pppdial: caught exception: %s\n", e);
		return "fail: internal error: "+e;
	}
	return nil;	
}

pppconnect(errc: chan of string, sync: chan of int)
{
	connecting = 1;
	sys->pctl(Sys->NEWPGRP, nil);
	sync <-= 0;
	resp_chan: chan of int;
	logger := chan of int;
	pppgui := load PPPGUI PPPGUI->PATH;
	for (count :=0; count < ISP_RETRIES; count++) {
		resp_chan = pppgui->init(context, logger, ppp, nil);
		spawn ppp->connect(modeminfo, isp_number, scriptinfo, pppinfo, logger);
		x := <-resp_chan;
		if (x > 0) {
			if (x == 1) {
				# alt needed in case calling process has been killed
				alt {
					errc <-= nil => ;
					* => ;
				}
			} else	{		# user cancelled dial-in
				dialup_cancelled = 1;
				alt {
					errc <-= "fail: dialup cancelled" => ;
					* => ;
				}
			}
			connecting = 0;
			return;
		}
		# else connect failed, go around loop to try again
	}
	alt {
		errc <-= "fail: dialup failed" => ;
		* => ;
	}
	connecting = 0;
}

samefile(d1, d2: Sys->Dir): int
{
	return d1.dev==d2.dev && d1.dtype==d2.dtype &&
			d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
			d1.mtime==d2.mtime;
}