/*
 * Copyright 1985, 1986, 1987, 1988 by the Massachusetts Institute
 * of Technology.
 *
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 */

#include "config.h"
#include "protos.h"

RCSID("$Id: kerberos.c,v 1.76 1997/12/15 00:01:40 assar Exp $");

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#elif defined(HAVE_SYS_TIME_H)
#include <sys/time.h>
#else
#include <time.h>
#endif

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if defined(HAVE_SYS_IOCTL_H) && SunOS != 4
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif /* HAVE_SYS_FILIO_H */

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#include <err.h>

#ifdef SOCKS
#include <socks.h>
#endif

#include <roken.h>
#include <base64.h>

#include <des.h>
#include <krb.h>
#include <krb_db.h>
#include <prot.h>
#include <klog.h>

#include <krb_log.h>

#include <kdc.h>

static des_key_schedule master_key_schedule;
static des_cblock master_key;

static struct timeval kerb_time;
static u_char master_key_version;
static char k_instance[INST_SZ];
static char *lt;
static int more;

static int mflag;		/* Are we invoked manually? */
static char *log_file = KRBLOG;	/* name of alt. log file */
static int nflag;		/* don't check max age */
static int rflag;		/* alternate realm specified */

/* fields within the received request packet */
static char *req_name_ptr;
static char *req_inst_ptr;
static char *req_realm_ptr;
static u_int32_t req_time_ws;

static char local_realm[REALM_SZ];

/* options */
static int max_age = -1;
static int pause_int = -1;

/*
 * Print usage message and exit.
 */
static void
usage(void)
{
    fprintf(stderr, "Usage: %s [-s] [-m] [-n] [-p pause_seconds]"
	    " [-a max_age] [-l log_file] [-i address_to_listen_on]"
	    " [-r realm] [database_pathname]\n",
	    __progname);
    exit(1);
}

/*
 * kerb_err_reply creates an error reply packet and sends it to the
 * client. 
 */

static void
kerb_err_reply(int f, struct sockaddr_in *client, int err, char *string)
{
    static KTEXT_ST e_pkt_st;
    KTEXT   e_pkt = &e_pkt_st;
    static char e_msg[128];

    strcpy(e_msg, "\nKerberos error -- ");
    strcat(e_msg, string);
    cr_err_reply(e_pkt, req_name_ptr, req_inst_ptr, req_realm_ptr,
		 req_time_ws, err, e_msg);
    sendto(f, (char*)e_pkt->dat, e_pkt->length, 0, (struct sockaddr *)client,
	   sizeof(*client));
}

static void
hang(void)
{
    if (pause_int == -1) {
	klog(L_KRB_PERR, "Kerberos will pause so as not to loop init");
	for (;;)
	    pause();
    } else {
	char buf[256];
	snprintf(buf, sizeof(buf),
		 "Kerberos will wait %d seconds before dying so as not to loop init",
		 pause_int);
	klog(L_KRB_PERR, buf);
	sleep(pause_int);
	klog(L_KRB_PERR, "Do svedania....\n");
	exit(1);
    }
}

