/*
   CIPE - encrypted IP over UDP tunneling

   ciped.c - the user mode driver

   Copyright 1996 Olaf Titz <olaf@bigred.inka.de>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version
   2 of the License, or (at your option) any later version.
*/
/* $Id: ciped.c,v 1.49.2.1 2001/02/11 21:42:39 olaf Exp $ */

#define CTLDIR "/etc/cipe"

#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if_arp.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <values.h>
#include <linux/if_ether.h>
#include "ciped.h"
#include "ioctl.h"
#include "cipelib.h"

#ifndef LOGFAC
#define LOGFAC LOG_DAEMON
#endif

static const char daemon_version[]=VERSION;

void usage(const char *p)
{
    fprintf(stderr, "usage: %s [ -i | -s fd ] [ -o file ] [option...]\n", p);
    exit(1);
}

/* FIXME: places marked *#* have byteorder conversion missing */
/* This is corrected in version 3 protocol */

/* Statistics */
struct kstat {
    int rreq, req, ind, indb, ack, ackb, bogus;
} ks = { 0, 0, 0, 0, 0, 0, 0 };

char device[16] = "";
int devnum = -1;
int inetd = 0;
int usesock = -1;

#ifdef VER_CRC32
typedef unsigned long crc;
#define CRCFORM "%08lX"
#else
typedef unsigned short crc;
#define CRCFORM "%04X"
#endif

/*** Locked memory structure, contains everything to do with keys */
#define SECPOOL 256
struct lockmem {
    char pool[SECPOOL];                         /* for strings */
    int poolptr;
    struct siocsifcipkey sio;                   /* setkey ioctl */
    unsigned char kxbuf[KEYXCHGBLKMIN+2];       /* KX comm buff */
    crc scrc;
    unsigned char skey[userKeySize];            /* holds current KX key */
} *LM;

char *secstrdup(const char *p)
{
    char *q=LM->pool+LM->poolptr;
    int n=strlen(p)+1;
    if (LM->poolptr+n>SECPOOL)
        return NULL;
    strcpy(q, p);
    LM->poolptr+=n;
    return q;
}

#ifdef HAVE_MLOCK
void xmlock(void *addr, size_t len)
{
    if (mlock(addr, len)<0)
        Log(LOG_WARNING, "mlock: %m"); /* not fatal */
}
void xmunlock(void *addr, size_t len)
{
    if (munlock(addr, len)<0)
        Log(LOG_WARNING, "munlock: %m"); /* not fatal */
}
#else
#define xmlock(a,l)
#define xmunlock(a,l)
#endif


/*** Option parser ***/

/* Set an option */
int setopt(const char *n, char *v, int r)
{
    struct options *o=opts;
    static const char * const protos[]={NULL, "udp", "tcp"};

    while (o->oname) {
	if (!strcmp(o->oname, n)) {
	    if ((!v) && (o->otyp!=Tbool)) {
		fprintf(stderr, "missing value for %s\n", n); return -1;
	    }
	    switch(o->otyp) {
	    case Tbool:
		o->ov.ovint=1;
		break;
	    case Tint:
		o->ov.ovint=atoi(v);
		break;
	    case Tstr:
		if (!(o->ov.ovstr=strdup(v))) {
		    fprintf(stderr, "setopt: out of memory\n"); return -1;
		}
		break;
	    case Taddr:
	    case Tuaddr:
	    case Ttaddr:
		if (!(o->ov.ovsaddr=malloc(sizeof(*o->ov.ovsaddr)))) {
		    fprintf(stderr, "setopt: out of memory\n"); return -1;
		}
		if (getaddr(v, o->ov.ovsaddr, protos[o->otyp-Taddr])<0) {
		    return -1;
		}
		break;
            case Tsecret:
                if (r) {
                    fprintf(stderr, "Refused to set key from command line!\n");
                    return -1;
                }
		if (!(o->ov.ovstr=secstrdup(v))) {
		    fprintf(stderr, "setopt: out of secret-pool memory\n");
                    return -1;
		}
		break;
	    default:
		fprintf(stderr, "internal error: %d\n", __LINE__); return -1;
	    }
	    return 0;
	}
	++o;
    }
    fprintf(stderr, "unknown option: %s\n", n);
    return -1;
}

/* Set options from command line */
void setopt_cmdline(int argc, char *argv[])
{
    int i;
    char *q;
    for (i=0; i<argc; ++i) {
	if ((q=strchr(argv[i], '=')))
	    *q++='\0';
	(void) setopt(argv[i], q, 1);
    }
}

