/*
 * virtual private network daemon (vpnd)
 *
 * cryptographic stuff (c) 1999 Andreas Steinmetz, astmail@yahoo.com
 * other code (c) 1999 D.O.M. Datenverarbeitung GmbH, author Andreas Steinmetz
 *
 * License:
 * This code is in the public domain under the GNU public license.
 * The copyright holders will however retain their copyright.
 * There is no guarantee for the fitness and usability of this code
 * for any purpose. The author and the copyright holders take no
 * responsibility for any damages caused by the use of this code.
 * Distribution and use of this code is explicitly granted provided
 * that the above header is not modified and the above conditions
 * are met.
 * 
 */

/* header files */

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>
#include <net/if.h>
#ifdef LINUX1
#include <net/if_route.h>
#include <linux/if_slip.h>
#endif
#ifdef LINUX2
#include <net/route.h>
#include <net/if_slip.h>
#endif
#include <sys/termios.h>
#include <stdlib.h>
#include <signal.h>
#include <syslog.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#ifdef COMPRESS
#include <zlib.h>
#endif
#include <errno.h>
#define BLOWFISH_NO_DECRYPT
#include "common.h"
#include "blowfish.h"

/* special definitions, do not change!!! */

#define MAXBLOCK 2048	/* largest block size transferred, do not change! */
#define XON 0x11	/* XON (for escaping/filtering)			  */
#define XOFF 0x13	/* XOFF (for escaping/filtering)		  */
#define ESC 0x1b	/* ESCAPE (for escaping/filtering)		  */

/* debug definitions */

#ifdef DEBUG
#define DEBUG1(a,b)         {if(debug>a){printf(b);fflush(stdout);}}
#define DEBUG2(a,b,c)       {if(debug>a){printf(b,c);fflush(stdout);}}
#define DEBUG3(a,b,c,d)     {if(debug>a){printf(b,c,d);fflush(stdout);}}
#define DEBUG4(a,b,c,d,e)   {if(debug>a){printf(b,c,d,e);fflush(stdout);}}
#define GOTO(a,b)           {DEBUG2(3,"%s\n",a);goto b;}
#define JUMP(a,b)           {DEBUG3(3,"%s %s\n",a,strerror(errno));goto b;}
#define RET(a,b)            {DEBUG2(3,"%s\n",a);return b;}
#define RETURN(a,b)         {DEBUG3(3,"%s %s\n",a,strerror(errno));return b;}
#define ERRNO(a)            {DEBUG3(3,"%s %s\n",a,strerror(errno));}
#define ENTER(a)            {DEBUG2(2,"%s called\n",a);}
#define LEAVE(a)            {DEBUG2(2,"%s complete\n",a);}
#else
#define DEBUG1(a,b)
#define DEBUG2(a,b,c)
#define DEBUG3(a,b,c,d)
#define DEBUG4(a,b,c,d,e)
#define GOTO(a,b)           goto b
#define JUMP(a,b)           goto b
#define RET(a,b)            return b
#define RETURN(a,b)         return b
#define ERRNO(a)            {}
#define ENTER(a)
#define LEAVE(a)
#endif

/* message ids for syslog messages */

#define SHUTDOWN    1
#define SIGNAL      2
#define SLIPFAIL    3
#define SLIPUP      4
#define SRVSFAIL    5
#define SRVSLSTN    6
#define PEERFAIL    7
#define CRYPTFAIL   8
#define CCONNECT    9
#define DEVFAIL    10
#define FORKFAIL   11
#define INITCHAT   12
#define SELECTFAIL 13
#define CHATFAIL   14
#define DCONNECT   15
#define SPEERFAIL  16
#define ACPTERR    17
#define ILLEGAL    18
#define PEEROPT    19
#define PEERIOCTL  20
#define SCRYPT     21
#define SCONNECT   22
#define KEYFAIL    23

/* configuration data structure */

typedef struct
{
	char *item;	/* command		*/
	int option;	/* 1 if optional	*/
	int min;	/* minimum parameters	*/
	int max;	/* maximum parameters	*/
	int found;	/* command found flag	*/
	char *p1;	/* first parameter	*/
	char *p2;	/* second parameter	*/
	char *p3;	/* third parameter	*/
} CONFIG;

/* configuration data array, keep sequence! */

CONFIG config[]=
{
{ "mode",      0,1,1,0,NULL,NULL,NULL },
{ "local",     0,1,1,0,NULL,NULL,NULL },
{ "remote",    0,1,1,0,NULL,NULL,NULL },
{ "server",    0,1,2,0,NULL,NULL,NULL },
{ "client",    0,1,2,0,NULL,NULL,NULL },
{ "keepalive", 1,0,1,0,NULL,NULL,NULL },
{ "autoroute", 1,0,0,0,NULL,NULL,NULL },
{ "keyfile",   1,1,1,0,NULL,NULL,NULL },
{ "route1",    1,3,3,0,NULL,NULL,NULL },
{ "route2",    1,3,3,0,NULL,NULL,NULL },
{ "route3",    1,3,3,0,NULL,NULL,NULL },
{ "route4",    1,3,3,0,NULL,NULL,NULL },
{ "route5",    1,3,3,0,NULL,NULL,NULL },
{ "route6",    1,3,3,0,NULL,NULL,NULL },
{ "route7",    1,3,3,0,NULL,NULL,NULL },
{ "route8",    1,3,3,0,NULL,NULL,NULL },
{ "route9",    1,3,3,0,NULL,NULL,NULL },
{ "pidfile",   1,1,1,0,NULL,NULL,NULL },
{ "keyttl",    1,1,1,0,NULL,NULL,NULL },
{ "randomdev", 1,1,1,0,NULL,NULL,NULL },
{ "mtu",       1,1,1,0,NULL,NULL,NULL },
{ "speed",     1,1,1,0,NULL,NULL,NULL },
{ "retry",     1,1,1,0,NULL,NULL,NULL },
{ "modemchat", 1,1,1,0,NULL,NULL,NULL },
{ "localline", 1,0,0,0,NULL,NULL,NULL },
{ "nortscts",  1,0,0,0,NULL,NULL,NULL },
{ "noanswer",  1,1,1,0,NULL,NULL,NULL },
{ "nocslip",   1,0,0,0,NULL,NULL,NULL },
{ "keysize",   1,1,1,0,NULL,NULL,NULL },
{ "nocompress",1,0,0,0,NULL,NULL,NULL },
{ "xfilter",   1,0,0,0,NULL,NULL,NULL },
{ "threshold", 1,1,1,0,NULL,NULL,NULL },
{ NULL,        0,0,0,0,NULL,NULL,NULL }
};

/* configuration data array access */

#define CFG_MODE(a)		config[0].a
#define CFG_LOCAL(a)		config[1].a
#define CFG_REMOTE(a)		config[2].a
#define CFG_SERVER(a)		config[3].a
#define CFG_CLIENT(a)		config[4].a
#define CFG_KEEPALIVE(a)	config[5].a
#define CFG_AUTOROUTE(a)	config[6].a
#define CFG_KEYFILE(a)		config[7].a
#define CFG_FIRSTROUTE		8
#define CFG_ROUTE(a,b)		config[a].b
#define CFG_LASTROUTE		16
#define CFG_PIDFILE(a)		config[17].a
#define CFG_KEYTTL(a)		config[18].a
#define CFG_RANDOMDEV(a)	config[19].a
#define CFG_MTU(a)		config[20].a
#define CFG_SPEED(a)		config[21].a
#define CFG_RETRY(a)		config[22].a
#define CFG_MODEMCHAT(a)	config[23].a
#define CFG_LOCALLINE(a)	config[24].a
#define CFG_NORTSCTS(a)		config[25].a
#define CFG_NOANSWER(a)		config[26].a
#define CFG_NOCSLIP(a)		config[27].a
#define CFG_KEYSIZE(a)		config[28].a
#define CFG_NOCOMPRESS(a)	config[29].a
#define CFG_XFILTER(a)		config[30].a
#define CFG_THRESHOLD(a)	config[31].a

/* total amount of routing entries possible */

#define ROUTES (CFG_LASTROUTE-CFG_FIRSTROUTE+1)

/* serial line speed conversion structure */

typedef struct
{
	int speednum;	/* numeric speed	*/
	int speedval;	/* speed setup value	*/
} SPEED;

/* serial line speeds */

SPEED speeddata[]=
{
	{ 19200,  B19200  },
	{ 38400,  B38400  },
	{ 57600,  B57600  },
	{ 115200, B115200 },
	{ 230400, B230400 },
#ifdef LINUX2
	{ 460800, B460800 },
#endif
	{ 0,      0       }
};

int pty=-1;				/* master side of tty		*/
int tty=-1;				/* tty used for slip interface	*/
int disc;				/* saved tty line dicipline	*/
int proxy;				/* number of slip device	*/
BLOWFISH_SCHEDULE(sched1);		/* key schedule 2		*/
BLOWFISH_SCHEDULE(sched2);		/* key schedule 1		*/
WORD32 *local;				/* key schedule local to remote */
WORD32 *remote;				/* key schedule remote to local */
WORD08 local_iv[BLOWFISH_BLOCKSIZE];	/* local iv			*/
WORD08 remote_iv[BLOWFISH_BLOCKSIZE];	/* remote iv			*/
char local_ip[16]="";			/* local ip of slip interface	*/
char remote_ip[16]="";			/* remote ip of slip interface	*/
int slip=0;				/* slip up flag			*/
int peer=-1;				/* handle to peer		*/
int autoroute=0;			/* ifconfig adds route		*/
int server=-1;				/* server accept handle		*/
char server_ip[16]="";			/* server ip address		*/
char client_ip[16]="";			/* client ip address		*/
int server_port=379;			/* server port			*/
int client_port=0;			/* client port			*/
int keepalive=0;			/* send pings when idle		*/
int ping=-1;				/* icmp socket for pings	*/
WORD08 icmp_out[64];			/* ping send buffer		*/
WORD08 icmp_in[64];			/* ping receive buffer		*/
struct sockaddr_in icmp_target;		/* remote address of slip link	*/
struct sockaddr_in icmp_source;		/* remote address of slip link	*/
int mode;				/* client/server mode		*/
WORD32 rtab[ROUTES][3];			/* additional routes		*/
int rmax=0;				/* total of additional routes	*/
char *pidfile=NULL;			/* process id file		*/
char *conffile="/etc/vpnd.conf";	/* default configuration file	*/
int keyttl=3600;			/* current key time to live	*/
int mtu=1500;				/* maximum transfer unit (slip)	*/
char *randomdev="/dev/random";		/* random number device file	*/
int serialmode;				/* flag for serial or ip mode	*/
int serial=-1;				/* serial device file handle	*/
int speed=B115200;			/* serial line speed		*/
char *serialdev;			/* serial device to be used	*/
int child;				/* init chat child process pid	*/
int retry=10;				/* global retry delay		*/
char *modemchat=NULL;			/* modem init chat file		*/
int localline=0;			/* CLOCAL for serial line	*/
int rtscts=1;				/* CRTSCTS for serial line	*/
mode_t normalmode;			/* saved file mode		*/
int timeout=0;				/* sigalrm flag for script test	*/
int verbose=0;				/* verbose mode for script test	*/
int noresponse=11;			/* missing pong line drop	*/
#ifdef DEBUG
int debug=0;				/* debug level			*/
#endif
int cslip=1;				/* compressed slip headers	*/
struct termios original;		/* original tty state		*/
struct termios line;			/* original serial state	*/
int keysize=BLOWFISH_MAXKEY;		/* key length in bytes		*/
char *keyfile="/etc/vpnd.key";		/* master key file		*/
#ifdef COMPRESS
int nocompress=0;			/* data compression disable	*/
#endif
WORD08 secret[BLOWFISH_MAXKEY];		/* shared secret		*/
WORD08 white[256];			/* whitening buffer		*/
WORD08 rwhite;				/* read data unwhiten index	*/
WORD08 wwhite;				/* write data whiten index	*/
int xfilter=0;				/* XON/OFF escape and filtering	*/
int threshold=16;			/* compression test threshold	*/


/*============================================================================*/
/* the slaves: data block send and receive                                    */
/*============================================================================*/

/*
 * dosend
 *
 * input:  data   - the data to be sent
 *	   length - the length of the data to be sent
 *
 * return: 1 - success
 *	   0 - failure
 *
 * This procedure sends data to the remote side.
 * The first byte must be transferred within 5 seconds,
 * all other bytes must be transferred within a one
 * second per byte interval.
 */

int dosend(WORD08 *data,WORD32 length)
{
	int l;			/* amount of data written		*/
	fd_set s;		/* write set for select call		*/
	fd_set e;		/* exception set for select call	*/
	struct timeval t;	/* time out for select call		*/
	int xflag;		/* character escape processing state	*/
	WORD08 bfr;		/* character escape work buffer		*/


	/* debug message */

	ENTER("dosend");

#ifdef DEBUG
	if(debug>5)
	{
		for(l=0;l<length;l++)printf("%02x ",data[l]);
		printf("\n");
	}
#endif

	/* reset escaping state */

	xflag=0;

	/* set up initial delay for select call */

	t.tv_sec=5;
	t.tv_usec=0;

	/* while there is data to be sent */

	while(length)
	{
		/* set up sets for select call */

		FD_ZERO(&s);
		FD_SET(peer,&s);
		FD_ZERO(&e);
		FD_SET(peer,&e);

		/* wait for data send possible, signal error, if not */

		l=select(peer+1,NULL,&s,&e,&t);
		if(l==-1)if(errno==EINTR||errno==EAGAIN)continue;
		if(l==0)RETURN("select timeout",0);

		/* signal error if exception occurred */

		if(FD_ISSET(peer,&e))RETURN("select exception",0);

		/* subsequent timeout for next select call */

		t.tv_sec=5;
		t.tv_usec=0;

		/* in XON/XOFF filter mode act according to escaping state */

		if(xfilter)switch(xflag)
		{
		/* in case of a to be escaped character */

		case 0:	if(*data==XON||*data==XOFF||*data==ESC)
			{
				/* set new escaping state */

				xflag=1;

				/* write escape character to buffer */

				bfr=ESC;
			}

			/* done */

			break;

		/* in case of the character to be escaped escape character */

		case 2: bfr=*data^0x80;

			/* done */

			break;
		}

		/* send data, react to errors */

		if((l=write(peer,xflag?&bfr:data,xfilter?1:length))<0)
		{
			if(errno==EINTR||errno==EWOULDBLOCK)continue;
			RETURN("write",0);
		}
		if(!l)RET("write (0 bytes)",0);

		/* in escaping state process escaping states */

		if(xflag)if(++xflag!=3)continue;
		else xflag=0;

		/* adjust data pointer and remaining length */

		data+=l;
		length-=l;
	}

	/* debug message */

	LEAVE("dosend");

	/* signal success */

	return 1;
}

/*
 * dorecv
 *
 * input:  length - the length of the data to be received
 *
 * output: data - the data to be received
 *
 * return: 1 - success
 *	   0 - failure
 *
 * This procedure receives data from the remote side.
 * The first byte must be transferred within 5 seconds,
 * all other bytes must be transferred within a one
 * second per byte interval.
 */

