#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>

#define	LOG	"pptpd"

typedef struct Call	Call;
typedef struct Event Event;

#define	SDB	if(debug) fprint(2, 
#define EDB	);

enum {
	Magic	= 0x1a2b3c4d,
	Nhash	= 17,
	Nchan	= 10,		/* maximum number of channels */
	Window	= 8,		/* default window size */
	Timeout	= 60,		/* timeout in seconds for control channel */
	Pktsize = 2000,		/* maximum packet size */
	Tick	= 500,		/* tick length in milliseconds */
	Sendtimeout = 4,	/* in ticks */
};

enum {
	Syncframe	= 0x1,
	Asyncframe	= 0x2,
	Analog		= 0x1,
	Digital		= 0x2,
	Version		= 0x100,
};

enum {
	Tstart		= 1,
	Rstart		= 2,
	Tstop		= 3,
	Rstop		= 4,
	Techo		= 5,
	Recho		= 6,
	Tcallout	= 7,
	Rcallout	= 8,
	Tcallreq	= 9,
	Rcallreq	= 10,
	Acallcon	= 11,
	Tcallclear	= 12,
	Acalldis	= 13,
	Awaninfo	= 14,
	Alinkinfo	= 15,
};


struct Event {
	QLock;
	QLock waitlk;
	int	wait;
	int ready;
};

struct Call {
	int	ref;
	QLock	lk;
	int	id;
	int	serial;
	int	pppfd;

	int	closed;

	int	pac;	/* server is acting as a PAC */

	int	recvwindow;	/* recv windows */
	int	sendwindow;	/* send windows */
	int	delay;

	int	sendaccm;
	int	recvaccm;

	uint	seq;		/* current seq number - for send */
	uint	ack;		/* current acked mesg - for send */
	uint	rseq;		/* highest recv seq number for in order packet  */
	uint	rack;		/* highest ack sent */

	Event	eack;		/* recved ack - for send */
	ulong	tick;

	uchar	remoteip[IPaddrlen];	/* remote ip address */
	int	dhcpfd[2];	/* pipe to dhcpclient */

	/* error stats */
	struct {
		int	crc;
		int	frame;
		int	hardware;
		int	overrun;
		int	timeout;
		int	align;
	} err;

	struct {
		int	send;
		int	sendack;
		int	recv;
		int	recvack;
		int	dropped;
		int	missing;
		int	sendwait;
		int	sendtimeout;
	} stat;

	Call	*next;
};

struct {
	QLock	lk;
	int	start;
	int	grefd;
	int	grecfd;
	uchar	local[IPaddrlen];
	uchar	remote[IPaddrlen];
	char	*tcpdir;
	uchar	ipaddr[IPaddrlen];		/* starting ip addresss to allocate */

	int	recvwindow;

	char	*pppdir;
	char	*pppexec;

	double	rcvtime;	/* time at which last request was received */
	int	echoid;		/* id of last echo request */

	Call	*hash[Nhash];
} srv;

/* GRE flag bits */
enum {
	GRE_chksum	= (1<<15),
	GRE_routing	= (1<<14),
	GRE_key		= (1<<13),
	GRE_seq		= (1<<12),
	GRE_srcrt	= (1<<11),
	GRE_recur	= (7<<8),
	GRE_ack		= (1<<7),
	GRE_ver		= 0x7,
};

/* GRE protocols */
enum {
	GRE_ppp		= 0x880b,
};

int	debug;
double	drop;

void	myfatal(char *fmt, ...);

#define	PSHORT(p, v)		((p)[0]=((v)>>8), (p)[1]=(v))
#define	PLONG(p, v)		(PSHORT(p, (v)>>16), PSHORT(p+2, (v)))
#define	PSTRING(d,s,n)		strncpy((char*)(d), s, n)
#define	GSHORT(p)		(((p)[0]<<8) | ((p)[1]<<0))
#define	GLONG(p)		((GSHORT((p))<<16) | ((GSHORT((p)+2))<<0))
#define	GSTRING(d,s,n)		strncpy(d, (char*)(s), n), d[(n)-1] = 0

void	serve(void);

int	sstart(uchar*, int);
int	sstop(uchar*, int);
int	secho(uchar*, int);
int	scallout(uchar*, int);
int	scallreq(uchar*, int);
int	scallcon(uchar*, int);
int	scallclear(uchar*, int);
int	scalldis(uchar*, int);
int	swaninfo(uchar*, int);
int	slinkinfo(uchar*, int);

