diff options
Diffstat (limited to 'crypto/kerberosIV/server/kerberos.c')
-rw-r--r-- | crypto/kerberosIV/server/kerberos.c | 1089 |
1 files changed, 1089 insertions, 0 deletions
diff --git a/crypto/kerberosIV/server/kerberos.c b/crypto/kerberosIV/server/kerberos.c new file mode 100644 index 0000000..09a65df --- /dev/null +++ b/crypto/kerberosIV/server/kerberos.c @@ -0,0 +1,1089 @@ +/* + * Copyright 1985, 1986, 1987, 1988 by the Massachusetts Institute + * of Technology. + * + * For copying and distribution information, please see the file + * <mit-copyright.h>. + */ +/* $FreeBSD$ */ + +#include "config.h" +#include "protos.h" + +RCSID("$Id: kerberos.c,v 1.87.2.3 2000/10/18 20:24:13 assar Exp $"); + +/* + * If support for really large numbers of network interfaces is + * desired, define FD_SETSIZE to some suitable value. + */ +#define FD_SETSIZE (4*1024) + +#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 != 40 +#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 <openssl/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 *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]; + + snprintf (e_msg, sizeof(e_msg), + "\nKerberos error -- %s", 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); + strlcpy (lastrealm, r, REALM_SZ); + 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){ + strlcpy((char*)rpkt->dat, + "Packet too short", + sizeof(rpkt->dat)); + return KFAILURE; + } + + gettimeofday(&kerb_time, NULL); + + pvno = *p++; + if(pvno != KRB_PROT_VERSION){ + msg = klog(L_KRB_PERR, "KRB protocol version mismatch (%d)", pvno); + strlcpy((char*)rpkt->dat, + msg, + sizeof(rpkt->dat)); + 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, sizeof(name), + inst, sizeof(inst), + realm, sizeof(realm)); + p += krb_get_int(p, &req_time, 4, lsb); + life = *p++; + p += krb_get_nir(p, service, sizeof(service), + sinst, sizeof(sinst), NULL, 0); + 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))){ + strlcpy((char*)rpkt->dat, + krb_get_err_text(err), + sizeof(rpkt->dat)); + return err; + } + tk->length = 0; + if((err = check_princ(service, sinst, 0, &s_name))){ + strlcpy((char*)rpkt->dat, + krb_get_err_text(err), + sizeof(rpkt->dat)); + 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: + strlcpy(realm, (char*)buf + 3, REALM_SZ); + 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)); + strlcpy((char*)rpkt->dat, + msg, + sizeof(rpkt->dat)); + 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)); + strlcpy((char*)rpkt->dat, + msg, + sizeof(rpkt->dat)); + return err; + } + p += krb_get_int(p, &req_time, 4, lsb); + life = *p++; + p += krb_get_nir(p, service, sizeof(service), + sinst, sizeof(sinst), NULL, 0); + 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); + strlcpy((char*)rpkt->dat, + msg, + sizeof(rpkt->dat)); + return KERB_ERR_PRINCIPAL_UNKNOWN; + } + + if(!strcmp(service, "changepw")){ + strlcpy((char*)rpkt->dat, + "Can't authorize password changed based on TGT", + sizeof(rpkt->dat)); + return KERB_ERR_PRINCIPAL_UNKNOWN; + } + + err = check_princ(service, sinst, life, &s_name); + if(err){ + strlcpy((char*)rpkt->dat, + krb_get_err_text(err), + sizeof(rpkt->dat)); + 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)); + strlcpy((char*)rpkt->dat, + msg, + sizeof(rpkt->dat)); + 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/1\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 (sock >= FD_SETSIZE) { + errno = EMFILE; + errx(1, "Aborting: too many descriptors"); + } +#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(const 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; + char *port_spec_copy = strdup (port_spec); + + if (port_spec_copy == NULL) + err (1, "strdup"); + + for(p = strtok_r(port_spec_copy, ", \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); + } + } + } + free (port_spec_copy); + + 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 %lu bytes", + (unsigned long)(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 %lu bytes", + (unsigned long)(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 %lu bytes", + (unsigned long)(*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:")) != -1) { + switch(c) { + case 's': + /* + * Set parameters to slave server defaults. + */ + if (max_age == -1 && !nflag) + max_age = THREE_DAYS; /* Survive weekend */ + 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. */ + char *tmp; + + pause_int = strtol (optarg, &tmp, 0); + if (pause_int == 0 && tmp == optarg) { + fprintf(stderr, "pause_int `%s' not a number\n", optarg); + usage (); + } + + 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. */ + char *tmp; + + max_age = strtol (optarg, &tmp, 0); + if (max_age == 0 && tmp == optarg) { + fprintf (stderr, "max_age `%s' not a number\n", optarg); + usage (); + } + 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++; + strlcpy(local_realm, optarg, sizeof(local_realm)); + 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); + + 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((char *)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/1\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 fd_set readfds; + +static void +loop(struct descr *fds, int base_nfds) +{ + int nfds = base_nfds; + int max_tcp = min(FD_SETSIZE, getdtablesize()) - fds[base_nfds - 1].s; + if (max_tcp <= 10) { + errno = EMFILE; + errx(1, "Aborting: too many descriptors"); + } + max_tcp -= 10; /* We need a few extra for DB, logs, etc. */ + if (max_tcp > 100) max_tcp = 100; /* Keep to some sane limit. */ + + for (;;) { + int ret; + struct timeval tv; + int next_timeout = 10; /* In seconds */ + 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); + next_timeout = min(next_timeout, tv.tv_sec - n->timeout); + } + /* add more space for sockets */ + if (minfree == NULL && nfds < base_nfds + max_tcp) { + int i = nfds; + struct descr *new; + nfds *=2; + if (nfds > base_nfds + max_tcp) + nfds = base_nfds + max_tcp; + new = realloc(fds, sizeof(struct descr) * nfds); + if(new){ + fds = new; + minfree = fds + i; + for(; i < nfds; i++) fds[i].s = -1; + } + } + if (minfree == NULL) { + /* + * We are possibly the subject of a DOS attack, pick a TCP + * connection at random and drop it. + */ + int r = rand() % (nfds - base_nfds); + r = r + base_nfds; + FD_CLR(fds[r].s, &readfds); + close(fds[r].s); + fds[r].s = -1; + minfree = &fds[r]; + } + if (next_timeout < 0) next_timeout = 0; + tv.tv_sec = next_timeout; + tv.tv_usec = 0; + ret = select(maxfd + 1, &readfds, 0, 0, &tv); + if (ret < 0) { + if (errno != EINTR) + klog(L_KRB_PERR, "select: %s", strerror(errno)); + continue; + } + 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 || s >= FD_SETSIZE) { + 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); + } + } + } +} |