/* Set options from file, report file not found if (v) */
int setopt_file(const char *n, int v)
{
    FILE *f;
    char b[128];
    char *p, *q;

    if (secchk(n, 0077, 0022, v))
        return -1;
    if (!(f=fopen(n, "r"))) {
	if (v)
	    perror("setopt_file: open");
	return -1;
    }
    xmlock(b, sizeof(b));
    /* FIXME: perhaps fgets could allocate unlocked memory */
    while (fgets(b, sizeof(b), f)) {
        for (p=b; isspace(*p); ++p);
        if ((!*p) || strchr("#;:!", *p))
            continue;
        parseopt(b, &p, &q);
        (void) setopt(p, q, 0);
        memset(b, 0, sizeof(b));
    }
    xmunlock(b, sizeof(b));
    fclose(f);
    return 0;
}

/* Make a "name=value" string of option */
char *optstr(struct options *o)
{
    static char buf[1024];
    switch(o->otyp) {
    case Tbool:
        snprintf(buf, sizeof(buf), "%s=%s", o->oname,
                 o->ov.ovint?"yes":"no");
        break;
    case Tint:
        snprintf(buf, sizeof(buf), "%s=%d", o->oname, o->ov.ovint);
        break;
    case Tstr:
        snprintf(buf, sizeof(buf), "%s=%s", o->oname,
                 o->ov.ovstr?o->ov.ovstr:"(none)");
        break;
    case Tsecret:
        snprintf(buf, sizeof(buf), "%s=%s", o->oname,
                 o->ov.ovstr?"(secret)":"(none)");
        break;
    case Taddr:
        if (o->ov.ovsaddr)
            snprintf(buf, sizeof(buf), "%s=%s", o->oname,
                     inet_ntoa(o->ov.ovsaddr->sin_addr));
        else
            snprintf(buf, sizeof(buf), "%s=", o->oname);
        break;
    case Tuaddr:
    case Ttaddr:
        if (o->ov.ovsaddr)
            snprintf(buf, sizeof(buf), "%s=%s:%d", o->oname,
                     inet_ntoa(o->ov.ovsaddr->sin_addr),
                     ntohs(o->ov.ovsaddr->sin_port));
        else
            snprintf(buf, sizeof(buf), "%s=", o->oname);
        break;
    default: fprintf(stderr, "internal error: %d\n", __LINE__);
    }
    return buf;
}

/* Print out all options */
void dumpopt(void)
{
    struct options *o;
    for (o=opts; o->oname; ++o)
        printf("%s\n", optstr(o));
}

/*** Logging ***/

/* missing option */
void argmiss(const char *s)
{
    Log(LOG_ERR, "missing argument: %s", s);
}

/* print if debugging */
#define dprintf(f)    if(OI(debug)){printf f;}

/*** Initialize, open and attach the CIPE device. ***/