Call	*callalloc(int id);
void	callclose(Call*);
void	callfree(Call*);
Call	*calllookup(int id);

void	gretimeout(void*);
void	pppread(void*);

void	srvinit(void);
void	greinit(void);
void	greread(void*);
void	greack(Call *c);

void	timeoutthread(void*);

int	argatoi(char *p);
void	usage(void);
int	ipaddralloc(Call *c);

void	*emallocz(int size);
void	esignal(Event *e);
void	ewait(Event *e);
int	proc(char **argv, int fd0, int fd1, int fd2);
double	realtime(void);
ulong	thread(void(*f)(void*), void *a);

void
main(int argc, char *argv[])
{
	ARGBEGIN{
	case 'd': debug++; break;
	case 'p': srv.pppdir = ARGF(); break;
	case 'P': srv.pppexec = ARGF(); break;
	case 'w': srv.recvwindow = argatoi(ARGF()); break;
	case 'D': drop = atof(ARGF()); break;
	default:
		usage();
	}ARGEND

	fmtinstall('I', eipfmt);
	fmtinstall('E', eipfmt);
	fmtinstall('V', eipfmt);
	fmtinstall('M', eipfmt);

	rfork(RFNOTEG|RFREND);

	if(argc != 1)
		usage();

	srv.tcpdir = argv[0];

	srvinit();

	syslog(0, LOG, ": src=%I: pptp started: %d", srv.remote, getpid());

	SDB  "\n\n\n%I: pptp started\n", srv.remote EDB

	greinit();

	thread(timeoutthread, 0);

	serve();

	syslog(0, LOG, ": src=%I: server exits", srv.remote);

	postnote(PNGROUP, getpid(), "die");
	exits(0);
}

void
usage(void)
{
	fprint(2, "usage: pptpd [-dD] [-p ppp-net] [-w window] tcpdir\n");
	exits("usage");
}

void
serve(void)
{
	uchar buf[2000], *p;
	int n, n2, len;
	int magic;
	int op, type;

	n = 0;
	for(;;) {
		n2 = read(0, buf+n, sizeof(buf)-n);
		if(n2 < 0)
			myfatal("bad read on ctl channel: %r");
		if(n2 == 0)
			break;
		n += n2;
		p = buf;
		for(;;) {
			if(n < 12)
				break;

			qlock(&srv.lk);
			srv.rcvtime = realtime();
			qunlock(&srv.lk);

			len = GSHORT(p);
			type = GSHORT(p+2);
			magic = GLONG(p+4);
			op = GSHORT(p+8);
			if(magic != Magic)
				myfatal("bad magic number: got %x", magic);
			if(type != 1)
				myfatal("bad message type: %d", type);
			switch(op) {
			default:
				myfatal("unknown control op: %d", op);
			case Tstart:		/* start-control-connection-request */
				n2 = sstart(p, n);
				break;
			case Tstop:
				n2 = sstop(p, n);
				if(n2 > 0)
					return;
				break;
			case Techo:
				n2 = secho(p, n);
				break;
			case Tcallout:
				n2 = scallout(p, n);
				break;
			case Tcallreq:
				n2 = scallreq(p, n);
				break;
			case Acallcon:
				n2 = scallcon(p, n);
				break;
			case Tcallclear:
				n2 = scallclear(p, n);
				break;
			case Acalldis:
				n2 = scalldis(p, n);
				break;
			case Awaninfo:
				n2 = swaninfo(p, n);
				break;
			case Alinkinfo:
				n2 = slinkinfo(p, n);
				break;
			}	
			if(n2 == 0)
				break;
			if(n2 != len)
				myfatal("op=%d: bad length: got %d expected %d", op, len, n2);
			n -= n2;
			p += n2;
			
		}

		/* move down partial message */
		if(p != buf && n != 0)
			memmove(buf, p, n);
	}

}