int dorecv(WORD08 *data,WORD32 length)
{
	int l;			/* amount of data read			*/
	fd_set s;		/* read set for select call		*/
	fd_set e;		/* exception set for select call	*/
	struct timeval t;	/* time out for select call		*/
	int xflag;		/* character escape processing state	*/
#ifdef DEBUG
	int len=length;		/* length memory for debug output	*/
	WORD08 *dta=data;	/* data pointer memory for debug output	*/
#endif


	/* debug message */

	ENTER("dorecv");

	/* reset escaping state */

	xflag=0;

	/* set up initial delay for select call */

	t.tv_sec=5;
	t.tv_usec=0;

	/* while there is data to be read */

	while(length)
	{
		/* set up sets for select call */

		FD_ZERO(&s);
		FD_SET(peer,&s);
		FD_ZERO(&e);
		FD_SET(peer,&e);

		/* wait for data read possible, signal error, if not */

		l=select(peer+1,&s,NULL,&e,&t);
		if(l==-1)if(errno==EINTR||errno==EAGAIN)continue;
		if(l==0)RETURN("select timeout",0);

		/* signal error if exception occurred */

		if(FD_ISSET(peer,&e))RETURN("select exception",0);

		/* subsequent timeout for next select call */

		t.tv_sec=5;
		t.tv_usec=0;

		/* read data, react to errors */

		if((l=read(peer,data,xfilter?1:length))<0)
		{
			if(errno==EINTR||errno==EWOULDBLOCK)continue;
			RETURN("read",0);
		}
		if(!l)RET("read (0 bytes)",0);

		/* in case of XON/XOFF filter */

		if(xfilter)
		{
			/* ignore all XON/XOFF characters */

			if(*data==XON||*data==XOFF)continue;

			/* process escaping state */

			switch(xflag)
			{
			/* if an escape character is received */

			case 0:	if(*data==ESC)
				{
					/* increase escaping state */

					xflag++;

					/* skip character */

					continue;
				}

				/* done */

				break;

			/* in case of an escaped character restore character */

			case 1:	*data^=0x80;

				/* reset escaping state */

				xflag=0;

				/* done */

				break;
			}
		}

		/* adjust data pointer and remaining length */

		data+=l;
		length-=l;
	}

	/* debug message */

#ifdef DEBUG
	if(debug>5)
	{
		for(l=0;l<len;l++)printf("%02x ",dta[l]);
		printf("\n");
	}
#endif

	LEAVE("dorecv");

	/* signal success */

	return 1;
}

/*============================================================================*/
/* shhhhhhh! the crypto stuff (blowfish itself is resident in its own file)   */
/*============================================================================*/

/*
 * getrandom
 *
 * input:  len - the amount of random data to collect
 *
 * output: out - 64 bytes of random data
 *
 * return: 0 - random data collection failed
 *	   1 - random data collection successful
 *
 * This procedure gathers and returns 64 bytes of random data.
 */

int getrandom(WORD08 *out,WORD32 len)
{
	int fd;	/* file descriptor of random device */


	/* debug message */

	ENTER("getrandom");

	/* open random device (no block if insufficient entropy),
	   handle errors */

	if((fd=open(randomdev,O_RDONLY))<0)RETURN("open",0);

	/* read random data, handle errors */

	if(read(fd,out,len)!=len)RETURN("read",0);

	/* close random device */

	if(close(fd))ERRNO("close");

	/* debug message */

	LEAVE("getrandom");

	/* signal success */

	return 1;
}

/*
 * checksum
 *
 * input:  data   - the data the checksum is to be created for
 *	   length - the length of the data
 *
 * output: out - the location the 2 byte checksum is to be stored at
 *
 * This procedure creates a checksum of given data and stores it to the
 * given location.
 */

void checksum(register WORD08 *data,register WORD32 length,WORD08 *out)
{
register WORD32 sum=0;	/* checksum creation buffer */


/* create checksum */

while(length--)sum=((sum<<1)|((sum>>15)&1))^*data++;

/* return checksum to caller */

*out++=(WORD08)(sum);
*out=(WORD08)(sum>>8);
}

/*
 * procedure encrypt_cfb_64_8
 *
 * input: total    - the amount of data elements to be processed
 *
 * inout: data     - the data to be processed
 *
 * This procedure encodes n sets of data bytes using
 * 8 and 64 bit cipher feedback (CFB) mode.
 */

void encrypt_cfb_64_8(WORD08 *data,WORD32 total)
{
	register WORD32 i;		/* general purpose usage	*/
	register WORD08 *ptr;		/* buffer fast access		*/
	register WORD08 mem;		/* general purpose memory	*/
	WORD08 bfr[BLOWFISH_BLOCKSIZE];	/* encryption buffer		*/


	/* debug message */

	ENTER("encrypt_cfb_64_8");

	/* copy iv to buffer */

	for(i=0,ptr=bfr;i<BLOWFISH_BLOCKSIZE;i++)*ptr++=local_iv[i];

	/* increase iv */

	for(mem=1,i=0,ptr=local_iv;mem&&i<BLOWFISH_BLOCKSIZE;i++)
	{
		*ptr+=mem;
		mem=*ptr++?0:1;
	}

	/* for all large blocks use CFB64 */

	while(total>=BLOWFISH_BLOCKSIZE)
	{
		/* encrypt buffer */

		blowfish_encrypt(bfr,local);

		/* XOR data bytes with buffer, memorize new iv
		   and do whitening */

		for(i=0,ptr=bfr;i<BLOWFISH_BLOCKSIZE;i++)
		{
			*data^=*ptr;
			*ptr++=*data;
			*data++^=white[wwhite++];
		}

		/* adjust data count */

		total-=BLOWFISH_BLOCKSIZE;
	}

	/* for all remaining data bytes use CFB8 */

	while(total--)
	{
		/* encrypt buffer */

		blowfish_encrypt(bfr,local);

		/* XOR data byte with buffer */

		*data^=*bfr;

		/* shift iv left and append cipher byte and do whitening */

		for(i=0,ptr=bfr+1;i<BLOWFISH_BLOCKSIZE-1;i++)bfr[i]=*ptr++;
		bfr[i]=*data;
		*data++^=white[wwhite++];
	}

	/* debug message */

	LEAVE("encrypt_cfb_64_8");
}

/*
 * procedure decrypt_cfb_64_8
 *
 * input: total    - the amount of data elements to be processed
 *
 * inout: data     - the data to be processed
 *
 * This procedure decodes n sets of data bytes using
 * 8 and 64 bit cipher feedback (CFB) mode.
 */

void decrypt_cfb_64_8(WORD08 *data,WORD32 total)
{
	register WORD32 i;		/* general purpose usage	*/
	register WORD08 *ptr;		/* buffer fast access		*/
	register WORD08 mem;		/* general purpose memory	*/
	WORD08 bfr[BLOWFISH_BLOCKSIZE];	/* encryption buffer		*/


	/* debug message */

	ENTER("decrypt_cfb_64_8");

	/* copy iv to buffer */

	for(i=0,ptr=bfr;i<BLOWFISH_BLOCKSIZE;i++)*ptr++=remote_iv[i];

	/* increase iv */

	for(mem=1,i=0,ptr=remote_iv;mem&&i<BLOWFISH_BLOCKSIZE;i++)
	{
		*ptr+=mem;
		mem=*ptr++?0:1;
	}

	/* for all large blocks use CFB64 */

	while(total>=BLOWFISH_BLOCKSIZE)
	{
		/* encrypt buffer */

		blowfish_encrypt(bfr,remote);

		/* unwhite, memorize new iv and then XOR data bytes 
		   with buffer */

		for(i=0,ptr=bfr;i<BLOWFISH_BLOCKSIZE;i++)
		{
			*data^=white[rwhite++];
			mem=*data;
			*data++^=*ptr;
			*ptr++=mem;
		}

		/* adjust data count */

		total-=BLOWFISH_BLOCKSIZE;
	}

	/* for all remaining data bytes use CFB8 */

	while(total--)
	{
		/* encrypt buffer */

		blowfish_encrypt(bfr,remote);

		/* unwhite, memorize and then XOR data byte with buffer */

		*data^=white[rwhite++];
		mem=*data;
		*data++^=*bfr;

		/* shift iv left and append cipher MSB */

		for(i=0,ptr=bfr+1;i<BLOWFISH_BLOCKSIZE-1;i++)bfr[i]=*ptr++;
		bfr[i]=mem;
	}

	/* debug message */

	LEAVE("decrypt_cfb_64_8");
}

/*
 * datasend
 *
 * input:  length - the length of the data to be sent
 *
 * output: data - the data to be sent
 *
 * return: 1 - success
 *	   0 - failure
 *
 * This procedure sends data encypted to the remote side.
 * It wraps the data with integrity checking information.
 * A maximum of BLOCKSIZE bytes can be sent. If the length of
 * the data to be sent is 0, new key exchange is done.
 */

int datasend(register WORD08 *data,register WORD32 length)
{
	register WORD32 i;			/* counter/index	*/
	WORD08 hdr[4];				/* data header		*/
#ifdef COMPRESS
	WORD08 tmp[MAXBLOCK+(MAXBLOCK/2)];	/* compression buffer	*/
	WORD32 tmplen=sizeof(tmp);		/* used for compression	*/
#endif
						/* new key and iv memory*/
	WORD08 key[BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY];
	WORD08 keymem[BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY];


	/* debug message */

	ENTER("datasend");

	/* key change requested */

	if(!length)
	{
		/* error if in plaintext mode */

		if(!keysize)RET("key change in plaintext mode",0);

		/* debug message */

		DEBUG1(1,"key change\n");

		/* create length header */

		hdr[2]=hdr[3]=0;

		/* server */

		if(!mode)
		{
			/* get 80 random bytes */

			if(!getrandom(key,BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY))
				GOTO("getrandom",keyerr);

			/* encrypt 80 bytes in ECB mode (you never know) */

			for(i=0;i<BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY;
				i+=BLOWFISH_BLOCKSIZE)
				blowfish_encrypt(key+i,local);

			/* create checksum header */

			checksum(key,BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY,hdr);

			/* memorize new key and iv */

			for(i=0;i<BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY;i++)
				keymem[i]=key[i];

			/* encrypt header */

			encrypt_cfb_64_8(hdr,4);

			/* send header to peer, handle errors */

			if(!dosend(hdr,4))GOTO("dosend hdr",keyerr);

			/* encrypt data */

			encrypt_cfb_64_8(key,
				BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY);

			/* send data to peer, handle errors */

			if(!dosend(key,BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY))
				GOTO("dosend key",keyerr);

			/* toggle local key schedule */

			if(local==sched1)local=sched2;
			else local=sched1;

			/* generate schedule from new key */

			blowfish_key_schedule(keymem+BLOWFISH_BLOCKSIZE,
				keysize,local);
		}

		/* client */

		else
		{
			/* get 8 random bytes */

			if(!getrandom(key,BLOWFISH_BLOCKSIZE))
				GOTO("getrandom",keyerr);

			/* encrypt 8 bytes in ECB mode (you never know) */

			blowfish_encrypt(key,local);

			/* create checksum header */

			checksum(key,BLOWFISH_BLOCKSIZE,hdr);

			/* memorize new iv */

			for(i=0;i<BLOWFISH_BLOCKSIZE;i++)keymem[i]=key[i];

			/* encrypt header */

			encrypt_cfb_64_8(hdr,4);

			/* send header to peer, handle errors */

			if(!dosend(hdr,4))GOTO("dosend hdr",keyerr);

			/* encrypt data */

			encrypt_cfb_64_8(key,BLOWFISH_BLOCKSIZE);

			/* send data to peer, handle errors */

			if(!dosend(key,BLOWFISH_BLOCKSIZE))
				GOTO("dosend key",keyerr);

			/* from now on use key schedule of key received
			   from server */

			local=remote;
		}

		/* setup new iv */

		for(i=0;i<BLOWFISH_BLOCKSIZE;i++)local_iv[i]=keymem[i];

		/* clean up */

		for(i=0;i<BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY;i++)
			key[i]=keymem[i]=0;

		/* debug message */

		LEAVE("datasend");

		/* signal success */

		return 1;

		/* error handling */

keyerr:		for(i=0;i<BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY;i++)
			key[i]=keymem[i]=0;
		return 0;
	}

	/* signal error if maximum length exceeded */

	if(length>MAXBLOCK)RET("illegal length",0);

#ifdef COMPRESS

	/* no compression if disabled by configuration */

	if(nocompress)tmplen=0;

	/* no compression if data is anyway too small */

	else if(length<threshold)tmplen=0;

	/* if compression fails signal no compression */

	else if(compress2(tmp,&tmplen,data,length,9))
	{
		DEBUG1(3,"compress err\n");
		tmplen=0;
	}
	/* if compressed result >= original size signal no compression */

	else if(tmplen>=length)tmplen=0;

	/* in case of compression */

	if(tmplen)
	{
		/* debug message */

		DEBUG1(3,"compressed data\n");

		/* create length header with MSB set */

		hdr[2]=(WORD08)(tmplen);
		hdr[3]=((WORD08)(tmplen>>8))|0x80;

		/* set data pointer to compressed data buffer */

		data=tmp;

		/* change data length to compressed data length */

		length=tmplen;
	}

	/* in case of no compression */

	else
	{
		/* create regular length header */

		hdr[2]=(WORD08)(length);
		hdr[3]=(WORD08)(length>>8);
	}
#else
	/* create length header */

	hdr[2]=(WORD08)(length);
	hdr[3]=(WORD08)(length>>8);
#endif

	/* create checksum header */

	checksum(data,length,hdr);

	/* debug message */

	DEBUG3(4,"length=%04lx chksum=%04lx\n",
		((WORD32)(hdr[3])<<8)|(WORD32)(hdr[2]),
		((WORD32)(hdr[1])<<8)|(WORD32)(hdr[0]));

	/* encrypt header if not in plaintext mode */

	if(keysize)encrypt_cfb_64_8(hdr,4);

	/* send header to peer, handle errors */

	if(!dosend(hdr,4))GOTO("dosend hdr",keyerr);

	/* encrypt data if not in plaintext mode */

	if(keysize)encrypt_cfb_64_8(data,length);

	/* send data to peer, handle errors */

	if(!dosend(data,length))GOTO("dosend data",keyerr);

	/* debug message */

	LEAVE("datasend");

	/* signal success */

	return 1;
}

/*
 * datarecv
 *
 * input:  max    - the length of the data buffer
 *
 * output: data - the data to be received
 *
 * return: >0 - success, the amount of data received + 1
 *	   0  - failure
 *
 * This procedure receives encypted data from the remote side.
 * It performs basic data integrity checking. Furthermore
 * it performes special handling when a new key is received,
 * i.e. it sets up the new key schedule and iv from the
 * received key.
 */