int opendev(void)
{
    int f, i, iffl;
#ifndef NO_DYNDEV
    struct siocsifcipall d;
#endif
    struct siocsifcippar p;
    struct siocsifcipatt a;
    struct ifreq r;

#define amiss(s) do{ argmiss(s); goto error; }while(0)
#define err(s)   do{ Log(LOG_ERR, s); goto error; }while(0)

    /* Initialize, bind and connect the socket */
    if (inetd) {
        f=0;
    } else if (usesock>=0) {
	struct sockaddr_in sa;
	memset(&sa, 0, sizeof(sa));
	sa.sin_family=AF_UNSPEC;
	f=usesock;
	if (connect(f, (struct sockaddr *)&sa, sizeof(sa))<0) {
	    err("opendev: disconnect");
	}
    } else {
        if ((f=socket(AF_INET, SOCK_DGRAM, 0))<0) {
            Log(LOG_ERR, "opendev: socket: %m");
            return -1;
        }
    }
    i=65536; /* we need much to receive fast */
    if (setsockopt(f, SOL_SOCKET, SO_RCVBUF, &i, sizeof(i)))
	/* not fatal */
	Log(LOG_NOTICE, "opendev: setsockopt: %m");
    if (!OA(me)) {
	if (!(OA(me)=malloc(sizeof(*OA(me)))))
	    err("opendev: malloc");
	OA(me)->sin_family=AF_INET;
	OAaddr(me).s_addr=INADDR_ANY;
	OAport(me)=0;
    }
    if (!inetd) {
	if (usesock<0) {
	    if (bind(f, (struct sockaddr *)OA(me), sizeof(*OA(me)))<0)
		err("opendev: bind");
	}
        if (OA(peer)) {
            if (connect(f, (struct sockaddr *)OA(peer), sizeof(*OA(peer)))<0)
                err("opendev: bind");
        } else
            amiss("peer");
    }
    i=sizeof(*OA(me));
    if (getsockname(f, (struct sockaddr *)OA(me), &i)<0)
	/* not fatal */
	Log(LOG_NOTICE, "opendev: getsockname: %m");

#ifdef NO_DYNDEV
    if (!OS(device))
        amiss("device");
    if (strlen(OS(device)) > 15)
        err("opendev: device name too long.");
    strcpy(device, OS(device));
#else
    /* Allocate the device name */
    if (OS(device)) {
        char *x=OS(device)+strlen(OS(device))-1;
        while (x>=OS(device) && isdigit(*x))
            --x;
        d.num=atoi(++x);
    } else {
        d.num=-1;
    }
    if (ioctl_alloc(f, DEVNAME "0", &d)<0)
        err("opendev: alloc");
    strcpy(device, d.name);
    devnum=d.num;
    dprintf(("Using %s index %d\n", device, devnum));
#endif

    /* Set the CIPE operation parameters */
    memset(&p, 0, sizeof(p));
    if (OA(socks)) {
	p.sockshost=OAaddr(socks).s_addr;
	p.socksport=OAport(socks);
    }
    p.tmo_keyxchg=OI(tokxc);
    p.tmo_keylife=OI(tokey);
    p.flags=CIPF_MAY_STKEY;
    if (OI(nokey))
        p.flags|=CIPF_MAY_CLEAR;
    if (OI(dynip))
        p.flags|=CIPF_MAY_DYNIP;
    if (OI(checksum))
	p.flags|=CIPF_DO_CSUM;
    p.cttl=OI(cttl);
    if (ioctl_setpar(f, device, &p)<0)
	err("opendev: setpar");

    /* Set the key */
    if (OS(key)) {
	unsigned char *c=OS(key);
	unsigned char *x=(unsigned char *)&LM->sio.thekey;
	LM->sio.which=KEY_STATIC;
#ifdef BUG_COMPATIBLE
	/* BROKEN attempt at a primitive hex decoder */
	i=0;
	while(*c) {
	    x[i]=((*c)<<4)&0xF0;
	    *c++='\0'; /* clear after use */
	    if (!*c) break;
	    x[i++]|=(*c)&0x0F;
	    *c++='\0';
	    if (i>=userKeySize) break;
	}
#else
	gethex(c, x, userKeySize);
	memset(c, 0, strlen(c)); /* clear after use */
#endif
        LM->sio.keylen=userKeySize;
	if (ioctl_setkey(f, device, &LM->sio)<0)
	    err("opendev: setkey");
    } else {
	if (!OI(nokey))
	    amiss("key");
    }

    /* Perform attach */
    a.fd=f;
    if (ioctl_attach(f, device, &a)<0)
	err("opendev: attach");

    if (OI(ifconfig))
	return f;

    /* Perform ifconfig */
    strcpy(r.ifr_name, device);
    if (ioctl(f, SIOCGIFFLAGS, &r)<0)
	err("opendev: SIOCGIFFLAGS");
    iffl=r.ifr_flags;
    if (OA(ipaddr)) {
	memcpy(&r.ifr_addr, OA(ipaddr), sizeof(*OA(ipaddr)));
	if (ioctl(f, SIOCSIFADDR, &r)<0)
	    err("opendev: SIOCSIFADDR");
    } else
	amiss("ipaddr");
    if (OA(ptpaddr)) {
	memcpy(&r.ifr_dstaddr, OA(ptpaddr), sizeof(*OA(ptpaddr)));
	if (ioctl(f, SIOCSIFDSTADDR, &r)<0)
	    err("opendev: SIOCSIFDSTADDR");
	iffl|=IFF_POINTOPOINT;
    }
    if (OA(mask)) {
	memcpy(&r.ifr_netmask, OA(mask), sizeof(*OA(mask)));
	if (ioctl(f, SIOCSIFNETMASK, &r)<0)
	    err("opendev: SIOCSIFNETMASK");
    }
    if (OA(bcast)) {
	memcpy(&r.ifr_broadaddr, OA(bcast), sizeof(*OA(bcast)));
	if (ioctl(f, SIOCSIFBRDADDR, &r)<0)
	    err("opendev: SIOCSIFBRDADDR");
    }
    if (OI(mtu)) {
	r.ifr_mtu=OI(mtu);
	if (ioctl(f, SIOCSIFMTU, &r)<0)
	    err("opendev: SIOCSIFMTU");
    }
    if (OI(metric)) {
	r.ifr_metric=OI(metric);
	if (ioctl(f, SIOCSIFMETRIC, &r)<0)
	    err("opendev: SIOCSIFMETRIC");
    }
    if (OS(hwaddr)) {
	struct sockaddr sa;
	sa.sa_family=ARPHRD_ETHER;
	gethex(OS(hwaddr), (unsigned char *)&sa.sa_data, ETH_ALEN);
	memcpy(&r.ifr_hwaddr, &sa, sizeof(sa));
	if (ioctl(f, SIOCSIFHWADDR, &r)<0)
	    err("opendev: SIOCSIFHWADDR");
    }
    iffl|=IFF_UP|IFF_RUNNING;
    r.ifr_flags=iffl;
    if (ioctl(f, SIOCSIFFLAGS, &r)<0)
	err("opendev: SIOCSIFFLAGS");

    return f;

 error:
    close(f);
    return -1;

#undef amiss
#undef err
}