static int
check_princ(char *p_name, char *instance, unsigned int lifetime, Principal *p)
{
    static int n;
    static int more;

    n = kerb_get_principal(p_name, instance, p, 1, &more);
    
    if (n < 0) {
	lt = klog(L_KRB_PERR, "Database unavailable!");
	hang();
    }
    
    /*
     * if more than one p_name, pick one, randomly create a session key,
     * compute maximum lifetime, lookup authorizations if applicable,
     * and stuff into cipher. 
     */
    if (n == 0) {
	/* service unknown, log error, skip to next request */
	lt = klog(L_ERR_UNK, "UNKNOWN %s.%s", p_name, instance);
	return KERB_ERR_PRINCIPAL_UNKNOWN;
    }
    if (more) {
	/* not unique, log error */
	lt = klog(L_ERR_NUN, "Principal not unique %s.%s", p_name, instance);
	return KERB_ERR_PRINCIPAL_NOT_UNIQUE;
    }
    /* If the user's key is null, we want to return an error */
    if ((p->key_low == 0) && (p->key_high == 0)) {
	/* User has a null key */
	lt = klog(L_ERR_NKY, "Null key %s.%s", p_name, instance);
	return KERB_ERR_NULL_KEY;
    }
    if (master_key_version != p->kdc_key_ver) {
	/* log error reply */
	lt = klog(L_ERR_MKV,
		  "Incorrect master key version for %s.%s: %d (should be %d)",
		  p->name, p->instance, p->kdc_key_ver, master_key_version);
	return KERB_ERR_NAME_MAST_KEY_VER;
    }
    /* make sure the service hasn't expired */
    if ((u_int32_t) p->exp_date < (u_int32_t) kerb_time.tv_sec) {
	/* service did expire, log it */
	time_t t = p->exp_date;
	lt = klog(L_ERR_SEXP,
		  "Principal %s.%s expired at %s", p->name, p->instance,
		  krb_stime(&t));
	return KERB_ERR_NAME_EXP;
    }
    /* ok is zero */
    return 0;
}

static void
unseal(des_cblock *key)
{
    kdb_encrypt_key(key, key, &master_key, master_key_schedule, DES_DECRYPT);
}


/* Set the key for krb_rd_req so we can check tgt */
static int
set_tgtkey(char *r)
              			/* Realm for desired key */
{
    int     n;
    static char lastrealm[REALM_SZ];
    Principal p_st;
    Principal *p = &p_st;
    des_cblock key;

    if (!strcmp(lastrealm, r))
	return (KSUCCESS);

    klog(L_ALL_REQ, "Getting key for %s", r);

    n = kerb_get_principal(KRB_TICKET_GRANTING_TICKET, r, p, 1, &more);
    if (n == 0)
	return (KFAILURE);

    /* unseal tgt key from master key */
    copy_to_key(&p->key_low, &p->key_high, key);
    unseal(&key);
    krb_set_key(key, 0);
    strcpy(lastrealm, r);
    return (KSUCCESS);
}


