/*
   PKCIPE - public key based configuration tool for CIPE

   negotiate.c - ciped option and invocation handling

   Copyright 2000 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: negotiate.c,v 1.11 2000/12/05 19:21:11 olaf Exp $ */

#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include "pkcipe.h"

#ifndef OPTDIR
#define OPTDIR "/var/run/cipe/%s"
#endif
#ifndef CIPED
#define CIPED "/usr/local/sbin/ciped-cb"
#endif

int cipeSocket=-1;

#define OPTNAMMAX 16
#define OPTVALMAX 64

typedef struct _optlist {
    struct _optlist *next;
    char nam[OPTNAMMAX];
    char val[OPTVALMAX];
    char oflag;
} optlist;

static optlist *opts=NULL;

void setOption(const char *n, const char *v, char oflag)
{
    optlist *p;
    for (p=opts; p; p=p->next)
	if (!strcmp(n, p->nam))
	    break;
    if (!p) {
	if (!(p=malloc(sizeof(*p)))) {
	    Log(LOG_ERR, "setOption: malloc: %m");
	    return; /* XX handle this */
	}
	memset(p, 0, sizeof(*p));
	if (mlock(p, sizeof(*p))<0)
	    Log(LOG_ERR, "setOption: mlock: %m"); /* not fatal */
	p->next=opts;
	opts=p;
    }
    strncpy(p->nam, n, OPTNAMMAX-1);
    if (v)
	strncpy(p->val, v, OPTVALMAX-1);
    else
	p->val[0]='\0';
    p->oflag=oflag;
}

#if 0 /* not used by now */
void unsetOption(const char *n)
{
    optlist *p, *q;
    for (p=opts; p; p=q->next) {
	q=p;
	if (!strcmp(n, p->nam))
	    break;
    }
    if (!p)
	return;
    if (q==opts)
	opts=p->next;
    else
	q->next=p->next;
    free(p);
}
#endif

/* don't use stdio here because of secret keys which shouldn't go
   through buffers */
static void printOptions(int fd)
{
    optlist *p;
    for (p=opts; p; p=p->next) {
	if (p->oflag==OF_REJECT)
	    continue;
        xwrite(fd, p->nam, strlen(p->nam));
	if (*p->val) {
            xwrite(fd, "=", 1);
            xwrite(fd, p->val, strlen(p->val));
        }
        xwrite(fd, "\n", 1);
    }
}

void readOptions(FILE *f)
{
    char buf[256];
    char *p, *q;
    char c;
    while (fgets(buf, sizeof(buf), f)) {
	c=OF_DEFAULT;
	for (p=buf; isspace(*p); ++p);
	switch (*p) {
	case 0:
	case '\n':
	case '#':
	    continue;
	case OF_REQUIRE:
	case OF_REQVAL:
	case OF_REJECT:
	case OF_MIN:
	case OF_MAX:
	case OF_IGNORE:
	    c=*p;
	    ++p;
	}
	parseopt(p, &p, &q);
	if (!strcasecmp(p, "key")) {
	    /* sanity check */
	    Log(LOG_WARNING, "attempt to use static key ignored");
	    continue;
	}
	setOption(p, q, c);
    }
}

pState negotiate(int fd, unsigned char *pkt, int len)
{
    debug((DEB_PROTO, "negotiate: %s", pkt+1));

    if (*pkt!=PKT_OPT_REQ)
        goto err;

    if (!strncmp(pkt+1, "me=", 3)) {
	setOption("peer", pkt+4, OF_DEFAULT);
	/**/
	return ready(fd);
    }
 err:
    {
        char buf[32];
        int i=snprintf(buf, sizeof(buf), "%cunimplemented option", PKT_ERROR);
        packetSend(fd, buf, i);
        return Serr;
    }
}

pState ready(int fd)
{
    char buf[PKTMAXLEN];
    int i;

    alarm(0); /* ?? */
    lockMaster();
    /* when this routine returns error, don't unlock;
       we then expect the program to exit soon */
    if ((i=fork())<0) {
	i=snprintf(buf, sizeof(buf), "%cfork failed: %s",
		   PKT_ERROR, strerror(errno));
	packetSend(fd, buf, i);
	Log(LOG_ERR, "ready: %s", buf+1);
	return Serr;
    }
    if (i) {
	/* we have exactly one child, wait till it daemonizes */
	int r;
	char *p;
	Log(LOG_NOTICE, "starting %s for peer %s", CIPED, peerIdentity);
	wait(&r);
	p=retstatus(r);
	if (!p) {
	    r=snprintf(buf, sizeof(buf), "%cstarted ciped", PKT_READY);
	    packetSend(fd, buf, r);
	    unlockMaster();
	    return Sready;
	}
	i=snprintf(buf, sizeof(buf), "%cciped %s", PKT_ERROR, p);
	packetSend(fd, buf, i);
	Log(LOG_ERR, "ready: %s", buf+1);
	return Serr;
    }
    /* child */
    setOption("arg", peerIdentity, OF_REQVAL);
    snprintf(buf, sizeof(buf), OPTDIR, peerIdentity);
    if ((i=open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0600))<0)
	exit(98);
    printOptions(i);
    close(i);
    close(fd);
    closelog();
    {
	char ba[16];
	char *na[]={CIPED, "-o", buf, "-s", ba, NULL};
	if (cipeSocket>=0) {
	    snprintf(ba, sizeof(ba), "%d", cipeSocket);
	} else {
	    /* should not happen */
	    Log(LOG_ERR, "internal cipeSocket<0");
	    na[3]=NULL;
	}
	execv(CIPED, na);
    }
    exit(99);
}

