/*
   PKCIPE - public key based configuration tool for CIPE

   main.c - startup stuff

   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: main.c,v 1.13 2001/01/04 23:04:38 olaf Exp $ */

#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include "pkcipe.h"

#ifndef KEYFILE
#define KEYFILE "/etc/cipe/identity.priv"
#endif
#ifndef LOGFAC
#define LOGFAC LOG_DAEMON
#endif

void usage(const char *n) __attribute__((noreturn));

void SSLprinterror(int level)
{
    static int l=0;
    if (!l) {
        ERR_load_crypto_strings();
        l=1;
    }
    cipe_syslog(level, "%s", ERR_error_string(ERR_get_error(), NULL));
}

void term(int s)
{
    Log(LOG_NOTICE, "got termination signal");
    exit(255);
}

void tout(int s)
{
    Log(LOG_NOTICE, "Timeout");
    exit(3);
}

#define MAGIC_STRING_LEN 0x1b

int doFD(int fd, struct in_addr addr)
{
    unsigned char buf[32];
    int e;
    if (fcntl(fd, F_SETFD, FD_CLOEXEC)<0)
	perror("FD_CLOEXEC"); /* not fatal */
    signInit();
    vrfyInit();
    setSendKey(NULL);
    setRecvKey(NULL);
    sprintf(buf, "CIPE/%02d %-18.18s*", protoVersion, VERSION " " PVNOTE);
    packetSend(fd, (unsigned char *)buf, MAGIC_STRING_LEN);
    if (packetRecv(fd, buf, sizeof(buf))<6) {
	Log(LOG_INFO, "peer: no magic");
	close(fd);
	return 2;
    }
    for (e=0; buf[e]&&buf[e]!='/'; ++e);
    e=atoi(buf+e+1);
    if (protoVersion>e)
        protoVersion=e;
    debug((DEB_PROTO, "Using protocol %d", protoVersion));
    if (protoVersion<1 || protoVersion>MAX_SUPPORTED_PROTOCOL) {
	Log(LOG_INFO, "peer: unknown protocol: %d/%d", protoVersion, e);
	close(fd);
	return 2;
    }
    e=doProtocol(fd, addr);
    return (e<0) ? 2 : 0;
}

int doClient(const char *hp, struct sockaddr_in *ra)
{
    int c, e;
    struct sockaddr_in sa;
    if (getaddr(hp, &sa, "tcp")<0)
	return 1;
    if ((c=socket(PF_INET, SOCK_STREAM, 0))<0) {
	perror("socket");
	return 1;
    }
    setsig(SIGALRM, tout);
    alarm(timeout);
    if (connect(c, &sa, sizeof(sa))<0) {
	perror("connect");
	close(c);
	return 1;
    }
    if (ra)
	sa=*ra;
    Log(LOG_INFO, "connect to %s", inet_ntoa(sa.sin_addr));
    e=doFD(c, sa.sin_addr);
    close(c);
    return e;
}

#ifdef DEBUG
int doServer(int p, struct sockaddr_in *ra)
{
    struct sockaddr_in sa;
    int l, c, e;

    if ((l=socket(PF_INET, SOCK_STREAM, 0))<0) {
	perror("socket");
	return 1;
    }
    c=1;
    if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &c, sizeof(c))<0)
	perror("SO_REUSEADDR"); /* not fatal */
    if (fcntl(l, F_SETFD, FD_CLOEXEC)<0)
	perror("FD_CLOEXEC"); /* not fatal */
    memset(&sa, 0, sizeof(sa));
    sa.sin_family=AF_INET;
    sa.sin_port=htons(p);
    sa.sin_addr.s_addr=htonl(INADDR_ANY);
    if (bind(l, &sa, sizeof(sa))<0) {
	perror("bind");
	close(l);
	return 1;
    }
    if (listen(l, 64)<0) {
	perror("listen");
	close(l);
	return 1;
    }
    setsig(SIGALRM, tout);
    while (1) {
	e=sizeof(sa);
        alarm(0);
	if ((c=accept(l, &sa, &e))<0) {
	    if (errno!=EINTR)
		perror("accept");
	    continue;
	}
        Log(LOG_INFO, "connect from %s", inet_ntoa(sa.sin_addr));
        e=doFD(c, ra ? ra->sin_addr : sa.sin_addr);
        close(c);
        Log(LOG_INFO, "doServer: status %d", e);
    }
}
#endif