int datarecv(register WORD08 *data,WORD32 max)
{
	WORD08 hdr[4];			/* data header		*/
	WORD08 sum[2];			/* calculated checksum	*/
	register WORD32 i;		/* counter/index	*/
	register WORD32 length;		/* data length		*/
#ifdef COMPRESS
	WORD08 tmp[MAXBLOCK+(MAXBLOCK/2)];	/* compression buffer	*/
	WORD32 tmplen=max;		/* used for compression	*/
#endif
					/* new key and iv 	*/
	WORD08 newkey[BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY];


	/* debug message */

	ENTER("datarecv");

	/* read header from peer, signal errors */

	if(!dorecv(hdr,4))RET("dorecv hdr",0);

	/* decrypt received data if not in plaintext mode */

	if(keysize)decrypt_cfb_64_8(hdr,4);

	/* debug message */

	DEBUG3(4,"length=%04lx chksum=%04lx\n",
		((WORD32)(hdr[3])<<8)|(WORD32)(hdr[2]),
		((WORD32)(hdr[1])<<8)|(WORD32)(hdr[0]));

	/* new key from peer */

	if(!hdr[2]&&!hdr[3])
	{
		/* illegal, if in plaintext mode */

		if(!keysize)RET("key in plaintext mode",0);

		/* debug message */

		DEBUG1(1,"new key from peer\n");

		/* server */

		if(!mode)
		{
			/* read expected amount of data,signal errors */

			if(!dorecv(newkey,BLOWFISH_BLOCKSIZE))
				GOTO("dorecv newkey",keyerr);

			/* decrypt received data */

			decrypt_cfb_64_8(newkey,BLOWFISH_BLOCKSIZE);

			/* from now on use key schedule 
			   of shared new key */

			remote=local;
		}
		else
		{
			/* read expected amount of data,signal errors */

			if(!dorecv(newkey,BLOWFISH_BLOCKSIZE+
				BLOWFISH_MAXKEY))
				GOTO("dorecv newkey",keyerr);

			/* decrypt received data */

			decrypt_cfb_64_8(newkey,
				BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY);

			/* calculate checksum of read data */

			checksum(newkey,BLOWFISH_BLOCKSIZE+
				BLOWFISH_MAXKEY,sum);

			/* debug message */

			DEBUG3(4,"length=%04lx chksum=%04lx\n",
				((WORD32)(hdr[3])<<8)|(WORD32)(hdr[2]),
				((WORD32)(hdr[1])<<8)|(WORD32)(hdr[0]));

			/* in case of checksum mismatch signal error */

			if(hdr[0]!=sum[0]||hdr[1]!=sum[1])
				GOTO("chksum error",keyerr);

			/* toggle remote key schedule */

			if(remote==sched1)remote=sched2;
			else remote=sched1;

			/* generate schedule from new key */

			blowfish_key_schedule(newkey+BLOWFISH_BLOCKSIZE,
				keysize,remote);
		}

		/* setup new iv */

		for(i=0;i<BLOWFISH_BLOCKSIZE;i++)remote_iv[i]=newkey[i];

		/* clean up */

		for(i=0;i<BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY;i++)
			newkey[i]=0;

		/* debug message */

		LEAVE("datarecv");

		/* signal success */

		return 1;

		/* error handling */

keyerr:		for(i=0;i<BLOWFISH_BLOCKSIZE+BLOWFISH_MAXKEY;i++)
			newkey[i]=0;
		return 0;
	}

	/* restore data length */

#ifdef COMPRESS
	length=(((WORD32)((hdr[3]&0x7f)))<<8)|((WORD32)(hdr[2]));
#else
	length=(((WORD32)(hdr[3]))<<8)|((WORD32)(hdr[2]));
#endif
	/* signal error if length exceeds buffer size */

	if(length>max)RET("illegal length",0);

#ifdef COMPRESS

	/* in case of compressed data */

	if(hdr[3]&0x80)
	{
		/* debug message */

		DEBUG1(3,"compressed data\n");

		/* read expected amount of data, signal errors */

		if(!dorecv(tmp,length))RET("dorecv data",0);

		/* decrypt received data if not in plaintext mode */

		if(keysize)decrypt_cfb_64_8(tmp,length);

		/* calculate checksum of read data */

		checksum(tmp,length,sum);

		/* decompress data, signal errors */

		if(uncompress(data,&tmplen,tmp,length))
			RET("uncompress err",0);

		/* set up actual data length */

		length=tmplen;
	}

	/* in case of uncompressed data */

	else
	{
		/* read expected amount of data, signal errors */

		if(!dorecv(data,length))RET("dorecv data",0);

		/* decrypt received data if not in plaintext mode */

		if(keysize)decrypt_cfb_64_8(data,length);

		/* calculate checksum of read data */

		checksum(data,length,sum);
	}
#else
	/* read expected amount of data, signal errors */

	if(!dorecv(data,length))RET("dorecv data",0);

	/* decrypt received data if not in plaintext mode */

	if(keysize)decrypt_cfb_64_8(data,length);

	/* calculate checksum of read data */

	checksum(data,length,sum);
#endif
	/* debug message */

	DEBUG3(4,"chksum=%04lx expected=%04lx\n",
		((WORD32)(hdr[1])<<8)|(WORD32)(hdr[0]),
		((WORD32)(sum[1])<<8)|(WORD32)(sum[0]));

	/* in case of checksum mismatch signal error */

	if(hdr[0]!=sum[0]||hdr[1]!=sum[1])RET("chksum error",0);

	/* debug message */

	LEAVE("datarecv");

	/* signal success returning data length+1 */

	return length+1;
}

/*
 * genmaster
 *
 * return: 0 - success
 *	   1 - user/group not root
 *	   2 - getting first 72 random bytes failed
 *	   3 - getting second 72 random bytes failed
 *	   4 - opening master key file failed
 *	   5 - writing to master key file failed
 *	   6 - closing master key file failed
 *
 * This procedure creates a new master key and stores it
 * to the given file.
 */

int genmaster(void)
{
	WORD08 initial[BLOWFISH_MAXKEY];/* key creation memory		*/
	WORD08 key[BLOWFISH_MAXKEY];	/* key creation memory		*/
	time_t t;			/* current time			*/
	int f;				/* master key file handle	*/
	int i;				/* counter/index		*/


	/* debug message */

	ENTER("genmaster");

	/* get current time */

	t=time(NULL);

	/* check thet real user/group id is root */

	if(getuid()||getgid())
	{
		printf("You must be root and group root to create a key\n");
		return 1;
	}

	/* get 72 random bytes */

	if(!getrandom(key,BLOWFISH_MAXKEY))
	{
		printf("Failed to gather random data\n");
		return 2;
	}

	/* generate key schedule from random data */

	blowfish_key_schedule(key,BLOWFISH_MAXKEY,sched1);

	/* get another 72 random bytes */

	if(!getrandom(initial,BLOWFISH_MAXKEY))
	{
		printf("Failed to gather random data\n");
		return 3;
	}

	/* encrypt 72 bytes in ECB mode */

	for(i=0;i<BLOWFISH_MAXKEY;i+=BLOWFISH_BLOCKSIZE)
		blowfish_encrypt(initial+i,sched1);

	/* generate key schedule from encrypted data */

	blowfish_key_schedule(initial,BLOWFISH_MAXKEY,sched1);

	/* encrypt 72 bytes in ECB mode */

	for(i=0;i<BLOWFISH_MAXKEY;i+=BLOWFISH_BLOCKSIZE)
		blowfish_encrypt(key+i,sched1);

	/* make new key schedule from time */

	initial[0]=(WORD08)(t);
	initial[1]=(WORD08)(t>>8);
	initial[2]=(WORD08)(t>>16);
	initial[3]=(WORD08)(t>>24);

	/* generate key schedule from time and prevoius data */

	blowfish_key_schedule(initial,BLOWFISH_MAXKEY,sched1);

	/* encrypt 72 bytes in ECB mode */

	for(i=0;i<BLOWFISH_MAXKEY;i+=BLOWFISH_BLOCKSIZE)
		blowfish_encrypt(key+i,sched1);

	/* open key file */

	if((f=open(keyfile,O_CREAT|O_EXCL|O_SYNC|O_WRONLY,S_IRUSR))==-1)
	{
		ERRNO("open");
		printf("Can't create %s\n",keyfile);
		return 4;
	}

	/* write key to file */

	if(write(f,key,BLOWFISH_MAXKEY)!=BLOWFISH_MAXKEY)
	{
		ERRNO("write");
		printf("Writing %s failed\n",keyfile);
		close(f);
		unlink(keyfile);
		return 5;
	}

	/* close key file */

	if(close(f))
	{
		ERRNO("close");
		printf("Closing %s failed\n",keyfile);
		unlink(keyfile);
		return 6;
	}

	/* print message */

	printf("New key file %s created.\n",keyfile);

	/* debug message */

	LEAVE("genmaster");

	/* signal success */

	return 0;
}

/*
 * readmaster
 *
 * return: 1 in case of success, else 0
 *
 * This procedure reads the master secret from file to memory.
 */

int readmaster(void)
{
	int f;		/* file handle		*/
	int i;		/* counter/index	*/


	/* debug message */

	ENTER("readmaster");

	/* open key file, handle errors */

	if((f=open(keyfile,O_RDONLY))==-1)RETURN("open",0);

	/* read key file, handle errors */

	if(read(f,secret,BLOWFISH_MAXKEY)!=BLOWFISH_MAXKEY)
	{
		ERRNO("read");
		close(f);
		return 0;
	}

	/* close key file, handle errors */

	if(close(f))
	{
		for(i=0;i<BLOWFISH_MAXKEY;i++)secret[i]=0;
		RETURN("close",0);
	}

	/* debug message */

	LEAVE("readmaster");

	/* signal success */

	return 1;
}

/*
 * genwhite
 *
 * This procedure prepares the whitening buffer.
 */

void genwhite(void)
{
	int i;		/* counter/index	*/


	/* generate key schedule from master key */

	blowfish_key_schedule(secret,keysize,sched1);

	/* preset the whitening buffer with well known values */

	for(i=0;i<256;i++)white[i]=(WORD08)(i);

	/* encrypt the buffer four times */

	for(i=0;i<256;i+=BLOWFISH_BLOCKSIZE)blowfish_encrypt(white+i,sched1);
	for(i=0;i<256;i+=BLOWFISH_BLOCKSIZE)blowfish_encrypt(white+i,sched1);
	for(i=0;i<256;i+=BLOWFISH_BLOCKSIZE)blowfish_encrypt(white+i,sched1);
	for(i=0;i<256;i+=BLOWFISH_BLOCKSIZE)blowfish_encrypt(white+i,sched1);

	/* clean up */

	for(i=0;i<BLOWFISH_SCHEDULESIZE;i++)sched1[i]=0;
}

/*
 * server_setup
 *
 * return: 0 - success
 *	   1 - send of new key and server iv to client failed
 *	   2 - receive of client iv failed
 *
 * This procedure establishes encrypted communication with
 * the remote system in server mode.
 */

int server_setup(void)
{
	int i;				/* counter/index		*/
	WORD32 src;			/* numeric of local ip		*/
	WORD32 dst;			/* numeric of remote ip		*/


	/* debug message */

	ENTER("server_setup");

	/* reset whitening indexes */

	rwhite=wwhite=0;

	/* convert local and remote ip to numbers */

	src=(WORD32)inet_addr(local_ip);
	dst=(WORD32)inet_addr(remote_ip);

	/* copy local and remote ips to initial key */

	for(i=0;i<4;i++)
	{
		local_iv[i]=remote_iv[i]=(WORD08)(src>>(i<<3));
		local_iv[i+4]=remote_iv[i+4]=(WORD08)(dst>>(i<<3));
	}

	/* setup pointers to key schedule */

	local=remote=sched1;

	/* create key schedule from master secret */

	blowfish_key_schedule(secret,keysize,sched1);

	/* send new key and server iv, signal error in case of failure */

	if(!datasend(NULL,0))RET("datasend",1);

	/* receive client iv, signal error in case of failure */

	if(datarecv(NULL,0)!=1)RET("datarecv",2);

	/* debug message */

	LEAVE("server_setup");

	/* signal success */

	return 0;
}

/*
 * client_setup
 *
 * return: 0 - sucess
 *	   1 - receive of new key and server iv failed
 *	   2 - send of new client iv failed
 *
 * This procedure establishes encrypted communication with
 * the remote system in client mode.
 */

int client_setup(void)
{
	int i;				/* counter/index		*/
	WORD32 src;			/* numeric of local ip		*/
	WORD32 dst;			/* numeric of remote ip		*/


	/* debug message */

	ENTER("client_setup");

	/* reset whitening indexes */

	rwhite=wwhite=0;

	/* make sure that key update is only invoked by server */

	keyttl=0;

	/* convert local and remote ip to numbers */

	src=(WORD32)inet_addr(local_ip);
	dst=(WORD32)inet_addr(remote_ip);

	/* copy local and remote ips to initial key */

	for(i=0;i<4;i++)
	{
		local_iv[i+4]=remote_iv[i+4]=(WORD08)(src>>(i<<3));
		local_iv[i]=remote_iv[i]=(WORD08)(dst>>(i<<3));
	}

	/* setup pointers to key schedule */

	local=remote=sched1;

	/* create key schedule from master secret */

	blowfish_key_schedule(secret,keysize,sched1);

	/* receive new key and server iv, signal error in case of failure */

	if(datarecv(NULL,0)!=1)RET("datarecv",1);

	/* send new client iv, signal error in case of failure */

	if(!datasend(NULL,0))RET("datasend",2);

	/* debug message */

	LEAVE("client_setup");

	/* signal success */

	return 0;
}

/*============================================================================*/
/* slippery: slip interface creation and deletion                             */
/*============================================================================*/

/*
 * makeslip
 *
 * input:  mode - 0=use pty, 1=use serial line
 *
 * return: 0 - success
 *	   1 - no tty/pty available
 *	   2 - tty configuration failed
 *	   3 - saving line discipline failed
 *	   4 - setting slip line discipline failed
 *	   5 - setting slip encapsulation failed
 *	   6 - ifconfig failed
 *	   7 - creating route to partner failed
 *	   8 - creation of additional routes failed
 *	   9 - no icmp socket (with keepalive only)
 *	  10 - set socket option failed (with keepalive only)
 *
 * This procedure creates the proxy interface.
 */