int
sstart(uchar *p, int n)
{
	int ver, frame, bearer, maxchan, firm;
	char host[64], vendor[64], *sysname;
	uchar buf[156];

	if(n < 156)
		return 0;
	ver = GSHORT(p+12);
	frame = GLONG(p+16);
	bearer = GLONG(p+20);
	maxchan = GSHORT(p+24);
	firm = GSHORT(p+26);
	GSTRING(host, p+28, 64);
	GSTRING(vendor, p+92, 64);

	SDB "%I: start ver = %x f = %d b = %d maxchan = %d firm = %d host = %s vendor = %s\n",
		srv.remote, ver, frame, bearer, maxchan, firm, host, vendor EDB

	if(ver != Version)
		myfatal("bad version: got %x expected %x", ver, Version);

	if(srv.start)
		myfatal("multiple start messages");

	srv.start = 1;

	sysname = getenv("sysname");
	if(sysname == 0)
		strcpy(host, "gnot");
	else
		strncpy(host, sysname, 64);
	free(sysname);

	memset(buf, 0, sizeof(buf));

	PSHORT(buf+0, sizeof(buf));	/* length */
	PSHORT(buf+2, 1);		/* message type */
	PLONG(buf+4, Magic);		/* magic */
	PSHORT(buf+8, Rstart);		/* op */
	PSHORT(buf+12, Version);	/* version */
	buf[14] = 1;			/* result = ok */
	PLONG(buf+16, Syncframe|Asyncframe);	/* frameing */
	PLONG(buf+20, Digital|Analog);	/* berear capabilities */
	PSHORT(buf+24, Nchan);		/* max channels */
	PSHORT(buf+26, 1);		/* driver version */
	PSTRING(buf+28, host, 64);	/* host name */
	PSTRING(buf+92, "plan 9", 64);	/* vendor */

	if(write(1, buf, sizeof(buf)) < sizeof(buf))
		myfatal("write failed: %r");

	return 156;
}

int
sstop(uchar *p, int n)
{
	int reason;
	uchar buf[16];

	if(n < 16)
		return 0;
	reason = p[12];
	
	SDB "%I: stop %d\n", srv.remote, reason EDB

	memset(buf, 0, sizeof(buf));
	PSHORT(buf+0, sizeof(buf));	/* length */
	PSHORT(buf+2, 1);		/* message type */
	PLONG(buf+4, Magic);		/* magic */
	PSHORT(buf+8, Rstop);		/* op */
	buf[12] = 1;			/* ok */

	if(write(1, buf, sizeof(buf)) < sizeof(buf))
		myfatal("write failed: %r");

	return 16;
}

int
secho(uchar *p, int n)
{
	int id;
	uchar buf[20];

	if(n < 16)
		return 0;
	id = GLONG(p+12);
	
	SDB "%I: echo %d\n", srv.remote, id EDB

	memset(buf, 0, sizeof(buf));
	PSHORT(buf+0, sizeof(buf));	/* length */
	PSHORT(buf+2, 1);		/* message type */
	PLONG(buf+4, Magic);		/* magic */
	PSHORT(buf+8, Recho);		/* op */
	PLONG(buf+12, id);		/* id */
	p[16] = 1;			/* ok */

	if(write(1, buf, sizeof(buf)) < sizeof(buf))
		myfatal("write failed: %r");

	return 16;
}

int
scallout(uchar *p, int n)
{
	int id, serial;
	int minbps, maxbps, bearer, frame;
	int window, delay;
	int nphone;
	char phone[64], sub[64], buf[32];
	Call *c;

	if(n < 168)
		return 0;

	if(!srv.start)
		myfatal("%I: did not recieve start message", srv.remote);
	
	id = GSHORT(p+12);
	serial = GSHORT(p+14);
	minbps = GLONG(p+16);
	maxbps = GLONG(p+20);
	bearer = GLONG(p+24);
	frame = GLONG(p+28);
	window = GSHORT(p+32);
	delay = GSHORT(p+34);
	nphone = GSHORT(p+36);
	GSTRING(phone, p+40, 64);
	GSTRING(sub, p+104, 64);

	SDB "%I: callout id = %d serial = %d bps=[%d,%d] b=%x f=%x win = %d delay = %d np=%d phone=%s sub=%s\n",
		srv.remote, id, serial, minbps, maxbps, bearer, frame, window, delay, nphone, phone, sub EDB

	c = callalloc(id);
	c->sendwindow = window;
	c->delay = delay;
	c->pac = 1;
	c->recvwindow = srv.recvwindow;

	memset(buf, 0, sizeof(buf));
	PSHORT(buf+0, sizeof(buf));	/* length */
	PSHORT(buf+2, 1);		/* message type */
	PLONG(buf+4, Magic);		/* magic */
	PSHORT(buf+8, Rcallout);	/* op */
	PSHORT(buf+12, id);		/* call id */
	PSHORT(buf+14, id);		/* peer id */
	buf[16] = 1;			/* ok */
	PLONG(buf+20, 10000000);	/* speed */
	PSHORT(buf+24, c->recvwindow);	/* window size */
	PSHORT(buf+26, 0);		/* delay */
	PLONG(buf+28, 0);		/* channel id */
	
	if(write(1, buf, sizeof(buf)) < sizeof(buf))
		myfatal("write failed: %r");

	return 168;
}