/* Close the device. */

void closedev(int f)
{
    struct ifreq r;
    strcpy(r.ifr_name, device);
    if (ioctl(f, SIOCGIFFLAGS, &r)<0)
	Log(LOG_ERR, "closedev: SIOCGIFFLAGS: %m");
    r.ifr_flags&=~(IFF_UP|IFF_RUNNING);
    if (ioctl(f, SIOCSIFFLAGS, &r)<0)
	Log(LOG_ERR, "closedev: SIOCSIFFLAGS: %m");
    close(f);
#ifndef NO_DYNDEV
    if (devnum>0) {
        struct siocsifcipall x;
        if ((f=socket(AF_INET, SOCK_DGRAM, 0))<0) {
            Log(LOG_ERR, "closedev: socket: %m");
            return;
        }
        x.num=devnum;
        if (ioctl_unalloc(f, device, &x)<0)
            Log(LOG_ERR, "closedev: unalloc: %m");
        close(f);
    }
#endif
}

/* Fetch the device statistics. */

char *getstats(void)
{
    static char b[256];
    char *p, *q;
    FILE *f=fopen("/proc/net/dev", "r");
    if (!f)
        return NULL;
    while (fgets(b, sizeof(b), f)) {
        for (p=b; *p==' '; ++p);
        for (q=p; *q && *q!=':'; ++q);
        if (!*q)
            continue;
        *q++='\0';
        if (strcmp(p, device))
            continue;
        fclose(f);
        return(q);
    }
    fclose(f);
    return NULL;
}


/*** Open a SOCKS5 connection ***/

int opensocks(void) {
    struct sockaddr_in a;
    int fd=socks5_open(OA(socks));
    if (fd>=0) {
	/* 3rd par of socks5_cmd is in: my address, out: relayer */
	a=*OA(me);
	fd=socks5_cmd(fd, S5_UDP, &a);
	/* Swap peer and socks addresses.
	   Use the SOCKS relayer as "peer" and the real peer as "socks"
	   parameter. See output.c why. */
	*OA(socks)=*OA(peer);
	*OA(peer)=a;
    }
    return fd;
}

/* Check whether the SOCKS5 server has closed the connection */

const char * const eofsocks_str[] = {
    "no error", "exception", "read error", "EOF"
};

int eofsocks(int fd) {
    fd_set ifd, efd;
    struct timeval t={0,0};
    FD_ZERO(&ifd);
    FD_ZERO(&efd);
    FD_SET(fd, &ifd);
    FD_SET(fd, &efd);
    if (select(fd+1, &ifd, NULL, &efd, &t)>0) {
	if (FD_ISSET(fd, &efd)) {
	    return 1;
	}
	if (FD_ISSET(fd, &ifd)) {
	    int e;
	    char c;
	    if ((e=read(fd, &c, 1))<=0) {
		return e<0 ? 2 : 3;
	    }
	}
    }
    return 0;
}

/*** Signal handler. See also cipelib.h ***/

void sighandnot(int s)
{
    /* Dummy */
}