int makeslip(int mode)
{
	int i;			/* general purpose	*/
	int j;			/* general purpose	*/
	int k;			/* general purpose	*/
	struct rtentry rt;	/* used to add routes	*/
	struct ifreq ifr;	/* used to set up if	*/
	char bfr[16];		/* string creation	*/
	struct termios t;	/* pty/tty settings	*/
	int err;		/* return code		*/


	/* debug message */

	ENTER("makeslip");

	/* reset return code */

	err=0;

	/* if a pseudo terminal has to be used */

	if(!mode)
	{
		/* try to get pty/tty device pair */

		for(pty=-1,i='p';i<'t'&&pty<0;i++)for(j=0;j<16;j++)
		{
			sprintf(bfr,"/dev/pty%c%x",i,j);
			if((pty=open(bfr,O_RDWR))>=0)
			{
				sprintf(bfr,"/dev/tty%c%x",i,j);
				if((tty=open(bfr,O_RDWR|O_NOCTTY|O_NDELAY))>=0)
					break;
				close(pty);
				pty=-1;
			}
		}

		/* signal error in case of failure */

		if(pty<0)JUMP("open",err1);

		/* save tty device attributes, react to errors */

		if(tcgetattr(tty,&original)<0)JUMP("tcgetattr",err2);

		/* set tty device attributes, react to errors */

		t.c_cflag=CS8|CREAD|HUPCL|CLOCAL;
		t.c_iflag=IGNBRK|IGNPAR;
		t.c_oflag=0;
		t.c_lflag=0;
		for(i=0;i<NCCS;i++)t.c_cc[i]=0;
		t.c_cc[VMIN]=1;
		t.c_cc[VTIME]=0;
		cfsetispeed(&t,B115200);
		cfsetospeed(&t,B115200);
		if(tcsetattr(tty,TCSAFLUSH,&t)<0)JUMP("tcsetattr",err2);
	}

	/* in serial line mode use serial line handle */

	else tty=serial;

	/* save old line discipline, react to errors */

	if(ioctl(tty,TIOCGETD,&disc)<0)JUMP("ioctl(TIOCGETD)",err3);

	/* set line discipline to SLIP, react to errors */

	i=N_SLIP;
	if((proxy=ioctl(tty,TIOCSETD,&i))<0)JUMP("ioctl(TIOCSETD)",err4);

	/* set encapsulation, react to errors */

	i=cslip?SL_MODE_CSLIP:SL_MODE_SLIP;
	if(ioctl(tty,SIOCGIFENCAP,&i)<0)JUMP("ioctl(SIOCGIFENCAP)",err5);

	/* get control socket, react to errors */

	if((i=socket(AF_INET,SOCK_DGRAM,0))==-1)JUMP("socket",err6);

	/* reset error flag */

	j=0;

	/* create interface name */

	sprintf(ifr.ifr_name,"sl%u",proxy);

	/* set up interface address, react to errors */

	((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_family=AF_INET;
	((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_addr.s_addr=
		inet_addr(local_ip);
	if(ioctl(i,SIOCSIFADDR,&ifr)<0)j++;

	/* set up interface peer address, react to errors */

	((struct sockaddr_in *)(&(ifr.ifr_dstaddr)))->sin_family=AF_INET;
	((struct sockaddr_in *)(&(ifr.ifr_dstaddr)))->sin_addr.s_addr=
		inet_addr(remote_ip);

	/* set up interface netmask, react to errors */

	if(!j)if(ioctl(i,SIOCSIFDSTADDR,&ifr)<0)j++;
	((struct sockaddr_in *)(&(ifr.ifr_netmask)))->sin_family=AF_INET;
	((struct sockaddr_in *)(&(ifr.ifr_netmask)))->sin_addr.s_addr=
		htonl(0xffffffff);

	/* set up interface mtu, react to errors */

	if(!j)if(ioctl(i,SIOCSIFNETMASK,&ifr)<0)j++;
	ifr.ifr_mtu=mtu;
	if(!j)if(ioctl(i,SIOCSIFMTU,&ifr)<0)j++;

	/* activate interface, react to errors */

	ifr.ifr_flags=IFF_UP|IFF_RUNNING|IFF_POINTOPOINT|IFF_NOARP|
		IFF_MULTICAST;
	if(!j)if(ioctl(i,SIOCSIFFLAGS,&ifr)<0)j++;

	/* close control socket */
 
	if(close(i))ERRNO("close");

	/* signal any errors encountered */

	if(j)JUMP("ioctl(SIOCSIFADDR,SIOCSIFDSTADDR,SIOCSIFNETMASK," \
		"SIOCSIFMTU,SIOCSIFFLAGS)",err6);

	/* set up route to partner, react to errors */

	if(!autoroute)
	{
		/* set up routing structure */

		sprintf(bfr,"sl%u",proxy);
		rt.rt_flags=RTF_UP|RTF_HOST;
		rt.rt_metric=1;
		rt.rt_dev=bfr;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_addr.s_addr=
			inet_addr(remote_ip);
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_addr.s_addr=
			htonl(0xffffffff);

		/* add routing entry to peer, handle errors */

		if((i=socket(AF_INET,SOCK_DGRAM,0))==-1)JUMP("socket",err7);
		j=ioctl(i,SIOCADDRT,&rt);
		if(close(i))ERRNO("close");
		if(j<0)JUMP("ioctl(SIOCADDRT)",err7);
	}

	/* for all additional routes */

	for(k=0;k<rmax;k++)
	{
		/* set up routing structure */

		rt.rt_dev=NULL;
		if(rtab[k][1]==-1)rt.rt_flags=RTF_UP|RTF_HOST|RTF_GATEWAY;
		else rt.rt_flags=RTF_UP|RTF_GATEWAY;
		rt.rt_metric=1;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_addr.s_addr=
			rtab[k][0];
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_addr.s_addr=
			rtab[k][1];
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_addr.s_addr=
			rtab[k][2];

		/* add routing entry, handle errors */

		if((i=socket(AF_INET,SOCK_DGRAM,0))==-1)JUMP("socket",err8);
		j=ioctl(i,SIOCADDRT,&rt);
		if(close(i))ERRNO("close");
		if(j<0)JUMP("ioctl(SIOCADDRT)",err8);
	}

	/* in case of keepalive packages when link idle */

	if(keepalive)
	{
		/* create icmp socket, react to errors */

		if((ping=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))==-1)
			JUMP("socket",err9);

		/* set socket to nonblocking, handle errors */

		i=1;
		if(ioctl(ping,FIONBIO,&i))JUMP("ioctl(FIONBIO)",err10);

		/* fill icmp output structure with random data */

		getrandom(icmp_out,sizeof(icmp_out));

		/* preset icmp header */

		((struct icmphdr *)(icmp_out))->type=ICMP_ECHO;
		((struct icmphdr *)(icmp_out))->code=0;
		((struct icmphdr *)(icmp_out))->checksum=0;
		((struct icmphdr *)(icmp_out))->un.echo.id=getpid();
		((struct icmphdr *)(icmp_out))->un.echo.sequence=0;

		/* prepare target address structure */

		icmp_target.sin_family=AF_INET;
		icmp_target.sin_addr.s_addr=inet_addr(remote_ip);
		icmp_target.sin_port=0;
	}

	/* mark slip link as established */

	slip=1;

	/* debug message */

	LEAVE("makeslip");

	/* signal success */

	goto err0;

	/* error handling */

err10:	err++;
err9:	err++;
err8:	err++;
	for(k=rmax-1;k>=0;k--)
	{
		rt.rt_dev=NULL;
		if(rtab[k][1]==-1)rt.rt_flags=RTF_UP|RTF_HOST|RTF_GATEWAY;
		else rt.rt_flags=RTF_UP|RTF_GATEWAY;
		rt.rt_metric=2;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_addr.s_addr=
			rtab[k][0];
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_addr.s_addr=
			rtab[k][1];
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_addr.s_addr=
			rtab[k][2];
		if((i=socket(AF_INET,SOCK_DGRAM,0))!=-1)
		{
			ioctl(i,SIOCDELRT,&rt);
			close(i);
		}
	}
	if(!autoroute)
	{
		rt.rt_flags=RTF_UP|RTF_HOST;
		rt.rt_metric=1;
		sprintf(bfr,"sl%u",proxy);
		rt.rt_dev=bfr;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_addr.s_addr=
			inet_addr(remote_ip);
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_addr.s_addr=
			htonl(0xffffffff);
		if((i=socket(AF_INET,SOCK_DGRAM,0))!=-1)
		{
			ioctl(i,SIOCDELRT,&rt);
			close(i);
		}
	}
err7:	err++;
	if((i=socket(AF_INET,SOCK_DGRAM,0))!=-1)
	{
		sprintf(ifr.ifr_name,"sl%u",proxy);
		ifr.ifr_flags=0;
		ioctl(i,SIOCSIFFLAGS,&ifr);
		((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_addr.s_addr=0;
		ioctl(i,SIOCSIFADDR,&ifr);
		close(i);
	}
err6:	err++;
err5:	err++;
	ioctl(tty,TIOCSETD,&disc);
err4:	err++;
err3:	err++;
err2:	err++;
	if(!mode)
	{
		close(tty);
		close(pty);
	}
err1:	err++;
err0:	return err;
}

/*
 * delslip
 *
 * input:  mode - 0=use pty, 1=use serial line
 *
 * return: 0 - success
 *	   1 - old line discipline/device config restore failed
 *	   2 - ifconfig down failed
 *	   3 - route deletion failed
 *	   4 - deletion of additional route failed
 *
 * This procedure deletes the proxy interface.
 */

int delslip(int mode)
{
	int i;			/* general purpose	*/
	int j;			/* general purpose	*/
	int k;			/* general purpose	*/
	struct rtentry rt;	/* used to del routes	*/
	struct ifreq ifr;	/* used to shut down if	*/
	char bfr[16];		/* string creation	*/
	int err;		/* return code		*/


	/* debug message */

	ENTER("delslip");

	/* done, if no slip link */

	if(!slip)RET("slip link not up",0);

	/* reset return code */

	err=0;

	/* in case of keepalive packages when link idle */

	if(keepalive)
	{
		/* close icmp socket, mark socket as closed */

		if(close(ping))ERRNO("close");
		ping=-1;
	}

	/* delete additional routes, handle errors */

	for(k=rmax-1;k>=0;k--)
	{
		/* set up routing structure */

		rt.rt_dev=NULL;
		if(rtab[k][1]==-1)rt.rt_flags=RTF_UP|RTF_HOST|RTF_GATEWAY;
		else rt.rt_flags=RTF_UP|RTF_GATEWAY;
		rt.rt_metric=2;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_addr.s_addr=
			rtab[k][0];
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_addr.s_addr=
			rtab[k][1];
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_addr.s_addr=
			rtab[k][2];

		/* delete routing entry, handle errors */

		if((i=socket(AF_INET,SOCK_DGRAM,0))==-1)JUMP("socket",err4);
		j=ioctl(i,SIOCDELRT,&rt);
		if(close(i))ERRNO("close");
		if(j<0)JUMP("ioctl(SIOCDELRT)",err4);
	}

	/* delete route to partner, react to errors */

	if(!autoroute)
	{
		/* set up routing structure */

		rt.rt_flags=RTF_UP|RTF_HOST;
		rt.rt_metric=1;
		sprintf(bfr,"sl%u",proxy);
		rt.rt_dev=bfr;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_addr.s_addr=
			inet_addr(remote_ip);
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_addr.s_addr=
			htonl(0xffffffff);

		/* delete routing entry to peer, handle errors */

		if((i=socket(AF_INET,SOCK_DGRAM,0))==-1)JUMP("socket",err3);
		j=ioctl(i,SIOCDELRT,&rt);
		if(close(i))ERRNO("close");
		if(j<0)JUMP("ioctl(SIOCDELRT)",err3);
	}

	/* get control socket, react to errors */

	if((i=socket(AF_INET,SOCK_DGRAM,0))==-1)JUMP("socket",err2);

	/* reset error flag */

	j=0;

	/* create interface name */

	sprintf(ifr.ifr_name,"sl%u",proxy);

	/* deactivate interface, react to errors */

	ifr.ifr_flags=0;
	if(ioctl(i,SIOCSIFFLAGS,&ifr)<0)j++;

	/* reset interface address, react to errors */

	((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_family=AF_INET;
	((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_addr.s_addr=0;
	if(!j)if(ioctl(i,SIOCSIFADDR,&ifr)<0)j++;

	/* close control socket */
 
	if(close(i))ERRNO("close");

	/* signal any errors encountered */

	if(j)JUMP("ioctl(SIOCSIFFLAGS,SIOCSIFADDR)",err2);

	/* close master in pseudo terminal mode */

	if(!mode)if(close(pty))ERRNO("close");

	/* restore original line discipline, react to errors */

	if(ioctl(tty,TIOCSETD,&disc)<0)JUMP("ioctl(TIOCSETD",err1);

	/* restore old device characteristics, react to errors */

	if(!mode)if(tcsetattr(tty,TCSAFLUSH,&original)<0)JUMP("tcsetattr",err1);

	/* debug message */

	LEAVE("delslip");

	/* close tty, mark slip link as down and signal success */

	goto err0;

	/* error handling */

err4:	err++;
	for(k=rmax-1;k>=0;k--)
	{
		rt.rt_dev=NULL;
		if(rtab[k][1]==-1)rt.rt_flags=RTF_UP|RTF_HOST|RTF_GATEWAY;
		else rt.rt_flags=RTF_UP|RTF_GATEWAY;
		rt.rt_metric=2;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_dst)))->sin_addr.s_addr=
			rtab[k][0];
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_genmask)))->sin_addr.s_addr=
			rtab[k][1];
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(rt.rt_gateway)))->sin_addr.s_addr=
			rtab[k][2];
		if((i=socket(AF_INET,SOCK_DGRAM,0))!=-1)
		{
			ioctl(i,SIOCDELRT,&rt);
			close(i);
		}
	}
err3:	err++;
	if((i=socket(AF_INET,SOCK_DGRAM,0))!=-1)
	{
		sprintf(ifr.ifr_name,"sl%u",proxy);
		ifr.ifr_flags=0;
		ioctl(i,SIOCSIFFLAGS,&ifr);
		((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_family=AF_INET;
		((struct sockaddr_in *)(&(ifr.ifr_addr)))->sin_addr.s_addr=0;
		ioctl(i,SIOCSIFADDR,&ifr);
		close(i);
	}
err2:	err++;
	if(!mode)close(pty);
	ioctl(tty,TIOCSETD,&disc);
	if(!mode)tcsetattr(tty,TCSAFLUSH,&original);
err1:	err++;
err0:	if(!mode)if(close(tty))ERRNO("delslip final close");
	slip=0;
	return err;
}

/*============================================================================*/
/* good-ole-line: serial interface stuff                                      */
/*============================================================================*/

/*
 * getserial
 *
 * return: 1 in case of success, else 0
 *
 * This procedure opens a serial line and sets it to
 * raw state at the requested speed (speed, 8N1).
 */

int getserial(void)
{
	struct termios t;	/* tty settings		*/
	int i;			/* general purpose	*/


	/* debug message */

	ENTER("getserial");

	/* open serial device, react to errors */

	if((serial=open(serialdev,O_RDWR|O_NONBLOCK|O_NOCTTY))==-1)
		JUMP("open",err1);

	/* save tty device attributes, react to errors */

	if(tcgetattr(serial,&line)<0)JUMP("tcgetattr",err2);

	/* change file protection for privacy, handle errors */

	if(fchmod(serial,0))JUMP("fchmod",err2);

	/* set dtr line, react to errors */

	if(ioctl(serial,TIOCMGET,&i)<0)JUMP("ioctl(TIOCMGET)",err3);
	i|=TIOCM_DTR;
	if(ioctl(serial,TIOCMSET,&i)<0)JUMP("ioctl(TIOCMSET)",err3);

	/* set tty device attributes, react to errors */

	t.c_cflag=CS8|CREAD|HUPCL|(rtscts?CRTSCTS:0)|(localline?CLOCAL:0);
	t.c_iflag=IGNBRK|IGNPAR;
	t.c_oflag=0;
	t.c_lflag=0;
	for(i=0;i<NCCS;i++)t.c_cc[i]=0;
	t.c_cc[VMIN]=1;
	t.c_cc[VTIME]=0;
	cfsetispeed(&t,speed);
	cfsetospeed(&t,speed);
	if(tcsetattr(serial,TCSAFLUSH,&t)<0)JUMP("tcsetattr",err4);

	/* debug message */

	LEAVE("getserial");

	/* signal success */

	return 1;

	/* error handling */

err4:	if(ioctl(serial,TIOCMGET,&i)>=0)
	{
		i&=~TIOCM_DTR;
		ioctl(serial,TIOCMSET,&i);
	}
err3:	fchmod(serial,normalmode);
err2:	close(serial);
	serial=-1;
err1:	return 0;
}

/*
 * dropserial
 *
 * This procedure closes the serial line opened with getserial().
 */

void dropserial(void)
{
	int i;	/* used to drop dtr line */


	/* debug message */

	ENTER("dropserial");

	/* only, if the serial line is open */

	if(serial!=-1)
	{
		/* restore previous file permissions */

		if(fchmod(serial,normalmode))ERRNO("fchmod");

		/* flush buffers, restore line settings */

		if(tcsetattr(serial,TCSAFLUSH,&line)<0)ERRNO("tcsetattr");

		/* drop dtr line */

		if(ioctl(serial,TIOCMGET,&i)>=0)
		{
			i&=~TIOCM_DTR;
			if(ioctl(serial,TIOCMSET,&i)<0)ERRNO("ioctl(TIOCMGET)");
		}
		else ERRNO("ioctl(TIOCMGET)");

		/* close device file */

		if(close(serial))ERRNO("close");

		/* mark device file as closed */

		serial=-1;
	}

	/* debug message */

	LEAVE("dropserial");
}

/*============================================================================*/
/* net-o-rama: ip over ip (dis)connection functionality                       */
/*============================================================================*/

/*
 * client_socket
 *
 * return: 0 - success
 *	   1 - no socket
 *	   2 - bind failed
 *	   3 - connect failed
 *	   4 - set socket option or socket ioctl failed
 *
 * This procedure establishes a connection with the remote peer
 * as a tcp/ip client.
 */

int client_socket(void)
{
	int s;			/* socket handle		*/
	struct sockaddr_in a;	/* connection data		*/
	int x;			/* used for socket ioctl	*/
	int err;		/* return code			*/
	struct linger l;	/* delay on closing socket	*/


	/* debug message */

	ENTER("client_socket");

	/* reset return value */

	err=0;

	/* create socket, handle errors */

	if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==-1)JUMP("socket",err1);

	/* bind socket if port given (allow address reuse), handle errors */

	if(client_port)
	{
		x=1;
		if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&x,sizeof(x)))
			JUMP("setsockopt(SO_REUSEADDR)",err2);
		a.sin_family=AF_INET;
		a.sin_addr.s_addr=inet_addr(client_ip);
		a.sin_port=htons(client_port);
		if(bind(s,(struct sockaddr *)(&a),sizeof(a)))JUMP("bind",err2);
	}

	/* connect to peer, handle errors */

	a.sin_family=AF_INET;
	a.sin_addr.s_addr=inet_addr(server_ip);
	a.sin_port=htons(server_port);
	if(connect(s,(struct sockaddr *)(&a),sizeof(a)))JUMP("connect",err3);

	/* set linger on close time, handle errors */

	l.l_onoff=1;
	l.l_linger=10;
	if(setsockopt(s,SOL_SOCKET,SO_LINGER,&l,sizeof(l)))
		JUMP("setsockopt(SO_LINGER)",err4);

	/* pervent packet coalescence */

	x=1;
	if(setsockopt(s,SOL_TCP,TCP_NODELAY,&x,sizeof(x)))
		JUMP("setsockopt(TCP_NODELAY)",err4);

	/* set socket to nonblocking, handle errors */

	x=1;
	if(ioctl(s,FIONBIO,&x))JUMP("ioctl(FIONBIO)",err4);

	/* make peer handle globally available */

	peer=s;

	/* debug message */

	LEAVE("client_socket");

	/* signal success */

	goto err0;

err4:	err++;
err3:	err++;
err2:	err++;
	close(s);
err1:	err++;
err0:	return err;
}