int
scallreq(uchar *p, int n)
{
	USED(p);
	USED(n);

	myfatal("callreq: not done yet");
	return 0;
}

int
scallcon(uchar *p, int n)
{
	USED(p);
	USED(n);

	myfatal("callcon: not done yet");
	return 0;
}

int
scallclear(uchar *p, int n)
{
	Call *c;
	int id;
	uchar buf[148];

	if(n < 16)
		return 0;
	id = GSHORT(p+12);
	
	SDB "%I: callclear id=%d\n", srv.remote, id EDB
	
	if(c = calllookup(id)) {
		callclose(c);
		callfree(c);
	}

	memset(buf, 0, sizeof(buf));
	PSHORT(buf+0, sizeof(buf));	/* length */
	PSHORT(buf+2, 1);		/* message type */
	PLONG(buf+4, Magic);		/* magic */
	PSHORT(buf+8, Acalldis);	/* op */
	PSHORT(buf+12, id);		/* id */
	buf[14] = 3;			/* reply to callclear */

	if(write(1, buf, sizeof(buf)) < sizeof(buf))
		myfatal("write failed: %r");

	return 16;
}

int
scalldis(uchar *p, int n)
{
	Call *c;
	int id, res;

	if(n < 148)
		return 0;
	id = GSHORT(p+12);
	res = p[14];

	SDB "%I: calldis id=%d res=%d\n", srv.remote, id, res EDB

	if(c = calllookup(id)) {
		callclose(c);
		callfree(c);
	}

	return 148;
}

int
swaninfo(uchar *p, int n)
{
	Call *c;
	int id;

	if(n < 40)
		return 0;
	
	id = GSHORT(p+12);
	SDB "%I: waninfo id = %d\n", srv.remote, id EDB
	
	c = calllookup(id);
	if(c != 0) {
		c->err.crc = GLONG(p+16);
		c->err.frame = GLONG(p+20);
		c->err.hardware = GLONG(p+24);
		c->err.overrun = GLONG(p+28);
		c->err.timeout = GLONG(p+32);
		c->err.align = GLONG(p+36);

		callfree(c);
	}

	
	return 40;
}

int
slinkinfo(uchar *p, int n)
{
	Call *c;
	int id;
	int sendaccm, recvaccm;

	if(n < 24)
		return 0;
	id = GSHORT(p+12);
	sendaccm = GLONG(p+16);
	recvaccm = GLONG(p+20);

	SDB "%I: linkinfo id=%d saccm=%ux raccm=%ux\n", srv.remote, id, sendaccm, recvaccm EDB

	if(c = calllookup(id)) {
		c->sendaccm = sendaccm;
		c->recvaccm = recvaccm;

		callfree(c);
	}
	
	return 24;
}

Call*
callalloc(int id)
{
	uint h;
	Call *c;
	char *argv[30], local[20], remote[20], **p;
	int pfd[2];

	h = id%Nhash;

	qlock(&srv.lk);
	for(c=srv.hash[h]; c; c=c->next)
		if(c->id == id)
			myfatal("callalloc: duplicate id: %d", id);
	c = emallocz(sizeof(Call));
	c->ref = 1;
	c->id = id;
	c->sendaccm = ~0;
	c->recvaccm = ~0;

	if(!ipaddralloc(c))
		myfatal("callalloc: could not alloc remote ip address");

	if(pipe(pfd) < 0)
		myfatal("callalloc: pipe failed: %r");

	p = argv;
	*p++ = srv.pppexec;
	*p++ = "-SC";
	*p++ = "-x";
	*p++ = srv.pppdir;
	if(debug)
		*p++ = "-d";
	sprint(local, "%I", srv.ipaddr);
	*p++ = local;
	sprint(remote, "%I", c->remoteip);
	*p++ = remote;
	*p = 0;

	proc(argv, pfd[0], pfd[0], 2);

	close(pfd[0]);

	c->pppfd = pfd[1];

	c->next = srv.hash[h];
	srv.hash[h] = c;

	qunlock(&srv.lk);

	c->ref++;
	thread(pppread, c);
	c->ref++;
	thread(gretimeout, c);

	syslog(0, LOG, ": src=%I: call started: id=%d: remote ip=%I", srv.remote, id, c->remoteip);

	return c;
}