void sighandexit(int s)
{
    /* exit parent */
    exit(0);
}

/*** Control message handling ***/

time_t pingtime=MAXINT, pongtime=MAXINT;

void dprintct(const char *h, char c)
{
    switch(c) {
    case CT_DUMMY: 	dprintf(("%s CT_DUMMY\n", h));  	break;
    case CT_DEBUG: 	dprintf(("%s CT_DEBUG\n", h));  	break;
    case CT_PING:  	dprintf(("%s CT_PING\n", h));  	 	break;
    case CT_PONG:  	dprintf(("%s CT_PONG\n", h));   	break;
    case CT_KILL:  	dprintf(("%s CT_KILL\n",h));   	 	break;
    case CT_CONFREQ:  	dprintf(("%s CT_CONFREQ\n",h));    	break;
    case CT_CONF:  	dprintf(("%s CT_CONF\n",h));    	break;
    default:       	dprintf(("%s %d\n", h, c));
    }
}

/* send a control */
int sendctl(int f, const char *msg, int len, int v, int flags)
{
    int e;
    dprintct("sending", msg[0]);
    if (OI(nokey))
        return -1; /* unencrypted cant handle controls */
    if ((e=send(f, msg, len, flags))<0) {
	if (v)
	    Log(LOG_ERR, "sendctl: %m");
    }
    return e;
}

/* Configuration info structure */
struct confinfo {
    char cmd;
    char fill[3];
    char magic[4];
    char protocol, crypto, swmaj, swmin;
    char keyright;
};

struct confinfo myconfinfo = {
    CT_CONF, "\0",
    "CIPE", ProtocolVersion, CRNAMEC,
    VERSION_MAJ, VERSION_MIN,
#ifdef BUG_COMPATIBLE
    0
#else
    1
#endif
};

/* handle control message */
void ctrl(int f, char *msg, int len)
{
    char buf[64];
    struct confinfo *ci;

    if (OI(nokey))
        return; /* unencrypted cant handle controls */
    dprintct("received", msg[0]);
    switch (msg[0]) {
    case CT_DEBUG:
        /* Log it */
        Log(LOG_NOTICE, "peer: %s", msg+1);
	/* fall thru */
    case CT_DUMMY:
        break;
    case CT_PING:
        msg[0]=CT_PONG;
        sendctl(f, msg, len, 1, 0);
        break;
    case CT_PONG:
        pongtime=MAXINT;
        break;
    case CT_KILL:
        Log(LOG_NOTICE, "Shutting down at peer request %s", msg+1);
        gotsig=SIGHUP;
        break;
    case CT_CONFREQ:
#ifndef DONT_SEND_CONF
	myconfinfo.cmd=CT_CONF;
	sendctl(f, (char *)&myconfinfo, sizeof(myconfinfo), 1, MSG_OOB);
#endif
	/* fall thru */
    case CT_CONF:
	ci=(struct confinfo *)msg;
	if (memcmp(&ci->magic, "CIPE", 4))
	    break;
	snprintf(buf, sizeof(buf),
		 "proto=%d, crypto=%c, version=%d.%d, %s key parser",
		 ci->protocol, ci->crypto, ci->swmaj, ci->swmin,
		 ci->keyright?"correct":"broken");
	Log(LOG_INFO, "peer configuration info: %s", buf);
#define check(v,s) \
	if (ci->v!=myconfinfo.v) \
	    Log(LOG_ERR, s " mismatch to peer, this will not work!\n");
	check(protocol, "Protocol version");
	check(crypto, "Cipher algorithm");
	check(keyright, "Key parser");
	break;
#undef check
    default:
        Log(LOG_NOTICE, "unknown control %d", msg[0]);
    }
}

/*** The key exchange procedure ***/

#define REM 	256	/* How many keys to remember */

/* Cache last valid key CRCs. */

static int dupcheck(crc c)
{
    static crc ca[REM];
    static int caf=0, cal=0;
    int i;

    for (i=cal; i<caf; ++i)
	if (ca[i%REM]==c)
	    return 1;
    if (caf-cal>=REM)
	if (++cal>REM) {
	    caf-=REM;
	    cal-=REM;
	}
    ca[caf%REM]=c;
    ++caf;
    return 0;
}

int errcnt;