void usage(const char *n)
{
#ifdef DEBUG
    fprintf(stderr, "usage: %s [ -p proto ] [ -k keyfile ] [ -c host:port | -s port ]\n [ -r host ] [ -t timeout ] [ -D debug ] [ -E ] [ identity ]\n", n);
#else
    fprintf(stderr, "usage: %s [ -p proto ] [ -k keyfile ] [ -c host:port ]\n [ -r host ] [ -t timeout ] [ -E ] [ identity ]\n", n);
#endif
    exit(1);
}

int main(int argc, char *argv[])
{
    int c;
    char *ok=KEYFILE;
#ifdef DEBUG
    char *os=NULL;
#endif
    char *oc=NULL, *or=NULL;
    struct sockaddr_in ra;

    char *pn=strrchr(argv[0], '/');
    if (pn)
	++pn;
    else
	pn=argv[0];

#ifdef DEBUG
    {
        /* ensure we can dump core */
        struct rlimit rl;
        rl.rlim_cur=rl.rlim_max=8192*1024;
        if (setrlimit(RLIMIT_CORE, &rl)<0)
            perror("setrlimit");
        chdir("/var/run/cipe"); /* XX */
    }
#endif

    while ((c=getopt(argc, argv, "p:k:c:r:it:s:D:E"))!=EOF) {
	switch (c) {
        case 'p': protoVersion=atoi(optarg); break;
	case 'k': ok=optarg; break;
	case 'c': oc=optarg; break;
	case 'r': or=optarg; break;
	case 'i': break; /* ignored for compatibility */
	case 't': timeout=atoi(optarg); break;
#ifdef DEBUG
	case 's': os=optarg; break;
	case 'D': debugging=atoi(optarg); break;
#endif
        case 'E': logstderr=1; break;
	default: usage(pn);
	}
    }
    if (optind>=argc) {
	if (!(myIdentity=malloc(SYS_NMLN+2))) {
	    fprintf(stderr, "Out of memory, a hopeless case\n");
	    return 1;
	}
	if (gethostname(myIdentity, SYS_NMLN)<0) {
	    perror("gethostname");
	    return 1;
	}
    } else {
	myIdentity=argv[optind];
    }
    umask(077);

    OpenSSL_add_all_algorithms();
    if (initDH()<0)
	return -1;
    if (secchk(ok, 0077, 0022, 1)<0)
	return 1;
    {
	FILE *f=fopen(ok, "r");
	if (!f) {
	    perror("open keyfile");
	    return 1;
	}
	myKey=PEM_read_PrivateKey(f, NULL, NULL, NULL);
	fclose(f);
	if (!myKey || RSA_check_key(myKey->pkey.rsa)!=1) {
	    SSLprinterror(LOG_STDERR);
	    return 1;
	}
    }
    if (!logstderr)
        openlog(pn, LOG_CONS|LOG_PID, LOGFAC);
    if (or)
	getaddr(or, &ra, NULL);
    setsig(SIGINT, term);
    setsig(SIGTERM, term);
#ifdef DEBUG
    if (os)
	return doServer(atoi(os), or ? &ra : NULL);
#endif
    if (oc) {
	return doClient(oc, or ? &ra :NULL);
    } else {
	struct sockaddr_in sa;
	int ss=sizeof(sa);
	if (getpeername(0, &sa, &ss)<0) {
	    Log(LOG_ERR, "main: getsockname: %m");
	    sa.sin_addr.s_addr=htonl(INADDR_ANY);
	}
	Log(LOG_INFO, "connect from %s", inet_ntoa(sa.sin_addr));
	return doFD(0, or ? ra.sin_addr : sa.sin_addr);
    }
}