static int
kerberos(unsigned char *buf, int len,
	 char *proto, struct sockaddr_in *client,
	 struct sockaddr_in *server,
	 KTEXT rpkt)
{
    int pvno;
    int msg_type;
    int lsb;
    int life;
    int flags = 0;
    char name[ANAME_SZ], inst[INST_SZ], realm[REALM_SZ];
    char service[SNAME_SZ], sinst[INST_SZ];
    u_int32_t req_time;
    static KTEXT_ST ticket, cipher, adat;
    KTEXT tk = &ticket, ciph = &cipher, auth = &adat;
    AUTH_DAT ad;
    des_cblock session, key;
    int err;
    Principal a_name, s_name;
    
    char *msg;
    
    
    unsigned char *p = buf;
    if(len < 2){
	strcpy((char*)rpkt->dat, "Packet too short");
	return KFAILURE;
    }

    gettimeofday(&kerb_time, NULL);

    pvno = *p++;
    if(pvno != KRB_PROT_VERSION){
	msg = klog(L_KRB_PERR, "KRB protocol version mismatch (%d)", pvno);
	strcpy((char*)rpkt->dat, msg);
	return KERB_ERR_PKT_VER;
    }
    msg_type = *p++;
    lsb = msg_type & 1;
    msg_type &= ~1;
    switch(msg_type){
    case AUTH_MSG_KDC_REQUEST:
	/* XXX range check */
	p += krb_get_nir(p, name, inst, realm);
	p += krb_get_int(p, &req_time, 4, lsb);
	life = *p++;
	p += krb_get_nir(p, service, sinst, NULL);
	klog(L_INI_REQ,
	     "AS REQ %s.%s@%s for %s.%s from %s (%s/%u)", 
	     name, inst, realm, service, sinst,
	     inet_ntoa(client->sin_addr),
	     proto, ntohs(server->sin_port));
	if((err = check_princ(name, inst, 0, &a_name))){
	    strcpy((char*)rpkt->dat, krb_get_err_text(err));
	    return err;
	}
	tk->length = 0;
	if((err = check_princ(service, sinst, 0, &s_name))){
	    strcpy((char*)rpkt->dat, krb_get_err_text(err));
	    return err;
	}
	life = min(life, s_name.max_life);
	life = min(life, a_name.max_life);
    
	des_new_random_key(&session);
	copy_to_key(&s_name.key_low, &s_name.key_high, key);
	unseal(&key);
	krb_create_ticket(tk, flags, a_name.name, a_name.instance, 
			  local_realm, client->sin_addr.s_addr,
			  session, 
			  life, kerb_time.tv_sec, 
			  s_name.name, s_name.instance, &key);
	copy_to_key(&a_name.key_low, &a_name.key_high, key);
	unseal(&key);
	create_ciph(ciph, session, s_name.name, s_name.instance,
		    local_realm, life, s_name.key_version, tk, 
		    kerb_time.tv_sec, &key);
	memset(&session, 0, sizeof(session));
	memset(&key, 0, sizeof(key));
	{
	    KTEXT r;
	    r = create_auth_reply(name, inst, realm, req_time, 0, 
				  a_name.exp_date, a_name.key_version, ciph);
	    memcpy(rpkt, r, sizeof(*rpkt));
	}
	return 0;
    case AUTH_MSG_APPL_REQUEST:
	strcpy(realm, (char*)buf + 3);
	if((err = set_tgtkey(realm))){
	    msg = klog(L_ERR_UNK,
		       "Unknown realm %s from %s (%s/%u)", 
		       realm, inet_ntoa(client->sin_addr),
		       proto, ntohs(server->sin_port));
	    strcpy((char*)rpkt->dat, msg);
	    return err;
	}
	p = buf + strlen(realm) + 4;
	p = p + p[0] + p[1] + 2;
	auth->length = p - buf;
	memcpy(auth->dat, buf, auth->length);
	err = krb_rd_req(auth, KRB_TICKET_GRANTING_TICKET,
			 realm, client->sin_addr.s_addr, &ad, 0);
	if(err){
	    msg = klog(L_ERR_UNK,
		       "krb_rd_req from %s (%s/%u): %s", 
		       inet_ntoa(client->sin_addr),
		       proto,
		       ntohs(server->sin_port),
		       krb_get_err_text(err));
	    strcpy((char*)rpkt->dat, msg);
	    return err;
	}
	p += krb_get_int(p, &req_time, 4, lsb);
	life = *p++;
	p += krb_get_nir(p, service, sinst, NULL);
	klog(L_APPL_REQ,
	     "APPL REQ %s.%s@%s for %s.%s from %s (%s/%u)",
	     ad.pname, ad.pinst, ad.prealm,
	     service, sinst,
	     inet_ntoa(client->sin_addr),
	     proto,
	     ntohs(server->sin_port));

	if(strcmp(ad.prealm, realm)){
	    msg = klog(L_ERR_UNK, "Can't hop realms: %s -> %s", 
		       realm, ad.prealm);
	    strcpy((char*)rpkt->dat, msg);
	    return KERB_ERR_PRINCIPAL_UNKNOWN;
	}

	if(!strcmp(service, "changepw")){
	    strcpy((char*)rpkt->dat, 
		   "Can't authorize password changed based on TGT");
	    return KERB_ERR_PRINCIPAL_UNKNOWN;
	}

	err = check_princ(service, sinst, life, &s_name);
	if(err){
	    strcpy((char*)rpkt->dat, krb_get_err_text(err));
	    return err;
	}
	life = min(life, 
		   krb_time_to_life(kerb_time.tv_sec, 
				    krb_life_to_time(ad.time_sec, 
						     ad.life)));
	life = min(life, s_name.max_life);
	copy_to_key(&s_name.key_low, &s_name.key_high, key);
	unseal(&key);
	des_new_random_key(&session);
	krb_create_ticket(tk, flags, ad.pname, ad.pinst, ad.prealm,
			  client->sin_addr.s_addr, &session,
			  life, kerb_time.tv_sec,
			  s_name.name, s_name.instance,
			  &key);
	
	memset(&key, 0, sizeof(key));

	create_ciph(ciph, session, service, sinst, local_realm,
		    life, s_name.key_version, tk,
		    kerb_time.tv_sec, &ad.session);

	memset(&session, 0, sizeof(session));
	memset(ad.session, 0, sizeof(ad.session));
	{
	    KTEXT r;
	    r =create_auth_reply(ad.pname, ad.pinst, ad.prealm, 
				 req_time, 0, 0, 0, ciph);
	    memcpy(rpkt, r, sizeof(*rpkt));
	}
	memset(&s_name, 0, sizeof(s_name));
	return 0;
	
    case AUTH_MSG_ERR_REPLY:
	return -1;
    default:
	msg = klog(L_KRB_PERR,
		   "Unknown message type: %d from %s (%s/%u)", 
		   msg_type,
		   inet_ntoa(client->sin_addr),
		   proto,
		   ntohs(server->sin_port));
	strcpy((char*)rpkt->dat, msg);
	return KFAILURE;
    }
}