/*
 * server_socket
 *
 * return: 0 - success
 *	   1 - no socket
 *	   2 - set socket option failed
 *	   3 - bind failed
 *	   4 - listen failed
 *
 * This procedure creates a listening (server) tcp socket.
 */

int server_socket(void)
{
	int s;			/* connection wait handle	*/
	struct sockaddr_in a;	/* local/remote address port	*/
	int x;			/* setsockopt/ioctl parameter	*/
	int err;		/* return value			*/
	struct linger l;	/* delay on closing socket	*/


	/* debug message */

	ENTER("server_socket");

	/* reset return value */

	err=0;

	/* create socket, handle errors */

	if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==-1)
		JUMP("socket",err1);

	/* enable socket reuse, handle errors */

	x=1;
	if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&x,sizeof(x)))
		JUMP("setsockopt(SO_REUSEADDR)",err2);

	/* set linger on close time, handle errors */

	l.l_onoff=1;
	l.l_linger=10;
	if(setsockopt(s,SOL_SOCKET,SO_LINGER,&l,sizeof(l)))
		JUMP("setsockopt(SO_LINGER)",err2);

	/* bind socket, handle errors */

	a.sin_family=AF_INET;
	a.sin_addr.s_addr=inet_addr(server_ip);
	a.sin_port=htons(server_port);
	if(bind(s,(struct sockaddr *)(&a),sizeof(a)))JUMP("bind",err3);

	/* make socket listen, handle errors */

	if(listen(s,5))JUMP("listen",err4);

	/* make handle globally available */

	server=s;

	/* debug message */

	LEAVE("server_socket");

	/* signal success */

	goto err0;

err4:	err++;
err3:	err++;
err2:	err++;
err1:	err++;
err0:	return err;
}

/*
 * accept_socket
 *
 * output: addr - the address of the connectiong peer
 *
 * return: 0 - success
 *	   1 - accept of connection failed
 *	   2 - unexpected peer connected
 *	   3 - disabling packet coalescence failed
 *	   4 - setting socket to nonblocking failed
 *
 * This procedure accepts a client connection and
 * checks, that the client is the expected one.
 */

int accept_socket(struct sockaddr_in *addr)
{
	struct sockaddr_in a;	/* connection data		*/
	int x;			/* used for socket ioctl	*/
	int err;		/* return code			*/


	/* debug message */

	ENTER("accept_socket");

	/* reset return value */

	err=0;

	/* try to accept the connection */

	x=sizeof(a);
	if((peer=accept(server,(struct sockaddr *)(&a),&x))==-1)
		JUMP("accept",err1);

	/* return peer info to caller */

	*addr=a;

	/* if it's not the expected peer */

	if((a.sin_addr.s_addr!=inet_addr(client_ip)&&inet_addr(client_ip))||
		(client_port&&a.sin_port!=htons(client_port)))
		GOTO("illegal peer",err2);

	/* try to prevent packet coalescence */

	x=1;
	if(setsockopt(peer,SOL_TCP,TCP_NODELAY,&x,sizeof(x)))
		JUMP("setsockopt",err3);

	/* try to set socket to nonblocking */

	x=1;
	if(ioctl(peer,FIONBIO,&x))JUMP("ioctl",err4);

	/* debug message */

	LEAVE("accept_socket");

	/* signal success */

	goto err0;

	/* error handling */

err4:	err++;
err3:	err++;
err2:	err++;
	close(peer);
	peer=-1;
err1:	err++;
err0:	return err;
}

/*
 * disconnect
 *
 * input: mode - 0 for peer socket
 *	       - 1 for server socket
 *	       - 2 for peer and server socket
 *
 * This procedure disconnects the tcp/ip connection to the remote peer
 * or the listening server socket.
 */

void disconnect(int mode)
{
	/* debug message */

	ENTER("disconnect");

	/* if peer socket and if a connection is established */

	if(!(mode&1))if(peer!=-1)
	{
		/* if not in serial line mode */

		if(!serialmode)
		{
			/* send TCP_FIN */

			if(shutdown(peer,2))ERRNO("shutdown");

			/* close communication channel */

			if(close(peer))ERRNO("close");
		}

		/* in serial line mode close serial line */

		else dropserial();

		/* reset handle to no peer */

		peer=-1;
	}

	/* if server socket and if the server socket exists */

	if(mode)if(server!=-1)
	{
		/* close server socket */

		if(close(server))ERRNO("close");

		/* mark server socket as closed */

		server=-1;
	}

	/* debug message */

	LEAVE("disconnect");
}

/*============================================================================*/
/* the verbose one: syslog messages                                           */
/*============================================================================*/

/*
 * logmsg
 *
 * input: type   - the message id
 *	  reason - the reason code for the message
 *
 * This procedure writes a syslog message
 */

void logmsg(int type,int reason)
{
	struct in_addr addr;	/* used for ip to string conversion */


	/* debug message */

	ENTER("logmsg");

	/* print message according to message type given */

	switch(type)
	{
	case SHUTDOWN:
		syslog(LOG_NOTICE,"shutting down.");
		break;
	case SIGNAL:
		syslog(LOG_INFO,"signal %d received.",reason);
		break;
	case SLIPFAIL:
		syslog(LOG_NOTICE,"slip link failed, reason=%d, " \
			"will retry in %d seconds.",reason,retry);
		break;
	case SLIPUP:
		syslog(LOG_INFO,"slip link established.");
		break;
	case SRVSFAIL:
		syslog(LOG_NOTICE,"server socket failed, reason=%d, " \
			"will retry in %d seconds.",reason,retry);
		break;
	case SRVSLSTN:
		syslog(LOG_INFO,"listening with address %s on port %d",
			server_ip,server_port);
		break;
	case PEERFAIL:
		syslog(LOG_INFO,"peer link failed, reason=%d, " \
			"will retry in %d seconds.",reason,retry);
		break;
	case CRYPTFAIL:
		syslog(LOG_NOTICE,"crypto init failed, reason=%d, " \
			"will retry in %d seconds.",reason,retry);
		break;
	case CCONNECT:
		syslog(LOG_INFO,"connected to %s",server_ip);
		break;
	case DEVFAIL:
		syslog(LOG_INFO,"access to %s failed, " \
			"will retry in %d seconds.",serialdev,retry);
		break;
	case FORKFAIL:
		syslog(LOG_INFO,"fork failed, will retry in %d seconds.",retry);
		break;
	case INITCHAT:
		syslog(LOG_INFO,"serial line init chat started on %s",
			serialdev);
		break;
	case SELECTFAIL:
		syslog(LOG_NOTICE,"select failed, dying...");
		break;
	case CHATFAIL:
		syslog(LOG_INFO,"init chat failed, reason %d, " \
			"will retry in %d seconds.",reason,retry);
		break;
	case DCONNECT:
		syslog(LOG_INFO,"connection established on %s",serialdev);
		break;
	case SPEERFAIL:
		syslog(LOG_INFO,"peer link failed, reason %d, " \
			"closing connection.",reason);
		break;
	case ACPTERR:
		syslog(LOG_INFO,"client connect failed, still waiting.");
		break;
	case ILLEGAL:
		addr.s_addr=reason;
		syslog(LOG_NOTICE,"illegal connect from %s, " \
			"closing connection.",inet_ntoa(addr));
		break;
	case PEEROPT:
		syslog(LOG_NOTICE,"peer socket option setup failed, " \
			"closing connection.");
		break;
	case PEERIOCTL:
		syslog(LOG_NOTICE,"peer socket ioctl failed, " \
			"closing connection.");
		break;
	case SCRYPT:
		syslog(LOG_NOTICE,"crypto init failed, reason=%d, " \
			"closing connection.",reason);
		break;
	case SCONNECT:
		addr.s_addr=reason;
		syslog(LOG_INFO,"connect from %s",inet_ntoa(addr));
		break;
	case KEYFAIL:
		syslog(LOG_NOTICE,"reading master key from file failed, " \
			"terminating...");
		break;
	default:
		syslog(LOG_NOTICE,"unknown message with reason %d",reason);
		break;
	}

	/* debug message */

	LEAVE("logmsg");
}

/*============================================================================*/
/* the graveyard: process termination                                         */
/*============================================================================*/

/*
 * die
 *
 * This procedure does a final clean up and then exits. It never returns.
 */

void die(void)
{
	int i;			/* counter/index	*/


	/* debug message */

	ENTER("die");

	/* kill init chat child if any */

	if(child>0)if(kill(child,SIGTERM))ERRNO("kill");

	/* shut down slip interface if up */

	delslip(!keysize&&serialmode?1:0);

	/* disconnect from peer if connected/close server socket if any */

	disconnect(2);

	/* close open serial line if open */

	dropserial();

	/* reset cryptographic data */

	for(i=0;i<BLOWFISH_SCHEDULESIZE;i++)sched1[i]=sched2[i]=0;
	for(i=0;i<BLOWFISH_BLOCKSIZE;i++)local_iv[i]=remote_iv[i]=0;
	for(i=0;i<BLOWFISH_MAXKEY;i++)secret[i]=0;

	/* print info */

	logmsg(SHUTDOWN,0);

	/* end logging */

	closelog();

	/* remove pid file if used */

	if(pidfile)if(unlink(pidfile))ERRNO("unlink");

	/* debug message */

	LEAVE("die");

	/* exit process */

	exit(1);
}

/*============================================================================*/
/* events and friends: signal handling and setup                              */
/*============================================================================*/

/*
 * sigdie
 *
 * This procedure is a signal front end that calls die if a
 * termination signal is received.
 */

void sigdie(int sig)
{
	/* debug message */

	ENTER("sigdie");

	/* print info */

	logmsg(SIGNAL,sig);

	/* clean up and terminate */

	die();

	/* debug message (should never be reached) */

	LEAVE("sigdie");
}

/*
 * chatalarm
 *
 * This procedure is a signal front end that calls die if a
 * termination signal is received.
 */

void chatalarm(int sig)
{
	/* debug message */

	ENTER("chatalarm");

	/* print info */

	if(verbose)printf("command timed out\n");

	/* set timeout flag */

	timeout=1;

	/* debug message */

	LEAVE("chatalarm");
}

/*
 * chatdie
 *
 * This procedure is a signal front end that calls die if a
 * termination signal is received.
 */

void chatdie(int sig)
{
	/* debug message */

	ENTER("chatdie");

	/* clean up and terminate */

	die();

	/* debug message (should never be reached) */

	LEAVE("chatdie");
}

/*
 * setsig
 *
 * input: signal - the signal for which action is to be taken
 *	  action - the action to be taken for the signal
 *
 * This procedure sets up a signal handler for the
 * desired signal which is sequentialized and
 * multiple signal aware.
 */

void setsig(int signal,void (*action)(int))
{
	struct sigaction act;	/* signal setup structure */


	/* debug message */

	ENTER("setsig");

	/* preset signal setup structure */

	act.sa_handler=action;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;

	/* set signal handler */

	sigaction(signal,&act,NULL);

	/* debug message */

	LEAVE("setsig");
}

/*
 * blocksig
 *
 * input: signal - the signal which is to be blocked
 *
 * This procedure blocks a signal from delivery.
 */

void blocksig(int signal)
{
	sigset_t t;	/* signal setup structure */


	/* debug message */

	ENTER("blocksig");

	/* preset signal setup structure */

	sigemptyset(&t);
	sigaddset(&t,signal);

	/* block signal */

	sigprocmask(SIG_BLOCK,&t,NULL);

	/* debug message */

	LEAVE("blocksig");
}

/*============================================================================*/
/* the big league: modem init chat, data pump, config parser and main         */
/*============================================================================*/

/*
 * chatter
 *
 * return: 0 in case of success, else <>0
 *
 * This procedure handles all serial line initalization chat.
 * It is run as a child process. If this process exits wit 0
 * then initialization is assumed to be successful.
 */