void
callclose(Call *c)
{
	Call *oc;
	int id;
	uint h;

	syslog(0, LOG, ": src=%I: call closed: id=%d: send=%d sendack=%d recv=%d recvack=%d dropped=%d missing=%d sendwait=%d sendtimeout=%d",
		srv.remote, c->id, c->stat.send, c->stat.sendack, c->stat.recv, c->stat.recvack,
		c->stat.dropped, c->stat.missing, c->stat.sendwait, c->stat.sendtimeout);

	qlock(&srv.lk);
	if(c->closed) {
		qunlock(&srv.lk);
		return;
	}
	c->closed = 1;

	close(c->dhcpfd[0]);
	close(c->dhcpfd[1]);
	close(c->pppfd);
	c->pppfd = -1;

	h = c->id%Nhash;
	id = c->id;
	for(c=srv.hash[h],oc=0; c; oc=c,c=c->next)
		if(c->id == id)
			break;
	if(oc == 0)
		srv.hash[h] = c->next;
	else
		oc->next = c->next;
	c->next = 0;
	qunlock(&srv.lk);

	callfree(c);
}

void
callfree(Call *c)
{	
	int ref;
	
	qlock(&srv.lk);
	ref = --c->ref;
	qunlock(&srv.lk);
	if(ref > 0)
		return;
	
	/* already unhooked from hash list - see callclose */
	assert(c->closed == 1);
	assert(ref == 0);
	assert(c->next == 0);

SDB "call free\n" EDB	
	free(c);
}

Call*
calllookup(int id)
{
	uint h;
	Call *c;

	h = id%Nhash;

	qlock(&srv.lk);
	for(c=srv.hash[h]; c; c=c->next)
		if(c->id == id)
			break;
	if(c != 0)
		c->ref++;
	qunlock(&srv.lk);

	return c;
}


void
srvinit(void)
{
	char buf[100];
	int fd, n;

	sprint(buf, "%s/local", srv.tcpdir);
	if((fd = open(buf, OREAD)) < 0)
		myfatal("could not open %s: %r", buf);
	if((n = read(fd, buf, sizeof(buf))) < 0)
		myfatal("could not read %s: %r", buf);
	buf[n] = 0;
	parseip(srv.local, buf);
	close(fd);

	sprint(buf, "%s/remote", srv.tcpdir);
	if((fd = open(buf, OREAD)) < 0)
		myfatal("could not open %s: %r", buf);
	if((n = read(fd, buf, sizeof(buf))) < 0)
		myfatal("could not read %s: %r", buf);
	buf[n] = 0;
	parseip(srv.remote, buf);
	close(fd);

	if(srv.pppdir == 0)
		srv.pppdir = "/net";

	if(srv.pppexec == 0)
		srv.pppexec = "/bin/ip/ppp";

	if(myipaddr(srv.ipaddr, srv.pppdir) < 0)
		myfatal("could not read local ip addr: %r");
	if(srv.recvwindow == 0)
		srv.recvwindow = Window;
}

void
greinit(void)
{
	char addr[100], *p;
	int fd, cfd;

	SDB "srv.tcpdir = %s\n", srv.tcpdir EDB
	strcpy(addr, srv.tcpdir);
	p = strrchr(addr, '/');
	if(p == 0)
		myfatal("bad tcp dir: %s", srv.tcpdir);
	*p = 0;
	p = strrchr(addr, '/');
	if(p == 0)
		myfatal("bad tcp dir: %s", srv.tcpdir);
	sprint(p, "/gre!%I!34827", srv.remote);

	SDB "addr = %s\n", addr EDB

	fd = dial(addr, 0, 0, &cfd);

	if(fd < 0)
		myfatal("%I: dial %s failed: %r", srv.remote, addr);

	srv.grefd = fd;
	srv.grecfd = cfd;

	thread(greread, 0);
}