int kxchg(int f, int r)
{
    int x=0, e=0;
    crc c;

    #define kx_typ       (LM->kxbuf[0])
    #define kx_nkind_key (LM->kxbuf+1)
    #define kx_ts        ntohl(*(time_t *)(LM->kxbuf+KEYXCHGTSPOS))
#ifdef VER_CRC32
    #define get_kx_nkind_crc(x) memcpy((x), LM->kxbuf+1+userKeySize, 4)
    #define put_kx_nkind_crc(x) memcpy(LM->kxbuf+1+userKeySize, (x), 4)
    #define get_kx_nkack_crc(x) memcpy((x), LM->kxbuf+1, 4)
    #define put_kx_nkack_crc(x) memcpy(LM->kxbuf+1, (x), 4)
#else
    #define get_kx_nkind_crc(x) memcpy((x), LM->kxbuf+1+userKeySize, 2)
    #define put_kx_nkind_crc(x) memcpy(LM->kxbuf+1+userKeySize, (x), 2)
    #define get_kx_nkack_crc(x) memcpy((x), LM->kxbuf+1, 2)
    #define put_kx_nkack_crc(x) memcpy(LM->kxbuf+1, (x), 2)
#endif

    if ((x=recv(f, LM->kxbuf, KEYXCHGBLKMIN, 0))<=0) {
	if (errno==EAGAIN || errno==EINTR)
	     /* This is normal, would be even after select */
	    return 0;
	Log(LOG_INFO, "kxchg: recv: %m");
	if (errno==ENXIO)
	    /* Device was closed */
	    return -1;
	/* allow for some amount of ECONNREFUSED etc. */
	return (OI(maxerr)<0 ? 0 : --errcnt);
    }
    errcnt=OI(maxerr);
    if (LM->kxbuf[0]>=CT_DUMMY) {
        LM->kxbuf[55]='\0'; /* max length of message */
        ctrl(f, LM->kxbuf, x);
        return 0;
    }
    if (OI(tokxts) && (abs(time(0)-kx_ts)>OI(tokxts))) {
        Log(LOG_NOTICE, "KX: timestamp error");
        return 0;
    }
    switch(kx_typ) {
    case NK_RREQ:
	kx_typ=NK_REQ;
	dprintf(("KX: [NK_RREQ] sending NK_REQ\n"));
	x=send(f, &LM->kxbuf, sizeof(LM->kxbuf), 0); e=errno;
	++ks.rreq;
	break;
    case NK_REQ:
	kx_typ=NK_IND;
	if (read(r, &LM->skey, userKeySize)!=userKeySize) {
	    Log(LOG_ERR, "kxchg: read(r): %m");
	    return -1;
	}
	memcpy(kx_nkind_key, LM->skey, userKeySize);
#ifdef VER_CRC32
	LM->scrc=htonl(crc32(LM->skey, userKeySize));
#else
	LM->scrc=block_crc(LM->skey, userKeySize)); /*#*/
#endif
        put_kx_nkind_crc(&LM->scrc);
	dprintf(("KX: [NK_REQ] sending NK_IND " CRCFORM "\n", LM->scrc));
	x=send(f, &LM->kxbuf, sizeof(LM->kxbuf), 0); e=errno;
        LM->sio.which=KEY_INVAL+KEY_SEND;
        if (ioctl_setkey(f, device, &LM->sio)<0)
            Log(LOG_ERR, "setkey: %m");
	++ks.req;
	break;
    case NK_IND:
        get_kx_nkind_crc(&c);
	if (
#ifdef VER_CRC32
	    crc32(kx_nkind_key, userKeySize)==ntohl(c)
#else
	    block_crc(kx_nkind_key, userKeySize)==c /*#*/
#endif
	    ) {
	    if (dupcheck(c)) {
		dprintf(("KX: [NK_IND] duplicate\n"));
		++ks.indb;
	    } else {
		LM->sio.which=KEY_RECV;
		memcpy(&LM->sio.thekey, kx_nkind_key, userKeySize);
                LM->sio.keylen=userKeySize;
		if (ioctl_setkey(f, device, &LM->sio)<0)
		    perror("setkey");
		else {
		    kx_typ=NK_ACK;
                    put_kx_nkack_crc(&c); /* XX */
		    dprintf(("KX: [NK_IND] sending NK_ACK " CRCFORM "\n",
			     c)); /*#*/
		    x=send(f, &LM->kxbuf, sizeof(LM->kxbuf), 0); e=errno;
		}
		++ks.ind;
	    }
	} else {
	    dprintf(("KX: [NK_IND] invalid\n"));
	    ++ks.indb;
	}
	break;
    case NK_ACK:
        get_kx_nkack_crc(&c);
	if (LM->scrc==c) { /*#*/
	    if (dupcheck(c)) {
		dprintf(("KX: [NK_ACK] duplicate\n"));
		++ks.ackb;
	    } else {
		LM->sio.which=KEY_SEND;
		memcpy(&LM->sio.thekey, &LM->skey, userKeySize);
                LM->sio.keylen=userKeySize;
		dprintf(("KX: [NK_ACK] got " CRCFORM "\n", LM->scrc));
		if (ioctl_setkey(f, device, &LM->sio)<0)
		    perror("setkey");
		++ks.ack;
		memset(&LM->skey, 0, sizeof(LM->skey));
	    }
	} else {
	    dprintf(("KX: [NK_ACK] unknown " CRCFORM "\n", c));
	    ++ks.ackb;
	}
	break;
    default:
	dprintf(("KX: invalid type %02X\n", kx_typ));
	++ks.bogus;
    }
    memset(LM->kxbuf, 0, sizeof(LM->kxbuf));
    if (x<0) {
	errno=e;
	Log(LOG_NOTICE, "kxchg: send: %m");
	if (e==ENXIO)
	    return -1;
	x=(OI(maxerr)<0 ? 0 : --errcnt);
    }
    return x;
}