static void
kerberos_wrap(int s, KTEXT data, char *proto, struct sockaddr_in *client, 
	      struct sockaddr_in *server)
{
    KTEXT_ST pkt;
    int http_flag = strcmp(proto, "http") == 0;
    int err = kerberos(data->dat, data->length, proto, client, server, &pkt);
    if(err == -1)
	return;
    if(http_flag){
	const char *msg = 
	    "HTTP/1.1 200 OK\r\n"
	    "Server: KTH-KRB/" VERSION "\r\n"
	    "Content-type: application/octet-stream\r\n"
	    "Content-transfer-encoding: binary\r\n\r\n";
	sendto(s, msg, strlen(msg), 0, (struct sockaddr *)client,
	       sizeof(*client));
    }
    if(err){
	kerb_err_reply(s, client, err, (char*)pkt.dat);
	return;
    }
    sendto(s, pkt.dat, pkt.length, 0, (struct sockaddr *)client,
	   sizeof(*client));
}


/*
 * setup_disc 
 *
 * disconnect all descriptors, remove ourself from the process
 * group that spawned us. 
 */

static void
setup_disc(void)
{
    int     s;

    for (s = 0; s < 3; s++) {
	close(s);
    }

    open("/dev/null", 0);
    dup2(0, 1);
    dup2(0, 2);

    setsid();

    chdir("/tmp");
    return;
}

/*
 * Make sure that database isn't stale.
 *
 * Exit if it is; we don't want to tell lies.
 */

static void
check_db_age(void)
{
    long age;
    
    if (max_age != -1) {
	/* Requires existance of kerb_get_db_age() */
	gettimeofday(&kerb_time, 0);
	age = kerb_get_db_age();
	if (age == 0) {
	    klog(L_KRB_PERR, "Database currently being updated!");
	    hang();
	}
	if ((age + max_age) < kerb_time.tv_sec) {
	    klog(L_KRB_PERR, "Database out of date!");
	    hang();
	    /* NOTREACHED */
	}
    }
}

struct descr{
    int s;
    KTEXT_ST buf;
    int type;
    int timeout;
    struct sockaddr_in addr;
};