int chatter(void)
{
	FILE *fp;	/* modem chat script file handle	*/
	char bfr[1024];	/* general purpose buffer		*/
	char lbl[1024];	/* general purpose buffer		*/
	int line=0;	/* current chat script line number	*/
	char *item;	/* command as string			*/
	char *time;	/* string command wait time		*/
	int delay;	/* numeric command wait time		*/
	char *label;	/* command failure jump label		*/
	char *value;	/* command parameter			*/
	char val[1024];	/* command parameter (unescaped)	*/
	int i;		/* general purpose usage		*/
	int j;		/* general purpose usage		*/
	char *ptr;	/* general purpose usage		*/
	int cmd;	/* internal command number		*/


	/* debug message */

	ENTER("chatter");

	/* assert that sigint and sigterm do not write log messages */

	setsig(SIGINT,chatdie);
	setsig(SIGTERM,chatdie);

	/* set up handler for alarm() timout */

	setsig(SIGALRM,chatalarm);

	/* close access to slip device if any */

	if(slip)
	{
		close(pty);
		close(tty);
	}

	/* set serial line mode to nonblocking, handle errors */

	i=0;
	if(ioctl(serial,FIONBIO,&i))
	{
		if(verbose)printf("changing serial line to " \
			"nonblocking failed\n");
		return 1;
	}

	/* if a modem chat script has to be processed */

	if(modemchat)
	{
		/* shut up compiler */

		value=NULL;

		/* reset timeout flag */

		timeout=0;

		/* try to open chat script file, handle errors */

		if((fp=fopen(modemchat,"r"))==NULL)
		{
			if(verbose)printf("accessing %s failed\n",modemchat);
			return 2;
		}

		/* for all lines of the chat script */

		while(fgets(bfr,sizeof(bfr),fp))
		{
			/* increase line number */

			line++;

			/* remove any trailing newline */

			if(*bfr)if(bfr[strlen(bfr)-1]=='\n')
				bfr[strlen(bfr)-1]=0;

			/* skip comment and empty lines */

			if(!*bfr||*bfr=='#')continue;

			/* get command and command wait time from line */

			item=strtok(bfr," 	");
			time=strtok(NULL," 	");
			label=strtok(NULL," 	");

			/* if the current command is a jump target */

			if(item)
			{
				/* if jump label */

				if(*item==':')
				{
					/* if looking for a jump target after
					   timeout and the target is found
					   reset timeout flag */

					if(timeout)if(!strcmp(lbl,item+1))
						timeout=0;

					/* print target if not in seek mode */

					if(!timeout)if(verbose)
						printf("%d: target %s\n",
						line,item+1);

					/* continue with next line */

					continue;
				}

				/* in case of a goto command and not in
				   label seek mode */

				else if(!strcmp(item,"goto")&&!timeout)
				{
					/* handle missing label error */

					if(!time)
					{
						if(verbose)
							printf("illegal " \
								"command " \
								" format in " \
								"line %d of " \
								"%s\n",line,
								modemchat);
						return 3;
					}

					/* print message in test mode */

					if(verbose)printf("%d: goto %s\n",
						line,time);

					/* save current target */

					strcpy(lbl,time);

					/* switch to label seek mode */

					timeout=1;
					fseek(fp,0,SEEK_SET);
					line=0;
				}

				/* in case of a sleep command if not in
				   label seek mode */

				else if(!strcmp(item,"sleep")&&!timeout)
				{
					/* print message in test mode */

					if(verbose)printf("%d: sleep\n",line);

					/* sleep for a second */

					sleep(1);

					/* next line */

					continue;
				}
			}

			/* don't process commands during label seek */

			if(timeout)continue;

			/* error if command or wait time are not given */

			if(!item||!time||!label)
			{
				if(verbose)printf("illegal command format in " \
					"line %d of %s\n",line,modemchat);
				return 3;
			}

			/* save current target */

			strcpy(lbl,label);

			/* error if wait time < 1 */

			if((delay=atoi(time))<=0)
			{
				if(verbose)printf("illegal wait time given " \
					"in line %d of %s\n",line,modemchat);
				return 4;
			}

			/* set up dcdwait command number */

			if(!strcmp(item,"dcdwait"))cmd=2;

			/* set up dsrwait command number */

			else if(!strcmp(item,"dsrwait"))cmd=3;

			/* set up ringwait command number */

			else if(!strcmp(item,"ringwait"))cmd=4;

			/* get additional parameters for send/wait/expect cmd */

			else if(!strcmp(item,"send")||!strcmp(item,"expect")||
				!strcmp(item,"wait"))
			{
				/* set up internal command number */

				if(!strcmp(item,"send"))cmd=0;
				else if(!strcmp(item,"wait"))cmd=1;
				else cmd=5;

				/* get command value */

				value=strtok(NULL," 	");

				/* error if value not given */

				if(!value)
				{
					if(verbose)printf("illegal command " \
						"format in line %d of %s\n",
						line,modemchat);
					return 5;
				}

				/* unescape value, handle errors */

				for(j=0,ptr=value;*ptr;ptr++)
				{
					/* in case of the escape character */

					if(*ptr=='\\')
					{
						/* process next character as
						   escape indentification,
						   handle errors */

						switch(*(++ptr))
						{
						case '\\': val[j++]='\\';
							  break;
						case 'b': val[j++]=' ';
							  break;
						case 't': val[j++]='\t';
							  break;
						case 'r': val[j++]='\r';
							  break;
						case 'n': val[j++]='\n';
							  break;
						default: if(verbose)printf(
								"illegal " \
								"escape " \
								"sequence in " \
								"line %d of " \
								"%s\n",line,
								modemchat);
							 return 6;
						}
					}

					/* just copy regular characters */

					else val[j++]=*ptr;
				}

				/* terminate unescaped value string */

				val[j]=0;
			}

			/* unkown command */

			else
			{
				if(verbose)printf("unknown command in line " \
					"%d of %s\n",line,modemchat);
				return 99;
			}

			/* print current command in proper format */

			if(verbose)
			{
				printf("%d: %s %d %s",line,item,delay,lbl);
				switch(cmd)
				{
				case 0:	
				case 1:
				case 5:	printf(" %s",value);
					break;
				}
				printf("\n");
			}

			/* process current command with given timeout */

			timeout=0;
			alarm(delay);

			switch(cmd)
			{
			/* send */

			case 0: /* send unescaped string to modem,
				   handle errors */

				if(write(serial,val,strlen(val))!=strlen(val))
				{
					/* print message in test mode */

					if(verbose)printf("write failed\n");

					/* terminate if no timeout */

					if(!timeout)return 101;
				}

				/* done */

				break;

			/* wait/expect */

			case 1:
			case 5: /* do, until the given string is found
				   or a timeout occurs */

				while(1)
				{
					/* at maximum for the given
					   buffer length - 1 */

					for(i=0;i<sizeof(bfr)-1;)
					{
						/* read character from
						   modem, handle errors */

						if(read(serial,bfr+i,1)!=1)
						{
							/* in test mode
							   print message */

							if(verbose)printf(
								"read " \
								"failed\n");

							/* terminate if no
							   timeout */

							if(!timeout)return 102;
						}

						/* exit loop if timeout */

						if(timeout)break;

						/* skip NUL characters */

						if(!bfr[i])continue;

						/* exit loop if newline read */

						if(bfr[i++]=='\n')break;
					}

					/* exit line loop if timeout */

					if(timeout)break;

					/* terminate line read */

					bfr[i]=0;

					/* print line in modem test mode */

					if(verbose)printf("read: %s",bfr);

					/* exit loop if given string found
					   in line */

					if((ptr=strstr(bfr,val))!=NULL)break;

					/* exit loop in expect mode with
					   error */

					if(cmd==5)
					{
						timeout=1;
						break;
					}
				}

				/* done */

				break;

			/* dcdwait */

			case 2:	/* do, until dcd is set or a timeout occurs */

				while(!timeout)
				{
					/* get modem status, handle errors */

					if(ioctl(serial,TIOCMGET,&i)<0)
					{
						/* in test mode print message */

						if(verbose)printf("can't get " \
							"modem control line " \
							"status\n");

						/* terminate */

						return 103;
					}

					/* exit loop if dcd detected */

					if(i&TIOCM_CAR)break;

					/* seep for 0.1 seconds */

					usleep(100000);
				}

				/* done */

				break;

			/* dsrwait */

			case 3:	/* do, until dsr is set or a timeout occurs */

				while(!timeout)
				{
					/* get modem status, handle errors */

					if(ioctl(serial,TIOCMGET,&i)<0)
					{
						/* in test mode print message */

						if(verbose)printf("can't get " \
							"modem control line " \
							"status\n");

						/* terminate */

						return 103;
					}

					/* exit loop if dcd detected */

					if(i&TIOCM_DSR)break;

					/* seep for 0.1 seconds */

					usleep(100000);
				}

				/* done */

				break;

			/* ringwait */

			case 4:	/* do, until dcd is set or a timeout occurs */

				while(!timeout)
				{
					/* get modem status, handle errors */

					if(ioctl(serial,TIOCMGET,&i)<0)
					{
						/* in test mode print message */

						if(verbose)printf("can't get " \
							"modem control line " \
							"status\n");

						/* terminate */

						return 103;
					}

					/* exit loop if dcd detected */

					if(i&TIOCM_RNG)break;

					/* seep for 0.1 seconds */

					usleep(100000);
				}

				/* done */

				break;
			}

			/* reset alarm */

			alarm(0);

			/* in case of a timeout seek for jump label 
			   target from beginning of file, handle
			   special labels fail and done directly */

			if(timeout)
			{
				if(!strcmp(lbl,"fail"))return 1;
				if(!strcmp(lbl,"done"))return 0;
				fseek(fp,0,SEEK_SET);
				line=0;
			}
		}

		/* close modem chat script */

		fclose(fp);

		/* handle label search errors */

		if(timeout)
		{
			if(verbose)printf("target %s not found\n",lbl);
			return 199;
		}
	}

	/* debug message */

	LEAVE("chatter");

	/* signal success */

	return 0;
}

/*
 * sequencer
 *
 * This procedure performs all necessary processing to transfer data
 * between the local slip interface and the slip interface of the peer.
 * It never returns.
 */