/*** Call the ip-up, ip-down scripts ***/

void pspawn(char *s)
{
    int p, c;
    if (secchk(s, 0022, 0022, 0))
        return;
    p=getpid();
    if ((c=fork())<0) {
	Log(LOG_ERR, "pspawn: fork: %m");
	return;
    }
    if (c) {
	(void) wait(NULL); /* exactly one child is active */
    } else {
	/* call <interface> <my-addr> <daemon-pid> <local> <remote> <arg> */
	char bufa[32], bufb[32], bufc[32], bufd[32];
	char *na[]={s, device, bufa, bufb, bufc, bufd, OS(arg), NULL};
        struct options *o = opts;
	sprintf(bufa, "%s:%d", inet_ntoa(OAaddr(me)), ntohs(OAport(me)));
	sprintf(bufb, "%d", p);
	sprintf(bufc, "%s", inet_ntoa(OAaddr(ipaddr)));
	sprintf(bufd, "%s", inet_ntoa(OAaddr(ptpaddr)));
        /* put all non-secret options into environment */
        for (o=opts+1; o->oname; ++o)
            if (o->otyp!=Tsecret)
                putenv(strdup(optstr(o)));
        /* should closelog(); ? */
	close(0); close(1); close(2);
	(void) open("/dev/null", O_RDWR);
	(void) dup(0); (void) dup(0);
	execv(s, na);
	Log(LOG_WARNING, "pspawn: exec %s: %m", s);
	exit(1);
    }
}

/*** Main program ***/