static void
mksocket(struct descr *d, struct in_addr addr, int type, 
	 const char *service, int port)
{
    int     on = 1;
    int sock;

    memset(d, 0, sizeof(struct descr));
    if ((sock = socket(AF_INET, type, 0)) < 0)
	err (1, "socket");
#if defined(SO_REUSEADDR) && defined(HAVE_SETSOCKOPT)
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on,
		   sizeof(on)) < 0)
	warn ("setsockopt (SO_REUSEADDR)");
#endif
    memset(&d->addr, 0, sizeof(d->addr));
    d->addr.sin_family = AF_INET;
    d->addr.sin_port   = port;
    d->addr.sin_addr   = addr;
    if (bind(sock, (struct sockaddr *)&d->addr, sizeof(d->addr)) < 0)
	err (1, "bind '%s/%s' (%d)",
	     service, (type == SOCK_DGRAM) ? "udp" : "tcp",
	     ntohs(d->addr.sin_port));
    
    if(type == SOCK_STREAM)
	listen(sock, SOMAXCONN);
    d->s = sock;
    d->type = type;
}


static void loop(struct descr *fds, int maxfd);

struct port_spec {
    int port;
    int type;
};

static int
add_port(struct port_spec **ports, int *num_ports, int port, int type)
{
    struct port_spec *tmp;
    tmp = realloc(*ports, (*num_ports + 1) * sizeof(*tmp));
    if(tmp == NULL)
	return ENOMEM;
    *ports = tmp;
    tmp[*num_ports].port = port;
    tmp[*num_ports].type = type;
    (*num_ports)++;
    return 0;
}

static void
make_sockets(char *port_spec, struct in_addr *i_addr, 
	     struct descr **fds, int *nfds)
{
    int tp;
    struct in_addr *a;
    char *p, *q, *pos = NULL;
    struct servent *sp;
    struct port_spec *ports = NULL;
    int num_ports = 0;
    int i, j;
    

    for(p = strtok_r(port_spec, ", \t", &pos); 
	p; 
	p = strtok_r(NULL, ", \t", &pos)){
	if(strcmp(p, "+") == 0){
	    add_port(&ports, &num_ports, 88, SOCK_DGRAM);
	    add_port(&ports, &num_ports, 88, SOCK_STREAM);
	    add_port(&ports, &num_ports, 750, SOCK_DGRAM);
	    add_port(&ports, &num_ports, 750, SOCK_STREAM);
	}else{
	    q = strchr(p, '/');
	    if(q){
		*q = 0;
		q++;
	    }
	    sp = getservbyname(p, q);
	    if(sp)
		tp = ntohs(sp->s_port);
	    else if(sscanf(p, "%d", &tp) != 1) {
		warnx("Unknown port: %s%s%s", p, q ? "/" : "", q ? q : "");
		continue;
	    }
	    if(q){
		if(strcasecmp(q, "tcp") == 0)
		    add_port(&ports, &num_ports, tp, SOCK_STREAM);
		else if(strcasecmp(q, "udp") == 0)
		    add_port(&ports, &num_ports, tp, SOCK_DGRAM);
		else
		    warnx("Unknown protocol type: %s", q);
	    }else{
		add_port(&ports, &num_ports, tp, SOCK_DGRAM);
		add_port(&ports, &num_ports, tp, SOCK_STREAM);
	    }
	}
    }

    if(num_ports == 0)
	errx(1, "No valid ports specified!");
    