void
greread(void *)
{
	uchar buf[Pktsize], *p;
	int n, i;
	int flag, prot, len, callid;
	uchar src[IPaddrlen], dst[IPaddrlen];
	uint rseq, ack;
	Call *c;
	static double t, last;

	for(;;) {
		n = read(srv.grefd, buf, sizeof(buf));
		if(n < 0)
			myfatal("%I: bad read on gre: %r", srv.remote);
		if(n == sizeof(buf))
			myfatal("%I: gre read: buf too small", srv.remote);

		p = buf;
		v4tov6(src, p);
		v4tov6(dst, p+4);
		flag = GSHORT(p+8);
		prot = GSHORT(p+10);
		p += 12; n -= 12;

		if(ipcmp(src, srv.remote) != 0 || ipcmp(dst, srv.local) != 0)
			myfatal("%I: gre read bad address src=%I dst=%I", srv.remote, src, dst);

		if(prot != GRE_ppp)
			myfatal("%I: gre read gave bad protocol", srv.remote);

		if(flag & (GRE_chksum|GRE_routing)){
			p += 4; n -= 4;
		}

		if(!(flag&GRE_key))
			myfatal("%I: gre packet does not contain a key: f=%ux",
				srv.remote, flag);

		len = GSHORT(p);
		callid = GSHORT(p+2);
		p += 4; n -= 4;

		c = calllookup(callid);
		if(c == 0) {
			SDB "%I: unknown callid: %d\n", srv.remote, callid EDB
			continue;
		}

		qlock(&c->lk);

		c->stat.recv++;

		if(flag&GRE_seq) {
			rseq = GLONG(p);
			p += 4; n -= 4;
		} else
			rseq = c->rseq;

		if(flag&GRE_ack){
			ack = GLONG(p);
			p += 4; n -= 4;
		} else
			ack = c->ack;

		/* skip routing if present */
		if(flag&GRE_routing) {
			while((i=p[3]) != 0) {
				n -= i;
				p += i;
			}
		}

		if(len > n)
			myfatal("%I: bad len in gre packet", srv.remote);

		if((int)(ack-c->ack) > 0) {
			c->ack = ack;
			esignal(&c->eack);
		}

		if(debug)
			t = realtime();

		if(len == 0) {
			/* ack packet */
			c->stat.recvack++;

SDB "%I: %.3f (%.3f): gre %d: recv ack a=%ux n=%d flag=%ux\n", srv.remote, t, t-last,
	c->id, ack, n, flag EDB

		} else {

SDB "%I: %.3f (%.3f): gre %d: recv s=%ux a=%ux len=%d\n", srv.remote, t, t-last,
	c->id, rseq, ack, len EDB

			/*
			 * the following handles the case of a single pair of packets
			 * received out of order
			 */
			n = rseq-c->rseq;
			if(n > 0 && (drop == 0. || frand() > drop)) {
				c->stat.missing += n-1;
				/* current packet */
				write(c->pppfd, p, len);
			} else {
				/* out of sequence - drop on the floor */
				c->stat.dropped++;

SDB "%I: %.3f: gre %d: recv out of order or dup packet: seq=%ux len=%d\n",
srv.remote, realtime(), c->id, rseq, len EDB

			}
		}

		if((int)(rseq-c->rseq) > 0)
			c->rseq = rseq;

		if(debug)
			last=t;

		/* open up client window */
		if((int)(c->rseq-c->rack) > (c->recvwindow>>1))
			greack(c);

		qunlock(&c->lk);


		callfree(c);
	}
}

void
greack(Call *c)
{
	uchar buf[20];

	c->stat.sendack++;

SDB "%I: %.3f: gre %d: send ack %ux\n", srv.remote, realtime(), c->id, c->rseq EDB

	v6tov4(buf+0, srv.local);		/* source */
	v6tov4(buf+4, srv.remote);		/* source */
	PSHORT(buf+8, GRE_key|GRE_ack|1);
	PSHORT(buf+10, GRE_ppp);
	PSHORT(buf+12, 0);
	PSHORT(buf+14, c->id);
	PLONG(buf+16, c->rseq);

	write(srv.grefd, buf, sizeof(buf));

	c->rack = c->rseq;

}