int main(int argc, char *argv[])
{
    int i=1, ur, fd, sd=-1;
    time_t t;
    pid_t pa=0;
    char *st;
    char *pn=strrchr(argv[0], '/');
    if (pn)
	++pn;
    else
	pn=argv[0];

    if (getuid() || geteuid()) {
	fprintf(stderr, "This program must be run by root.\n");
	exit(1);
        /* This program is not setuid-safe. If run by local users with
           arbitrary options, it could easily be tricked into executing
           any code as root. */
    }
    if (!(LM=malloc(sizeof(struct lockmem)))) {
        perror("malloc");
        exit(1);
    }
    memset(LM, 0, sizeof(struct lockmem));
    xmlock(LM, sizeof(struct lockmem));

    /* Snarf options from: standard options file, user-supplied file and
       finally, command line */
    setopt_file(CTLDIR "/options", 0);
    while ((i=getopt(argc, argv, "is:S:o:"))!=EOF) {
        switch(i) {
        case 'i': inetd=1; break;
	case 's': usesock=atoi(optarg); break;
	case 'S': sd=atoi(optarg); break;
        case 'o': if (setopt_file(optarg, 1)<0) exit(1); break;
        default: usage(pn);
        }
    }
    setopt_cmdline(argc-optind, argv+optind);
    if (!OI(maxerr))
	OI(maxerr)=8; /* compatibility */

    for (i=getdtablesize()-1; i>2; --i)
	if (i!=usesock)
	    (void) close(i);

    if (OI(debug)) {
	printf("CIPE daemon vers %s (c) Olaf Titz 1996-2000\n",
	       daemon_version);
        logstderr=1;
	dumpopt();
    } else if (!inetd) {
	/* daemonize */
	gotsig=0;
	setsig(SIGUSR1, sighandexit);
	setsig(SIGCHLD, sighand);
	if ((i=fork())<0) {
	    perror("fork"); exit(1);
	}
	if (i) {
            /* wait until the child is ready - will send SIGUSR1 */
            while (1) {
                pause();
                if (gotsig==SIGCHLD) {
                    int s;
                    wait(&s);
                    exit(WIFEXITED(s)?WEXITSTATUS(s):255);
                }
            }
	    exit(0);
        }
        /* re-lock after fork */
        xmlock(LM, sizeof(struct lockmem));
        pa=getppid();
	openlog(pn, LOG_CONS|LOG_NDELAY|LOG_PID, LOGFAC);
	/* don't let the syslog socket get in the way of the standard fds */
	close(0); close(1); close(2);
	setsid();
	syslog(LOG_INFO, "CIPE daemon vers %s (c) Olaf Titz 1996-2000",
	       daemon_version);
    }

    setsig(SIGHUP, sighand);
    setsig(SIGINT, sighand);
    setsig(SIGTERM, sighand);
    setsig(SIGPIPE, sighand);
    setsig(SIGUSR1, sighand);
    errcnt=OI(maxerr);
    if ((ur=open("/dev/urandom", O_RDONLY))<0) {
	Log(LOG_ERR, "open(/dev/urandom): %m"); exit(1);
    }
    i=MAXINT;
    if (OA(socks)) {
        i=30; /* set timeout for control socket check */
	if (sd<0)
	    sd=opensocks();
	if (sd<0)
	    exit(1);
    }
    if ((fd=opendev())<0)
	exit(1);
    pspawn(OS(ipup) ? OS(ipup) : CTLDIR "/ip-up");

    /* Set timer for periodic checks: ping, ping-timeout */
    if (OI(ping)) {
        pingtime=time(0);
        if (OI(ping)<i)
            i=OI(ping);
    }
    if (OI(toping)) {
        if (OI(toping)<i)
            i=OI(toping);
    }
    if (i<MAXINT) {
	struct itimerval t;
	setsig(SIGALRM, sighandnot);
	t.it_interval.tv_sec = i;
	t.it_interval.tv_usec = 0;
	t.it_value.tv_sec = i+10;
	t.it_value.tv_usec = 0;
	setitimer(ITIMER_REAL, &t, 0);
    }

    if (pa>1)
        kill(pa, SIGUSR1);
#if 0
    sendctl(fd, "\x71starting up", 13, 1, 0);
#endif
#ifndef DONT_SEND_CONF
    myconfinfo.cmd=CT_CONFREQ;
    sendctl(fd, (char *)&myconfinfo, sizeof(myconfinfo), 1, MSG_OOB);
#endif

    gotsig=0;
    /* Main loop */
    while (kxchg(fd, ur)>=0) {
        if (gotsig) {
            Log(LOG_NOTICE, "%s", strsignal(gotsig));
            break;
        }
	if ((sd>=0) && ((i=eofsocks(sd)))) {
	    Log(LOG_NOTICE, "%s on socks connection", eofsocks_str[i]);
	    break;
	}
        t=time(0);
        if (t>=pongtime) {
            Log(LOG_NOTICE, "keepalive timeout");
            if (OI(maxerr)>=0 && (--errcnt<0))
                break;
            pongtime=MAXINT;
        }
        if (t>=pingtime) {
            char buf=CT_PING;
            sendctl(fd, &buf, 1, 1, 0);
            pingtime=t+OI(ping);
            pongtime=t+OI(toping);
        }
    }
    gotsig=-1;
#if 1
    sendctl(fd, "\x71going down", 12, 0, 0);
#endif
    if ((st=getstats()))
        Log(LOG_INFO, "Interface stats %s\n", st);
    Log(LOG_INFO, "KX stats: rreq=%d, req=%d, ind=%d, indb=%d, ack=%d,"
        " ackb=%d, unknown=%d\n", ks.rreq, ks.req, ks.ind, ks.indb,
        ks.ack, ks.ackb, ks.bogus);
    pspawn(OS(ipdown) ? OS(ipdown) : CTLDIR "/ip-down");
    closedev(fd);
    Log(LOG_INFO, "%s: daemon exiting", device);
    return 0;
}