    if (i_addr) {
	*nfds = 1;
	a = malloc(sizeof(*a) * *nfds);
	if (a == NULL)
	    errx (1, "Failed to allocate %u bytes",
		  sizeof(*a) * *nfds);
	memcpy(a, i_addr, sizeof(struct in_addr));
    } else
	*nfds = k_get_all_addrs (&a);
    if (*nfds < 0) {
	struct in_addr any;

	any.s_addr = INADDR_ANY;

	warnx ("Could not get local addresses, binding to INADDR_ANY");
	*nfds = 1;
	a = malloc(sizeof(*a) * *nfds);
	if (a == NULL)
	    errx (1, "Failed to allocate %u bytes",
		  sizeof(*a) * *nfds);
	memcpy(a, &any, sizeof(struct in_addr));
    }
    *fds = malloc(*nfds * num_ports * sizeof(**fds));
    if (*fds == NULL)
	errx (1, "Failed to allocate %u bytes",
	      *nfds * num_ports * sizeof(**fds));
    for (i = 0; i < *nfds; i++) {
	for(j = 0; j < num_ports; j++) {
	    mksocket(*fds + num_ports * i + j, a[i], 
		     ports[j].type, "", htons(ports[j].port));
	}
    }
    *nfds *= num_ports;
    free(ports);
    free (a);
}


int
main(int argc, char **argv)
{
    int     child;
    int c;
    struct descr *fds;
    int nfds;
    int n;
    int     kerror;
    int i_flag = 0;
    struct in_addr i_addr;
    char *port_spec = "+";

    umask(077);		/* Create protected files */

    set_progname (argv[0]);

    while ((c = getopt(argc, argv, "snmp:P:a:l:r:i:")) != EOF) {
	switch(c) {
	case 's':
	    /*
	     * Set parameters to slave server defaults.
	     */
	    if (max_age == -1 && !nflag)
		max_age = ONE_DAY;	/* 24 hours */
	    if (pause_int == -1)
		pause_int = FIVE_MINUTES; /* 5 minutes */
	    break;
	case 'n':
	    max_age = -1;	/* don't check max age. */
	    nflag++;
	    break;
	case 'm':
	    mflag++;		/* running manually; prompt for master key */
	    break;
	case 'p':
	    /* Set pause interval. */
	    if (!isdigit(optarg[0]))
		usage();
	    pause_int = atoi(optarg);
	    if ((pause_int < 5) ||  (pause_int > ONE_HOUR)) {
		fprintf(stderr, "pause_int must be between 5 and 3600 seconds.\n");
		usage();
	    }
	    break;
	case 'P':
	    port_spec = optarg;
	    break;
	case 'a':
	    /* Set max age. */
	    if (!isdigit(optarg[0])) 
		usage();
	    max_age = atoi(optarg);
	    if ((max_age < ONE_HOUR) || (max_age > THREE_DAYS)) {
		fprintf(stderr, "max_age must be between one hour and three days, in seconds\n");
		usage();
	    }
	    break;
	case 'l':
	    /* Set alternate log file */
	    log_file = optarg;
	    break;
	case 'r':
	    /* Set realm name */
	    rflag++;
	    strcpy(local_realm, optarg);
	    break;
	case 'i':
	    /* Only listen on this address */
	    if(inet_aton (optarg, &i_addr) == 0) {
		fprintf (stderr, "Bad address: %s\n", optarg);
		exit (1);
	    }
	    ++i_flag;
	    break;
	default:
	    usage();
	    break;
	}
    }
    
    if (optind == (argc-1)) {
	if (kerb_db_set_name(argv[optind]) != 0) {
	    fprintf(stderr, "Could not set alternate database name\n");
	    exit(1);
	}
	optind++;
    }

    if (optind != argc)
	usage();
	
    printf("Kerberos server starting\n");
    
    if ((!nflag) && (max_age != -1))
	printf("\tMaximum database age: %d seconds\n", max_age);
    if (pause_int != -1)
	printf("\tSleep for %d seconds on error\n", pause_int);
    else
	printf("\tSleep forever on error\n");
    if (mflag)
	printf("\tMaster key will be entered manually\n");
    
    printf("\tLog file is %s\n", log_file);

    kset_logfile(log_file);
    
    /* find our hostname, and use it as the instance */
    if (gethostname(k_instance, INST_SZ))
	err (1, "gethostname");

    make_sockets(port_spec, i_flag ? &i_addr : NULL, &fds, &nfds);

    /* do all the database and cache inits */
    if ((n = kerb_init())) {
	if (mflag) {
	    printf("Kerberos db and cache init ");
	    printf("failed = %d ...exiting\n", n);
	    exit (1);
	} else {
	    klog(L_KRB_PERR,
	    "Kerberos db and cache init failed = %d ...exiting", n);
	    hang();
	}
    }

    /* Make sure database isn't stale */
    check_db_age();
    
    /* setup master key */
    if (kdb_get_master_key (mflag, &master_key, master_key_schedule) != 0) {
      klog (L_KRB_PERR, "kerberos: couldn't get master key.");
      exit (1);
    }
    kerror = kdb_verify_master_key (&master_key, master_key_schedule, stdout);
    if (kerror < 0) {
      klog (L_KRB_PERR, "Can't verify master key.");
      memset(master_key, 0, sizeof (master_key));
      memset (master_key_schedule, 0, sizeof (master_key_schedule));
      exit (1);
    }

    master_key_version = (u_char) kerror;

    fprintf(stdout, "\nCurrent Kerberos master key version is %d\n",
	    master_key_version);
    des_init_random_number_generator(&master_key);

    if (!rflag) {
	/* Look up our local realm */
	krb_get_lrealm(local_realm, 1);
    }
    fprintf(stdout, "Local realm: %s\n", local_realm);
    fflush(stdout);

    if (set_tgtkey(local_realm)) {
	/* Ticket granting service unknown */
	klog(L_KRB_PERR, "Ticket granting ticket service unknown");
	fprintf(stderr, "Ticket granting ticket service unknown\n");
	exit(1);
    }
    if (mflag) {
	if ((child = fork()) != 0) {
	    printf("Kerberos started, PID=%d\n", child);
	    exit(0);
	}
	setup_disc();
    }
    
    klog(L_ALL_REQ, "Starting Kerberos for %s (kvno %d)", 
	 local_realm, master_key_version);
    
    /* receive loop */
    loop(fds, nfds);
    exit(1);
}