void
gretimeout(void *a)
{
	Call *c;

	c = a;

	while(!c->closed) {
		sleep(Tick);
		qlock(&c->lk);
		c->tick++;
		qunlock(&c->lk);
		esignal(&c->eack);
	}
	callfree(c);
	exits(0);
}


void
pppread(void *a)
{
	Call *c;
	uchar buf[2000], *p;
	int n;
	ulong tick;

	c = a;
	for(;;) {
		p = buf+24;
		n = read(c->pppfd, p, sizeof(buf)-24);
		if(n <= 0)
			break;
		
		qlock(&c->lk);

		/* add gre header */
		c->seq++;
		tick = c->tick;

		while(c->seq-c->ack>c->sendwindow && c->tick-tick<Sendtimeout && !c->closed) {
			c->stat.sendwait++;
SDB "window full seq = %d ack = %ux window = %ux\n", c->seq, c->ack, c->sendwindow EDB
			qunlock(&c->lk);
			ewait(&c->eack);
			qlock(&c->lk);
		}
		
		if(c->tick-tick >= Sendtimeout) {
			c->stat.sendtimeout++;
SDB "send timeout = %d ack = %ux window = %ux\n", c->seq, c->ack, c->sendwindow EDB
		}

		v6tov4(buf+0, srv.local);		/* source */
		v6tov4(buf+4, srv.remote);		/* source */
		PSHORT(buf+8, GRE_key|GRE_seq|GRE_ack|1);
		PSHORT(buf+10, GRE_ppp);
		PSHORT(buf+12, n);
		PSHORT(buf+14, c->id);
		PLONG(buf+16, c->seq);
		PLONG(buf+20, c->rseq);

		c->stat.send++;
		c->rack = c->rseq;

SDB "%I: %.3f: gre %d: send s=%ux a=%ux len=%d\n", srv.remote, realtime(),
	c->id,  c->seq, c->rseq, n EDB

		if(drop == 0. || frand() > drop)
			if(write(srv.grefd, buf, n+24)<n+24)
				myfatal("pppread: write failed: %r");

		qunlock(&c->lk);
	}

	SDB "pppread exit: %d\n", c->id);
	
	callfree(c);
	exits(0);
}

void
timeoutthread(void*)
{
	for(;;) {
		sleep(30*1000);

		qlock(&srv.lk);
		if(realtime() - srv.rcvtime > 5*60)
			myfatal("server timedout");
		qunlock(&srv.lk);
	}
}


/* use syslog() rather than fprint(2, ...) */
void
myfatal(char *fmt, ...)
{
	char sbuf[512];
	va_list arg;
	uchar buf[16];

	/* NT don't seem to like us just going away */
	memset(buf, 0, sizeof(buf));
	PSHORT(buf+0, sizeof(buf));	/* length */
	PSHORT(buf+2, 1);		/* message type */
	PLONG(buf+4, Magic);		/* magic */
	PSHORT(buf+8, Tstop);		/* op */
	buf[12] = 3;			/* local shutdown */

	write(1, buf, sizeof(buf));

	va_start(arg, fmt);
	vseprint(sbuf, sbuf+sizeof(sbuf), fmt, arg);
	va_end(arg);

	SDB "%I: fatal: %s\n", srv.remote, sbuf EDB
	syslog(0, LOG, ": src=%I: fatal: %s", srv.remote, sbuf);

	close(0);
	close(1);
	close(srv.grefd);
	close(srv.grecfd);

	postnote(PNGROUP, getpid(), "die");
	exits(sbuf);
}

int
argatoi(char *p)
{
	char *q;
	int i;

	if(p == 0)
		usage();

	i = strtol(p, &q, 0);
	if(q == p)
		usage();
	return i;
}

void
dhcpclientwatch(void *a)
{
	Call *c = a;
	uchar buf[1];

	for(;;) {
		if(read(c->dhcpfd[0], buf, sizeof(buf)) <= 0)
			break;
	}
	if(!c->closed)
		myfatal("dhcpclient terminated");
	callfree(c);
	exits(0);
}