void sequencer(void)
{
	fd_set r;		/* read set for select			*/
	fd_set w;		/* write set for select			*/
	fd_set e;		/* exception set for select		*/
	WORD08 lbfr[MAXBLOCK];	/* local input buffer			*/
	WORD08 rbfr[MAXBLOCK];	/* peer input buffer			*/
	WORD08 *lptr=lbfr;	/* local input rover			*/
	WORD08 *rptr=rbfr;	/* peer input rover			*/
	struct timeval t;	/* timeout for select			*/
	time_t current;		/* current time in seconds		*/
	time_t next_slip;	/* next slip retry in seconds		*/
	time_t next_peer;	/* next peer connect retry in seconds	*/
	time_t next_ping;	/* next time to idle ping in seconds	*/
	time_t next_server;	/* next time create srv sk in seconds	*/
	time_t next_key;	/* next key create time in seconds	*/
	int result;		/* general purpose function call result	*/
	int high;		/* highest file descriptor for select	*/
	WORD32 localdata;	/* data bytes in local input buffer	*/
	WORD32 remotedata;	/* data bytes in peer input buffer	*/
	struct sockaddr_in a;	/* peer connection data			*/
	int len;		/* current amount of bytes read		*/
	int sum;		/* used to create checksum for icmp	*/
	int i;			/* used to crate checksum for icmp	*/
	int sendkey;		/* flag that key change is required	*/
	int leaky;		/* leaky bucket count			*/


	/* debug message */

	ENTER("sequencer");

	/* if plaintext mode on serial interface */

	if(!keysize&&serialmode)
	{
		/* never exit this loop */

		while(1)
		{
			/* try to get serial device, handle errors */

			if(!getserial())
			{
				logmsg(DEVFAIL,0);
				sleep(retry);
				continue;
			}

			/* print message */

			logmsg(INITCHAT,0);

			/* do init chat, handle errors */

			if((result=chatter())!=0)
			{
				logmsg(CHATFAIL,result);
				dropserial();
				sleep(retry);
				continue;
			}

			/* create slip interface, handle errors */

			if((result=makeslip(1))!=0)
			{
				logmsg(SLIPFAIL,result);
				dropserial();
				sleep(retry);
				continue;
			}

			/* print message */

			logmsg(DCONNECT,0);

			/* while select returns no serial line exception */

			while(1)
			{
				/* set serial line exception bit */

				FD_ZERO(&e);
				FD_SET(serial,&e);

				/* wait until select is interrupted or an
				   exception occurs */

				result=select(high,NULL,NULL,&e,NULL);

				/* interrupt or severe error */

				if(result==-1)
				{
					/* debug message */

					DEBUG1(3,"select interrupted\n");

					/* ignore interrupts */

					if(errno==EINTR||errno==EAGAIN)continue;

					/* print info and terminate */

					logmsg(SELECTFAIL,0);
					die();
				}

				/* exception on serial line */

				if(FD_ISSET(serial,&e))
				{
					/* print log message, drop slip
					   interface, close serial line,
					   wait and retry */

					logmsg(PEERFAIL,111);
					delslip(1);
					dropserial();
					sleep(retry);
					break;
				}
			}
		}
	}

	/* do only, if not in plaintext mode */

	if(keysize)
	{
		/* read the master key from file, if this fails print a log
		   message and terminate */

		if(!readmaster())
		{
			logmsg(KEYFAIL,0);
			die();
		}

		/* fill whitening buffer */

		genwhite();
	}

	/* reset leaky bucket count */

	leaky=noresponse;

	/* reset key change flag and buffer contents length variables */

	next_key=sendkey=localdata=remotedata=0;

	/* force connection to peer, creation of slip link and idle pings */

	next_ping=next_peer=next_slip=current=next_server=time(NULL)+1;

	/* do, until a fatal error or a signal occurs */

	while(1)
	{
		/* if the slip link is down and it's time to retry */

		if(!slip&&next_slip<=current)
		{
			/* try to set up slip link */

			result=makeslip(0);

			/* if link creation failed */

			if(result)
			{
				/* write info */

				logmsg(SLIPFAIL,result);

				/* retry link build */

				next_slip=current+retry;
			}

			/* if link creation was successful print info */

			else logmsg(SLIPUP,0);
		}

		/* if not in serial line mode */

		if(!serialmode)
		{
			/* if the peer link is not established, we're the
			   server, the server socket does not exist and it is
			   time to create the server socket */

			if(peer==-1&&server==-1&&!mode&&next_server<=current)
			{
				/* get server socket */

				result=server_socket();

				/* if socket creation failed */

				if(result)
				{
					/* give info */

					logmsg(SRVSFAIL,result);

					/* retry socket creation */

					next_server=current+retry;
				}

				/* in case of successful socket creation 
				   print info */

				else logmsg(SRVSLSTN,0);
			}

			/* if the peer link is not established, we're the client
			   and it is time to create the link */

			if(peer==-1&&mode&&next_peer<=current)
			{
				/* try to establish the peer link */

				result=client_socket();

				/* if link creation failed */

				if(result)
				{
					/* print info */

					logmsg(PEERFAIL,result);

					/* retry link creation */

					next_peer=current+retry;
				}

				/* if crypto setup fails in crypto mode */

				else if((result=keysize?client_setup():0)!=0)
				{
					/* print info */

					logmsg(CRYPTFAIL,result);

					/* close peer connection */

					disconnect(0);

					/* retry link creation */

					next_peer=current+retry;
				}

				/* in case of successful link creation */

				else 
				{
					/* print info */

					logmsg(CCONNECT,0);

					/* set up time for key change */

					next_key=current+keyttl;
					sendkey=0;
				}
			}
		}

		/* if in serial line mode if serial line is closed and
		   no child process is running and it is time for the
		   next try */

		else if(serial==-1&&next_peer<=current&&!child)
		{
			/* if accessing serial line device fails */

			if(!getserial())
			{
				/* print info */

				logmsg(DEVFAIL,0);

				/* retry serial device access */

				next_peer=current+retry;
			}

			/* if serial line is now open and configured */

			else
			{
				/* try to create child process */

				child=fork();

				/* if the child process creation failed */

				if(child==-1)
				{
					/* reset child process pid */

					child=0;

					/* print info */

					logmsg(FORKFAIL,0);

					/* close serial device */

					dropserial();

					/* retry fork */

					next_peer=current+retry;
				}

				/* child executes chatter (never returns) */

				else if(!child)exit(chatter());

				/* parent prints info */

				else logmsg(INITCHAT,0);
				}
			}

		/* get current time in seconds */

		current=time(NULL);

		/* reset highest file descriptor number */

		high=0;

		/* reset sets for select call */

		FD_ZERO(&r);
		FD_ZERO(&w);
		FD_ZERO(&e);

		/* if the slip link is up */

		if(slip)
		{
			/* check for read possible if empty local buffer */

			if(!localdata)FD_SET(pty,&r);

			/* check for write possible if peer data available */

			if(remotedata)FD_SET(pty,&w);

			/* always check for exceptions */

			FD_SET(pty,&e);

			/* set new highest file descriptor if pty is it */

			if(pty>high)high=pty;
		}

		/* if the peer link is up */

		if(peer!=-1)
		{
			/* signal key change if not disabled and
			   time has come for it */

			if(keyttl)if(current>=next_key)sendkey=1;

			/* check for read possible if empty peer buffer */

			if(!remotedata)FD_SET(peer,&r);

			/* check for write possible if local data available */

			if(localdata)FD_SET(peer,&w);

			/* always check for exceptions */

			FD_SET(peer,&e);

			/* set new highest file descriptor if peer is it */

			if(peer>high)high=peer;
		}

		/* if the peer link is down in server mode and if the
		   sever socket exists */

		else if(!mode&&server!=-1)
		{
			/* check for pending connect request */

			FD_SET(server,&r);

			/* always check for exceptions */

			FD_SET(server,&e);

			/* set new highest file descriptor if server is it */

			if(server>high)high=server;
		}

		/* increase highest file descriptor for select call */

		high++;

		/* set 1 second timeout for select call */

		t.tv_sec=1;
		t.tv_usec=0;

		/* check descriptor sets */

		result=select(high,&r,&w,&e,&t);

		/* in case of a fatal error or signal */

		if(result<0)
		{
			/* debug message */

			DEBUG1(3,"select interrupted\n");

			/* ignore interrupts */

			if(errno==EINTR||errno==EAGAIN)continue;

			/* print info and terminate */

			logmsg(SELECTFAIL,0);
			die();
		}

		/* if there's a serial line init chat process */

		if(child)
		{
			/* debug message */

			DEBUG1(3,"child running\n");

			/* if the process did terminate */

			if(waitpid(child,&high,WNOHANG))
			{
				/* reset child process id */

				child=0;

				/* get exit code of child process */

				if(!WIFEXITED(high))high=256;
				else high=WEXITSTATUS(high);

				/* if line initialization failed */

				if(high)
				{
					/* print info */

					logmsg(CHATFAIL,high);

					/* close serial line */

					dropserial();

					/* retry serial line */

					next_peer=current+retry;
				}

				/* if line initialization is successful */

				else
				{
					/* reset leaky bucket count */

					leaky=noresponse;

					/* set up link handle to remote */

					peer=serial;

					/* if not in plaintext mode */

					if(keysize)
					{
						/* do crypto setup */

						if(mode)high=client_setup();
						else high=server_setup();
					}

					/* in plaintext mode signal success */

					else high=0;

					/* if crypto setup fails */

					if(high)
					{
						/* print info */

						logmsg(CRYPTFAIL,high);

						/* disconnect from peer */

						disconnect(0);

						/* retry connection */

						next_peer=current+retry;
					}

					/* if crypto setup was ok */

					else
					{
						/* print info */

						logmsg(DCONNECT,0);

						/* set up next key change */

						next_key=current+keyttl;
						sendkey=0;
					}
				}
			}
		}

		/* if keepalive and both interfaces enabled */

		if(keepalive&&slip&&peer!=-1)
		{
			/* dummy read of echo replies, if at least one
			   reply reset leaky bucket count */

			sum=100;
			while(sum--)
			{
				icmp_source=icmp_target;
				i=sizeof(icmp_source);
				if(recvfrom(ping,icmp_in,sizeof(icmp_in),0,
					(struct sockaddr *)(&icmp_source),&i)==
					-1)break;
				leaky=noresponse;

				DEBUG1(3,"icmp received\n");
			}

		}

		/* if nothing to do */

		if(!result)
		{
			/* debug message */

			DEBUG2(3,"idle, current=%08lx\n",current);

			/* if idle pings are requested and links are up
			   and it is time for the next ping */

			if(keepalive&&slip&&peer!=-1)
			{
				/* done if it's not time to ping */

				if(next_ping>current)continue;

				/* reset checksum in icmp header */

				((struct icmphdr *)(icmp_out))->checksum=0;

				/* calculate packet checksum */

				for(sum=i=0;i<sizeof(icmp_out);)
				{
					sum+=icmp_out[i++];
					sum+=((int)(icmp_out[i++]))<<8;
				}
				while(sum&0xffff0000)sum=(sum>>16)+(sum&0xffff);
				sum=~sum;

				/* write checksum complement to header */

				((WORD08 *)&(((struct icmphdr *)(icmp_out))->
					checksum))[0]=(WORD08)(sum);
				((WORD08 *)&(((struct icmphdr *)(icmp_out))->
					checksum))[1]=(WORD08)(sum>>8);

				/* send echo request */

				sendto(ping,icmp_out,sizeof(icmp_out),0,
					(struct sockaddr *)(&icmp_target),
					sizeof(icmp_target));

				/* debug message */

				DEBUG1(3,"icmp sent\n");

				/* next sequence number */

				((struct icmphdr *)(icmp_out))->
					un.echo.sequence+=1;

				/* set up time for next ping */

				next_ping=current+keepalive;

				/* if serial mode if empty leaky bucket */

				if(serialmode)if(!--leaky)
				{
					/* print message, set retry time
					   and disconnect */

					logmsg(PEERFAIL,101);
					next_peer=current+retry;
					disconnect(0);
				}
			}

			/* make sure idle pings are made when interfaces
			   are up and idle pings are requested */

			else next_ping=current+keepalive;

			/* repeat whole process */

			continue;
		}

		/* set time for next idle ping */

		next_ping=current+keepalive;

		/* if the peer connection exists */

		if(peer!=-1)
		{
			/* if there is a peer exception */

			if(FD_ISSET(peer,&e))
			{
				/* print proper info (client/server),
				   set connect retry for client and
				   close peer connection */

				if(!mode&&!serialmode)
				{
					logmsg(SPEERFAIL,101);
					next_server=current+1;
				}
				else
				{
					logmsg(PEERFAIL,102);
					next_peer=current+retry;
				}
				disconnect(0);
			}
		}

		/* is the peer connection doesn't exist and there's a
		   server socket exception */

		else if(!mode&&server!=-1)if(FD_ISSET(server,&e))
		{
			/* close server socket */

			disconnect(1);

			/* print info */

			logmsg(SRVSFAIL,101);

			/* set server socket retry time */

			next_server=current+retry;
		}

		/* if the peer connection doesn't exist, we're in server
		   mode, the server socket exists and there's a connection
		   pending */

		if(peer==-1&&!mode&&server!=-1)if(FD_ISSET(server,&r))
		{
			switch(accept_socket(&a))
			{
			/* successful connection */

			case 0:	/* print connection info */

				logmsg(SCONNECT,a.sin_addr.s_addr);

				/* if crypto init fails in crypto mode */

				if((result=keysize?server_setup():0)!=0)
				{
					/* print info */

					logmsg(SCRYPT,result);

					/* close peer connection */

					disconnect(0);
				}

				/* if crypto init is successful */

				else
				{
					/* reset leaky bucket count */

					leaky=noresponse;

					/* set next key change time */

					next_key=current+keyttl;

					/* reset new key required flag */

					sendkey=0;

					/* close server socket */

					disconnect(1);
				}
				break;

			/* accept failed */

			case 1:	logmsg(ACPTERR,0);
				break;

			/* connection from unexpected peer refused */

			case 2:	logmsg(ILLEGAL,a.sin_addr.s_addr);
				break;

			/* setsockopt failed */

			case 3:	logmsg(PEEROPT,0);
				break;

			/* ioctl failed */

			case 4:	logmsg(PEERIOCTL,0);
				break;
			}
		}

		/* if the slip link exists and there's an exception pending */

		if(slip)if(FD_ISSET(pty,&e))
		{
			/* print info */

			logmsg(SLIPFAIL,101);

			/* drop slip link */

			delslip(0);

			/* set up slip link retry */

			next_slip=current+retry;
		}

		/* if the peer link exists and it's time to send a new key
		   and send is possible */

		if(sendkey&&peer!=-1)if(FD_ISSET(peer,&w))
		{
			/* reset key send required flag */

			sendkey=0;

			/* if not in plaintext mode */

			if(keysize)
			{
				/* if send of new key fails */

				if(!datasend(NULL,0))
				{
					/* print proper message and set proper
					   link retry time according to mode */

					if(!mode)
					{
						logmsg(SPEERFAIL,102);
						next_server=current+1;
					}
					else
					{
						logmsg(PEERFAIL,103);
						next_peer=current+retry;
					}

					/* close peer connection */

					disconnect(0);

					/* flush data buffers */

					remotedata=localdata=0;
				}

				/* in case of success */

				else
				{
					/* set up next key change time */

					next_key=current+keyttl;
				}

				/* restart loop as writing to peer may not yet
				   be possible again */

				continue;
			}
		}

		/* if both slip and peer link are established */

		if(slip&&peer!=-1)
		{
			/* if there's data from the peer in the buffer */

			if(remotedata)
			{
				/* if we can write to the slip interface */

				if(FD_ISSET(pty,&w))
				{
					/* if write to slip is successful */

					if((len=write(pty,rptr,remotedata))>0)
					{
						/* adjust data pointer */

						rptr+=len;

						/* adjust data length */

						remotedata-=len;
					}

					/* in case of write failure */

					else
					{
						/* print info */

						logmsg(SLIPFAIL,102);

						/* shut down slip interface */

						delslip(0);

						/* retry slip */

						next_slip=current+retry;

						/* flush data buffers */

						remotedata=localdata=0;
					}
				}
			}

			/* if there's no data from peer in the buffer */

			else
			{

				/* if peer data are available */

				if(FD_ISSET(peer,&r))
				{
					/* reset buffer rover */

					rptr=rbfr;

					/* in case of successful read
					   set new buffer length */

					if((len=datarecv(rbfr,MAXBLOCK))>0)
					{
						/* store new received 
						   data length */

						remotedata=len-1;

						/* change key/iv if new 
						   key from peer */

						if(!remotedata&&mode)sendkey=1;
					}

					/* in case of read failure */

					else
					{
						/* print proper message
						   according to mode,
						   in client mode retry */

						if(!mode&&!serialmode)
						{
							logmsg(SPEERFAIL,103);
							next_server=current+1;
						}
						else
						{
							logmsg(PEERFAIL,104);
							next_peer=current+retry;
						}

						/* close peer connection */

						disconnect(0);

						/* flush data buffers */

						remotedata=localdata=0;
					}
				}
			}

			/* if there is data from the slip interface available */

			if(localdata)
			{
				/* if data can be written to peer */

				if(peer!=-1&&FD_ISSET(peer,&w))
				{
					/* flush buffer in case of successful
					   send */

					if(datasend(lptr,localdata))localdata=0;

					/* in case of send failure */

					else
					{
						/* print proper message (client/
						   server) and retry in
						   client mode */

						if(!mode&&!serialmode)
						{
							logmsg(SPEERFAIL,104);
							next_server=current+1;
						}
						else
						{
							logmsg(PEERFAIL,105);
							next_peer=current+retry;
						}

						/* close connection to peer */

						disconnect(0);

						/* flush data buffers */

						remotedata=localdata=0;
					}
				}
			}

			/* if there's no data from slip in the buffer */

			else
			{
				/* if data from slip are available */

				if(slip&&FD_ISSET(pty,&r))
				{
					/* reset buffer rover */

					lptr=lbfr;

					/* in case of successful read adjust
					   buffer length */

					if((len=read(pty,lbfr,MAXBLOCK))>0)
						localdata=len;

					/* in case of read failure */

					else
					{
						/* print info */

						logmsg(SLIPFAIL,103);

						/* shut down slip */

						delslip(0);

						/* retry slip */

						next_slip=current+retry;

						/* flush data buffers */

						remotedata=localdata=0;
					}
				}
			}
		}

		/* if either slip or peer link is not established */

		else
		{
			/* if slip link is estableshed and there's data pending
			   try to read the data, if this fails drop the slip 
			   link, set link state to down and then the slip retry
			 */

			if(slip)if(FD_ISSET(pty,&r))if(read(pty,lbfr,MAXBLOCK)<=0)
			{
				logmsg(SLIPFAIL,104);
				delslip(0);
				next_slip=current+retry;
				remotedata=localdata=0;
			}

			/* if peer link is established and there's data pending
			   try to read the data, if this fails drop the peer
			   link, print proper info (client/server) and set the
			   peer retry.
			 */

			if(peer!=-1)if(FD_ISSET(peer,&r))
				if(!(i=datarecv(rbfr,MAXBLOCK)))
			{
				if(!mode&&!serialmode)
				{
					logmsg(SPEERFAIL,105);
					next_server=current+1;
				}
				else
				{
					logmsg(PEERFAIL,106);
					next_peer=current+retry;
				}
				disconnect(0);
				remotedata=localdata=0;
			}

			/* change key/iv if new key from peer */

			else if(i==1&&mode)sendkey=1;

		}
	}

	/* debug message, should never be reached */

	LEAVE("sequencer");
}

/*
 * parse
 *
 * return: 0 in case of error, 1 in case of success
 *
 * This procedure performs basic configuration file
 * parsing and data gathering.
 */