static void
read_socket(struct descr *n)
{
    int b;
    struct sockaddr_in from;
    int fromlen = sizeof(from);
    b = recvfrom(n->s, n->buf.dat + n->buf.length, 
		 MAX_PKT_LEN - n->buf.length, 0, 
		 (struct sockaddr *)&from, &fromlen);
    if(b < 0){
	if(n->type == SOCK_STREAM){
	    close(n->s);
	    n->s = -1;
	}
	n->buf.length = 0;
	return;
    }
    n->buf.length += b;
    if(n->type == SOCK_STREAM){
	char *proto = "tcp";
	if(n->buf.length > 4 && 
	   strncmp((char *)n->buf.dat, "GET ", 4) == 0 &&
	   strncmp((char *)n->buf.dat + n->buf.length - 4, 
		   "\r\n\r\n", 4) == 0){
	    char *p;
	    char *save = NULL;

	    n->buf.dat[n->buf.length - 1] = 0;
	    strtok_r(n->buf.dat, " \t\r\n", &save);
	    p = strtok_r(NULL, " \t\r\n", &save);
	    if(p == NULL)
		p = "";
	    if(*p == '/') p++;
	    n->buf.length = base64_decode(p, n->buf.dat);
	    if(n->buf.length <= 0){
		const char *msg = 
		    "HTTP/1.1 404 Not found\r\n"
		    "Server: KTH-KRB/" VERSION "\r\n"
		    "Content-type: text/html\r\n"
		    "Content-transfer-encoding: 8bit\r\n\r\n"
		    "<TITLE>404 Not found</TITLE>\r\n"
		    "<H1>404 Not found</H1>\r\n"
		    "That page does not exist. Information about "
		    "<A HREF=\"http://www.pdc.kth.se/kth-krb\">KTH-KRB</A> "
		    "is available elsewhere.\r\n";
		fromlen = sizeof(from);
		if(getpeername(n->s,(struct sockaddr*)&from, &fromlen) == 0)
		    klog(L_KRB_PERR, "Unknown HTTP request from %s", 
			 inet_ntoa(from.sin_addr));
		else
		    klog(L_KRB_PERR, "Unknown HTTP request from <unknown>");
		write(n->s, msg, strlen(msg));
		close(n->s);
		n->s = -1;
		n->buf.length = 0;
		return;
	    }
	    proto = "http";
	    b = 0;
	}
	else if(n->buf.length >= 4 && n->buf.dat[0] == 0){
	    /* if this is a new type of packet (with
	       the length attached to the head of the
	       packet), and there is no more data to
	       be read, fake an old packet, so the
	       code below will work */
	    u_int32_t len;
	    krb_get_int(n->buf.dat, &len, 4, 0);
	    if(n->buf.length == len + 4){
		memmove(n->buf.dat, n->buf.dat + 4, len);
		b = 0;
	    }
	}
	if(b == 0){
	    /* handle request if there are 
	       no more bytes to read */
	    fromlen = sizeof(from);
	    getpeername(n->s,(struct sockaddr*)&from, &fromlen);
	    kerberos_wrap(n->s, &n->buf, proto, &from,
			  &n->addr);
	    n->buf.length = 0;
	    close(n->s);
	    n->s = -1;
	}
    }else{
	/* udp packets are atomic */
	kerberos_wrap(n->s, &n->buf, "udp", &from,
		      &n->addr);
	n->buf.length = 0;
    }
}