int
ipaddralloc(Call *c)
{
	int pfd[2][2];
	char *argv[4], *p;
	Biobuf bio;

	argv[0] = "/bin/ip/dhcpclient";
	argv[1] = "-x";
	argv[2] = srv.pppdir;
	argv[3] = 0;

	if(pipe(pfd[0])<0)
		myfatal("ipaddralloc: pipe failed: %r");
	if(pipe(pfd[1])<0)
		myfatal("ipaddralloc: pipe failed: %r");

	if(proc(argv, pfd[0][0], pfd[1][1], 2) < 0)
		myfatal("ipaddralloc: proc failed: %r");

	close(pfd[0][0]);
	close(pfd[1][1]);
	c->dhcpfd[0] = pfd[1][0];
	c->dhcpfd[1] = pfd[0][1];

	Binit(&bio, pfd[1][0], OREAD);
	for(;;) {
		p = Brdline(&bio, '\n');
		if(p == 0)
			break;
		if(strncmp(p, "ip=", 3) == 0) {
			p += 3;
			parseip(c->remoteip, p);
		} else if(strncmp(p, "end\n", 4) == 0)
			break;
	}

	Bterm(&bio);

	c->ref++;

	thread(dhcpclientwatch, c);

	return ipcmp(c->remoteip, IPnoaddr) != 0;
}


void
esignal(Event *e)
{	
	qlock(e);
	if(e->wait == 0) {
		e->ready = 1;
		qunlock(e);
		return;
	}
	assert(e->ready == 0);
	e->wait = 0;
	rendezvous(e, (void*)1);
	qunlock(e);
}

void
ewait(Event *e)
{
	qlock(&e->waitlk);
	qlock(e);
	assert(e->wait == 0);
	if(e->ready) {
		e->ready = 0;
	} else {	
		e->wait = 1;
		qunlock(e);
		rendezvous(e, (void*)2);
		qlock(e);
	}
	qunlock(e);
	qunlock(&e->waitlk);
}

ulong
thread(void(*f)(void*), void *a)
{
	int pid;
	pid=rfork(RFNOWAIT|RFMEM|RFPROC);
	if(pid < 0)
		myfatal("rfork failed: %r");
	if(pid != 0)
		return pid;
	(*f)(a);
	return 0; // never reaches here
}

double
realtime(void)
{
	long times(long*);

	return times(0) / 1000.0;
}

void *
emallocz(int size)
{
	void *p;
	p = malloc(size);
	if(p == 0)
		myfatal("malloc failed: %r");
	memset(p, 0, size);
	return p;
}

static void
fdclose(void)
{
	int fd, n, i;
	Dir *d, *p;

	if((fd = open("/fd", OREAD)) < 0)
		return;

	n = dirreadall(fd, &d);
	for(p = d; n > 0; n--, p++) {
		i = atoi(p->name);
		if(i > 2)
			close(i);
	}
	free(d);
}

int
proc(char **argv, int fd0, int fd1, int fd2)
{
	int r, flag;
	char *arg0, file[200];

	arg0 = argv[0];

	strcpy(file, arg0);

	if(access(file, 1) < 0) {
		if(strncmp(arg0, "/", 1)==0
		|| strncmp(arg0, "#", 1)==0
		|| strncmp(arg0, "./", 2)==0
		|| strncmp(arg0, "../", 3)==0)
			return 0;
		sprint(file, "/bin/%s", arg0);
		if(access(file, 1) < 0)
			return 0;
	}

	flag = RFPROC|RFFDG|RFENVG|RFNOWAIT;
	if((r = rfork(flag)) != 0) {
		if(r < 0)
			return 0;
		return r;
	}

	if(fd0 != 0) {
		if(fd1 == 0)
			fd1 = dup(0, -1);
		if(fd2 == 0)
			fd2 = dup(0, -1);
		close(0);
		if(fd0 >= 0)
			dup(fd0, 0);
	}

	if(fd1 != 1) {
		if(fd2 == 1)
			fd2 = dup(1, -1);
		close(1);
		if(fd1 >= 0)
			dup(fd1, 1);
	}

	if(fd2 != 2) {
		close(2);
		if(fd2 >= 0)
			dup(fd2, 2);
	}

	fdclose();

	exec(file, argv);
	myfatal("proc: exec failed: %r");
	return 0;
}