int parse(void)
{
	FILE *fp;		/* config file access	*/
	char *item;		/* current item		*/
	char bfr[1024];		/* input buffer		*/
	int i;			/* counter/index	*/
	int j;			/* counter/index	*/
	struct stat stb;	/* used for file check	*/


	/* debug message */

	ENTER("parse");

	/* get config file information */

	if(lstat(conffile,&stb))
	{
		printf("can't stat '%s'\n",conffile);
		return 0;
	}

	/* check that config file is a regular file */

	if(!S_ISREG(stb.st_mode))
	{
		printf("'%s': not a regular file\n",conffile);
		return 0;
	}

	/* check that config file is owned by root:root */

	if(stb.st_uid||stb.st_gid)
	{
		printf("'%s' must be owned by root:root\n",conffile);
		return 0;
	}

	/* check that config file can only be written to by owner */

	if(stb.st_mode&022)
	{
		printf("'%s' must not be group/world writeable\n",conffile);
		return 0;
	}

	/* try to open config file, handle errors */

	if((fp=fopen(conffile,"r"))==NULL)
	{
		printf("'%s': no such file\n",conffile);
		return 0;
	}

	/* for all lines of the config file */

	while(fgets(bfr,sizeof(bfr),fp))
	{
		/* get first token */

		item=strtok(bfr," \t\r\n");

		/* next line if line is empty */

		if(!item)continue;

		/* next line if line is comment */

		if(*item=='#')continue;

		/* for all commands if the current item matches the command */

		for(i=0;config[i].item;i++)if(!strcmp(item,config[i].item))
		{
			/* if command previously encountered signal error */

			if(config[i].found)
			{
				printf("command '%s' repeated.\n",item);
				fclose(fp);
				return 0;
			}

			/* set comand encountered flag */

			config[i].found=1;

			/* reset parameter counter */

			j=0;

			/* for all remaining tokens of the line */

			while((item=strtok(NULL," \t\r\n"))!=NULL)
			{
				/* done if comment or empty element */

				if(*item=='#'||*item==0)break;

				/* signal error if maximum parameters 
				   exceeded */

				if(j==config[i].max)
				{
					printf("command '%s' takes only %d " \
						"parameters.\n",config[i].item,
						config[i].max);
					fclose(fp);
					return 0;
				}

				/* store parameter and adjust parameter
				   count */

				switch(j++)
				{
					case 0:	config[i].p1=strdup(item);
						break;
					case 1:	config[i].p2=strdup(item);
						break;
					case 2:	config[i].p3=strdup(item);
						break;
				}
			}

			/* signal error if not minimum parameters given */

			if(j<config[i].min)
			{
				printf("command '%s' takes at least %d " \
					"parameters.\n",config[i].item,
					config[i].min);
				fclose(fp);
				return 0;
			}

			/* exit command search loop */

			break;
		}

		/* if item didn't match any command signal error */

		if(!config[i].item)
		{
			printf("illegal command '%s' encountered.\n",item);
			fclose(fp);
			return 0;
		}
	}

	/* close config file */

	fclose(fp);

	/* signal error if mandatory command not given */

	for(i=0;config[i].item;i++)if(!config[i].option)if(!config[i].found)
	{
		printf("missing command '%s'\n",config[i].item);
		return 0;
	}

	/* check mode command parameter */

	if(!strcmp(CFG_MODE(p1),"client"))mode=1;
	else if(!strcmp(CFG_MODE(p1),"server"))mode=0;
	else
	{
		printf("mode must be 'client' or 'server'\n");
		return 0;
	}

	/* check local command ip */

	if(inet_addr(CFG_LOCAL(p1))!=-1&&strlen(CFG_LOCAL(p1))<16)
		strcpy(local_ip,CFG_LOCAL(p1));
	else
	{
		printf("illegal local ip\n");
		return 0;
	}

	/* check remote command ip */

	if(inet_addr(CFG_REMOTE(p1))!=-1&&strlen(CFG_REMOTE(p1))<16)
		strcpy(remote_ip,CFG_REMOTE(p1));
	else
	{
		printf("illegal remote ip\n");
		return 0;
	}

	/* check server command ip and port if not device mode */

	if(CFG_SERVER(p1[0])!='/')
	{
		/* check server ip */

		if(inet_addr(CFG_SERVER(p1))!=-1&&strlen(CFG_SERVER(p1))<16)
			strcpy(server_ip,CFG_SERVER(p1));
		else
		{
			printf("illegal server ip\n");
			return 0;
		}

		/* check server port if given */

		if(CFG_SERVER(p2))
		{
			if(atoi(CFG_SERVER(p2))>0&&atoi(CFG_SERVER(p2))<65536)
				server_port=atoi(CFG_SERVER(p2));
			else
			{
				printf("illegal server port\n");
				return 0;
			}
		}
	}

	/* check server command device */

	else
	{
		if(strncmp(CFG_SERVER(p1),"/dev/",5))
		{
			printf("illegal server device (not in /dev)\n");
			return 0;
		}
		if(CFG_SERVER(p2))
		{
			printf("do not use port for server device\n");
			return 0;
		}
		if(!mode)
		{
			if(lstat(CFG_SERVER(p1),&stb))
			{
				printf("can't stat '%s'\n",CFG_SERVER(p1));
				return 0;
			}
			if(!S_ISCHR(stb.st_mode))
			{
				printf("'%s' is not a character special " \
					"device\n",CFG_SERVER(p1));
				return 0;
			}
			serialdev=CFG_SERVER(p1);
		}
		serialmode=1;
		normalmode=stb.st_mode;
	}

	/* check client command ip if not in device mode */

	if(CFG_CLIENT(p1[0])!='/')
	{
		/* check client ip */

		if(inet_addr(CFG_CLIENT(p1))!=-1&&strlen(CFG_CLIENT(p1))<16)
			strcpy(client_ip,CFG_CLIENT(p1));
		else
		{
			printf("illegal client ip\n");
			return 0;
		}

		/* check client port if given */

		if(CFG_CLIENT(p2))
		{
			if(atoi(CFG_CLIENT(p2))>0&&atoi(CFG_CLIENT(p2))<65536)
				client_port=atoi(CFG_CLIENT(p2));
			else
			{
				printf("illegal client port\n");
				return 0;
			}
		}
	}

	/* check that client command device file is in /dev */

	else
	{
		if(strncmp(CFG_CLIENT(p1),"/dev/",5))
		{
			printf("illegal client device (not in /dev)\n");
			return 0;
		}
		if(CFG_CLIENT(p2))
		{
			printf("do not use port for client device\n");
			return 0;
		}
		if(mode)
		{
			if(lstat(CFG_CLIENT(p1),&stb))
			{
				printf("can't stat '%s'\n",CFG_CLIENT(p1));
				return 0;
			}
			if(!S_ISCHR(stb.st_mode))
			{
				printf("'%s' is not a character special " \
					"device\n",CFG_CLIENT(p1));
				return 0;
			}
			serialdev=CFG_CLIENT(p1);
		}
		normalmode=stb.st_mode;
	}

	/* assert that server and client are in same (non)device mode */

	if((CFG_SERVER(p1[0])=='/'&&CFG_CLIENT(p1[0])!='/')||
		(CFG_SERVER(p1[0])!='/'&&CFG_CLIENT(p1[0])=='/'))
	{
		printf("use either ip or device for both client and server\n");
		return 0;
	}

	/* check keepalive time if given */

	if(CFG_KEEPALIVE(p1))
	{
		if(atoi(CFG_KEEPALIVE(p1))>0&&atoi(CFG_KEEPALIVE(p1))<65536)
			keepalive=atoi(CFG_KEEPALIVE(p1));
		else
		{
			printf("illegal keepalive time\n");
			return 0;
		}
	}

	/* set autoroute flag */

	if(CFG_AUTOROUTE(found))autoroute=1;

	/* get keyfile if given */

	if(CFG_KEYFILE(found))keyfile=CFG_KEYFILE(p1);

	/* check all routes, if given */

	for(i=CFG_FIRSTROUTE;i<=CFG_LASTROUTE;i++)if(CFG_ROUTE(i,found))
	{
		if(inet_addr(CFG_ROUTE(i,p1))==-1||strlen(CFG_ROUTE(i,p1))>15||
		   inet_addr(CFG_ROUTE(i,p3))==-1||strlen(CFG_ROUTE(i,p3))>15||
		   (inet_addr(CFG_ROUTE(i,p2))==-1&&
		    strcmp(CFG_ROUTE(i,p2),"255.255.255.255"))||
		   strlen(CFG_ROUTE(i,p2))>15)
		{
			printf("illegal destination, netmask or gateway " \
				"for '%s' command\n",config[i].item);
			return 0;
		}
		rtab[rmax][0]=inet_addr(CFG_ROUTE(i,p1));
		rtab[rmax][1]=inet_addr(CFG_ROUTE(i,p2));
		rtab[rmax][2]=inet_addr(CFG_ROUTE(i,p3));
		rmax++;
	}

	/* use optional pid file */

	if(CFG_PIDFILE(found))pidfile=CFG_PIDFILE(p1);

	/* check keyttl time if given */

	if(CFG_KEYTTL(found))
	{
		if(!strcmp(CFG_KEYTTL(p1),"0")||(atoi(CFG_KEYTTL(p1))>0&&
			atoi(CFG_KEYTTL(p1))<65536))
			keyttl=atoi(CFG_KEYTTL(p1))*60;
		else
		{
			printf("illegal keyttl time\n");
			return 0;
		}
	}

	/* use optional random device file */

	if(CFG_RANDOMDEV(found))randomdev=CFG_RANDOMDEV(p1);

	/* check mtu if given */

	if(CFG_MTU(found))
	{
		if(atoi(CFG_MTU(p1))>0&&atoi(CFG_MTU(p1))<65536)
			mtu=atoi(CFG_MTU(p1));
		else
		{
			printf("illegal mtu value\n");
			return 0;
		}
	}

	/* check serial line speed if given */

	if(CFG_SPEED(found))
	{
		j=atoi(CFG_SPEED(p1));
		for(i=0;speeddata[i].speednum;i++)if(j==speeddata[i].speednum)
			break;
		if(speeddata[i].speednum)speed=speeddata[i].speedval;
		else
		{
			printf("illegal speed value\n");
			return 0;
		}
	}

	/* check retry if given */

	if(CFG_RETRY(found))
	{
		if(atoi(CFG_RETRY(p1))>0&&atoi(CFG_RETRY(p1))<65536)
			retry=atoi(CFG_RETRY(p1));
		else
		{
			printf("illegal retry value\n");
			return 0;
		}
	}

	/* use optional modem chat file */

	if(CFG_MODEMCHAT(found))modemchat=CFG_MODEMCHAT(p1);

	/* check modem init chat file if any */

	if(CFG_MODEMCHAT(found))
	{
		/* get file information */

		if(lstat(CFG_MODEMCHAT(p1),&stb))
		{
			printf("can't stat '%s'\n",CFG_MODEMCHAT(p1));
			return 0;
		}

		/* check that file is a regular file */

		if(!S_ISREG(stb.st_mode))
		{
			printf("'%s': not a regular file\n",CFG_MODEMCHAT(p1));
			return 0;
		}

		/* check that file is owned by root:root */

		if(stb.st_uid||stb.st_gid)
		{
			printf("'%s' must be owned by root:root\n",
				CFG_MODEMCHAT(p1));
			return 0;
		}

		/* check that file can only be written by owner */

		if(stb.st_mode&022)
		{
			printf("'%s' must not be group/world writeable\n",
				CFG_MODEMCHAT(p1));
			return 0;
		}
	}

	/* set CLOCAL if requested */

	if(CFG_LOCALLINE(found))localline=1;

	/* do not use rts/cts if requested */

	if(CFG_NORTSCTS(found))rtscts=0;

	/* check leaky bucket count if given */

	if(CFG_NOANSWER(found))
	{
		if(atoi(CFG_NOANSWER(p1))>0&&atoi(CFG_NOANSWER(p1))<65536)
			noresponse=atoi(CFG_NOANSWER(p1))+1;
		else
		{
			printf("illegal noanswer value\n");
			return 0;
		}
	}

	/* do not use header compression if requested */

	if(CFG_NOCSLIP(found))cslip=0;

	/* check keysize if given */

	if(CFG_KEYSIZE(found))
	{
		if(atoi(CFG_KEYSIZE(p1))>=0&&
			atoi(CFG_KEYSIZE(p1))<=BLOWFISH_MAXKEY)
			keysize=atoi(CFG_KEYSIZE(p1));
		else
		{
			printf("illegal keysize value\n");
			return 0;
		}
	}

#ifdef COMPRESS

	/* set CLOCAL if requested */

	if(CFG_NOCOMPRESS(found))nocompress=1;

	/* check threshold if given */

	if(CFG_THRESHOLD(found))
	{
		if(atoi(CFG_THRESHOLD(p1))>0&&atoi(CFG_THRESHOLD(p1))<MAXBLOCK)
			threshold=atoi(CFG_THRESHOLD(p1));
		else
		{
			printf("illegal threshold value\n");
			return 0;
		}
	}

#endif

	/* set CLOCAL if requested */

	if(CFG_XFILTER(found))xfilter=1;

	/* get key file information */

	if(lstat(keyfile,&stb))
	{
		printf("can't stat '%s'\n",keyfile);
		return 0;
	}

	/* check that key file is a regular file */

	if(!S_ISREG(stb.st_mode))
	{
		printf("'%s': not a regular file\n",keyfile);
		return 0;
	}

	/* check that key file is owned by root:root */

	if(stb.st_uid||stb.st_gid)
	{
		printf("'%s' must be owned by root:root\n",keyfile);
		return 0;
	}

	/* check that key file can only be read by owner */

	if(stb.st_mode&0377)
	{
		printf("'%s' must have permission 400\n",keyfile);
		return 0;
	}

	/* check that key file size is correct */

	if(stb.st_size!=BLOWFISH_MAXKEY)
	{
		printf("'%s' has wrong size\n",keyfile);
		return 0;
	}

	/* sanity settings */

	if(!serialmode)xfilter=0;
	if(!keysize)keyttl=0;

	/* debug message */

	LEAVE("parse");

	/* signal success */

	return 1;
}

/*
 * main
 *
 * input:  argc - the argument count
 *	   argv - argument vector pointer
 *
 * return: 1 in case of error, else
 *	   never returns
 *
 * This procedure is the main procedure of the
 * virtual provate network daemon (vpnd). It
 * takes care of command line processing,
 * configuration file parsing, daemonizing
 * as well as pid file creation and start 
 * of the main loop.
 */

int main(int argc,char *argv[])
{
	FILE *fp;	/* pid file access */
	int daemon;	/* detach flag     */
	int i;		/* counter/index   */


	/* debug message */

	ENTER("main");

	/* reset pid file handle */

	fp=NULL;

	/* default is to detach */

	daemon=1;

	/* if new master key file is requested */

	if(argc==2||argc==3)if(!strcmp(argv[1],"-m"))
	{
		/* if pathname is given, use it instead of default */

		if(argc==3)keyfile=argv[2];

		/* create master key file and exit */

		return genmaster();
	}

	/* process command line parameters */

	if(argc)while(--argc)
	{
		argv++;
		if(!strcmp(*argv,"-n"))daemon=0;
		else if(!strcmp(*argv,"-t"))verbose=1;
		else if(!strcmp(*argv,"-h"))goto usage;
		else if(!strcmp(*argv,"-f"))
		{
			if(!--argc)goto usage;
			conffile=*(++argv);
		}
#ifdef DEBUG
		else if(!strcmp(*argv,"-d"))
		{
			if(!--argc)goto usage;
			debug=atoi(*(++argv));
		}
#endif
		else goto usage;
			
	}
	/* parse config file, terminate in case of error */

	if(!parse())return 1;

	/* in modem chat test mode open serial line,
	   call chatter, then close serial line, done */

	if(verbose)
	{
		/* assert that serial line mode is used */

		if(!serialmode)
		{
			printf("-t only valid for modem init chat test\n");
			return 1;
		}

		/* set up signal handlers (default is ignore) */

		for(i=1;i<_NSIG;i++)setsig(i,SIG_IGN);
		setsig(SIGINT,chatdie);
		setsig(SIGTERM,chatdie);

		/* try to get serial line */

		if(!getserial())
		{
			printf("can't access %s\n",serialdev);
			return 1;
		}

		/* print info */

		printf("starting modem init chat in test mode\n");

		/* modem init chat in test mode */

		i=chatter();

		/* print chat test result */

		printf("modem init chat test returned %d\n",i);

		/* done */

		return i;
	}

	/* access pidfile, terminate in case of errors */

	if(pidfile)if((fp=fopen(pidfile,"w"))==NULL)
	{
		printf("can't access '%s'\n",pidfile);
		return 1;
	}

	/* become daemon if not disabled */

	if(daemon)
	{
		switch(fork())
		{
			case 0:  break;
			case -1: printf("fork failed\n");
				 if(fp)fclose(fp);
				 return 1;
			default: if(fp)fclose(fp);
				 return 0;
		}
		switch(fork())
		{
			case 0:  break;
			case -1: printf("fork failed\n");
				 if(fp)fclose(fp);
				 return 1;
			default: if(fp)fclose(fp);
				 return 0;
		}
	}

	/* write pid to pid file if file is open, then close file */

	if(fp)
	{
		fprintf(fp,"%u",getpid());
		fclose(fp);
	}

	/* detach from tty and then close possibly open file handles
	   (wild guess but usually hits all files left open */

	if(daemon)
	{
		i=1;
		ioctl(0,TIOCNOTTY,&i);
		for(i=0;i<256;i++)close(i);
	}

	/* open syslog */

	openlog("vpnd",LOG_PID|(daemon?0:LOG_PERROR),LOG_DAEMON);

	/* set up signal handlers (defualt is ignore) */

	for(i=1;i<_NSIG;i++)setsig(i,SIG_IGN);
	setsig(SIGINT,sigdie);
	setsig(SIGTERM,sigdie);

	/* funny but if you don't do this waitpid() fails... */

	blocksig(SIGCHLD);

	/* do main job (never returns) */

	sequencer();

	/* debug message */

	LEAVE("main");

	/* just to make sure */

	goto err;

	/* usage info */

usage:	printf("usage: vpnd -h\n");
	printf("usage: vpnd -m [<master-key-file>]\n");
#ifdef DEBUG
	printf("       vpnd [-d number] [-n|-t] [-f configfile]\n");
#else
	printf("       vpnd [-n|-t] [-f configfile]\n");
#endif
	printf("    -h prints this info\n");
	printf("    -m creates master key file (default is /etc/vpnd.key),\n");
	printf("       note that the mile must not yet exist\n");
#ifdef DEBUG
	printf("    -d print debug messages up to level <number>");
#endif
	printf("    -n do not become daemon\n");
	printf("    -t do modem init chat test\n");
	printf("    -f use configfile instead of /etc/vpnd.conf\n");
err:	return 1;
}