static void
loop(struct descr *fds, int nfds)
{
    for (;;) {
	int ret;
        fd_set readfds;
	struct timeval tv;
	int maxfd = 0;
	struct descr *n, *minfree;
	int accepted; /* accept at most one socket per `round' */
	
	FD_ZERO(&readfds);
	gettimeofday(&tv, NULL);
	maxfd = 0;
	minfree = NULL;
	/* Remove expired TCP sockets, and add all other 
	   to the set we are selecting on */
	for(n = fds; n < fds + nfds; n++){
	    if(n->s >= 0 && n->timeout && tv.tv_sec > n->timeout){
		kerb_err_reply(n->s, NULL, KERB_ERR_TIMEOUT, "Timeout");
		close(n->s);
		n->s = -1;
	    }
	    if(n->s < 0){
		if(minfree == NULL) minfree = n;
		continue;
	    }
	    FD_SET(n->s, &readfds);
	    maxfd = max(maxfd, n->s);
	}
	/* add more space for sockets */
	if(minfree == NULL){
	    int i = nfds;
	    struct descr *new;
	    nfds *=2;
	    new = realloc(fds, sizeof(struct descr) * nfds);
	    if(new){
		fds = new;
		minfree = fds + i;
		for(; i < nfds; i++) fds[i].s = -1;
	    }
	}
	ret = select(maxfd + 1, &readfds, 0, 0, 0);
	accepted = 0;
	for (n = fds; n < fds + nfds; n++){
	    if(n->s < 0) continue;
	    if (FD_ISSET(n->s, &readfds)){
		if(n->type == SOCK_STREAM && n->timeout == 0){
		    /* add accepted socket to list of sockets we are
                       selecting on */
		    int s;
		    if(accepted) continue;
		    accepted = 1;
		    s = accept(n->s, NULL, 0);
		    if(minfree == NULL){
			kerb_err_reply(s, NULL, KFAILURE, "Out of memory");
			close(s);
		    }else{
			minfree->s = s;
			minfree->type = SOCK_STREAM;
			gettimeofday(&tv, NULL);
			minfree->timeout = tv.tv_sec + 4; /* XXX */
			minfree->buf.length = 0;
			memcpy(&minfree->addr, &n->addr, sizeof(minfree->addr));
		    }
		}else
		    read_socket(n);
	    }
	}
    }
}
