X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=common-src%2Fkrb4-security.c;h=6a3dad380be0713e9afabfafcb66dbc24a70ad4f;hb=12179dea039515c06168c0037d048566a3f623de;hp=521ba7139e705ae3edb97ad5087a54a3d1f040af;hpb=3ab887b9bc819a846c75dd7f2ee5d41fac22b19f;p=debian%2Famanda diff --git a/common-src/krb4-security.c b/common-src/krb4-security.c index 521ba71..6a3dad3 100644 --- a/common-src/krb4-security.c +++ b/common-src/krb4-security.c @@ -1,6 +1,6 @@ /* * Amanda, The Advanced Maryland Automatic Network Disk Archiver - * Copyright (c) 1993 University of Maryland + * Copyright (c) 1993,1999 University of Maryland * All Rights Reserved. * * Permission to use, copy, modify, distribute, and sell this software and its @@ -20,50 +20,276 @@ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * Author: James da Silva, Systems Design and Analysis Group - * Computer Science Department - * University of Maryland at College Park + * Authors: the Amanda Development Team. Its members are listed in a + * file named AUTHORS, in the root directory of this distribution. */ + /* + * $Id: krb4-security.c,v 1.18 2006/07/13 03:22:20 paddy_s Exp $ + * * krb4-security.c - helper functions for kerberos v4 security. */ + +#include "config.h" +#ifdef KRB4_SECURITY + +#include +#include + #include "amanda.h" -#include "krb4-security.h" +#include "dgram.h" +#include "event.h" +#include "packet.h" +#include "queue.h" +#include "security.h" +#include "security-util.h" #include "protocol.h" +#include "stream.h" +#include "version.h" + + +/* + * If you don't have atexit() or on_exit(), you could just consider + * making atexit() empty and clean up your ticket files some other + * way + */ +#ifndef HAVE_ATEXIT +#ifdef HAVE_ON_EXIT +#define atexit(func) on_exit(func, 0) +#else +#define atexit(func) (you must to resolve lack of atexit) +#endif /* HAVE_ON_EXIT */ +#endif /* ! HAVE_ATEXIT */ + +int krb_set_lifetime(int); +int kuserok(AUTH_DAT *, char *); + +/* + * This is the private handle data + */ +struct krb4_handle { + security_handle_t sech; /* MUST be first */ + struct sockaddr_in peer; /* host on other side */ + char hostname[MAX_HOSTNAME_LENGTH+1]; /* human form of above */ + char proto_handle[32]; /* protocol handle for this req */ + int sequence; /* last sequence number we received */ + char inst[INST_SZ]; /* krb4 instance form of above */ + char realm[REALM_SZ]; /* krb4 realm of this host */ + unsigned long cksum; /* cksum of the req packet we sent */ + des_cblock session_key; /* session key */ + + /* + * The rest is used for the async recvpkt/recvpkt_cancel + * interface. + */ + void (*fn)(void *, pkt_t *, security_status_t); + /* func to call when packet recvd */ + void *arg; /* argument to pass function */ + event_handle_t *ev_timeout; /* timeout handle for recv */ + TAILQ_ENTRY(krb4_handle) tq; /* queue handle */ +}; + +/* + * This is the internal security_stream data for krb4. + */ +struct krb4_stream { + security_stream_t secstr; /* MUST be first */ + struct krb4_handle *krb4_handle; /* pointer into above */ + int fd; /* io file descriptor */ + int port; /* local port this is bound to */ + int socket; /* fd for server-side accepts */ + event_handle_t *ev_read; /* read event handle */ + char databuf[MAX_TAPE_BLOCK_BYTES]; /* read buffer */ + int len; /* */ + void (*fn)(void *, void *, ssize_t);/* read event fn */ + void *arg; /* arg for previous */ +}; + +/* + * This is the tcp stream buffer size + */ +#define STREAM_BUFSIZE (MAX_TAPE_BLOCK_BYTES * 2) + +/* + * Interface functions + */ +static void krb4_connect(const char *, char *(*)(char *, void *), + void (*)(void *, security_handle_t *, security_status_t), + void *, void *); +static void krb4_accept(const struct security_driver *, int, int, void (*)(security_handle_t *, pkt_t *)); +static void krb4_close(void *); +static int krb4_sendpkt(void *, pkt_t *); +static void krb4_recvpkt(void *, void (*)(void *, pkt_t *, security_status_t), + void *, int); +static void krb4_recvpkt_cancel(void *); +static void * krb4_stream_server(void *); +static int krb4_stream_accept(void *); +static void * krb4_stream_client(void *, int); +static void krb4_stream_close(void *); +static int krb4_stream_auth(void *); +static int krb4_stream_id(void *); +static int krb4_stream_write(void *, const void *, size_t); +static void krb4_stream_read(void *, void (*)(void *, void *, int), void *); +static int krb4_stream_read_sync(void *, void **); +static void krb4_stream_read_cancel(void *); + + +/* + * This is our interface to the outside world. + */ +const security_driver_t krb4_security_driver = { + "KRB4", + krb4_connect, + krb4_accept, + krb4_close, + krb4_sendpkt, + krb4_recvpkt, + krb4_recvpkt_cancel, + krb4_stream_server, + krb4_stream_accept, + krb4_stream_client, + krb4_stream_close, + krb4_stream_auth, + krb4_stream_id, + krb4_stream_write, + krb4_stream_read, + krb4_stream_read_sync, + krb4_stream_read_cancel, + sec_close_connection_none, +}; + +/* + * Cache the local hostname + */ +static char hostname[MAX_HOSTNAME_LENGTH+1]; + +/* + * This is the dgram_t that we use to send and recv protocol packets + * over the net. There is only one per process, so it lives globally + * here. + */ +static dgram_t netfd; + +/* + * This is a queue of outstanding async requests + */ +static struct { + TAILQ_HEAD(, krb4_handle) tailq; + int qlength; +} handleq = { + TAILQ_HEAD_INITIALIZER(handleq.tailq), 0 +}; + +/* + * Macros to add or remove krb4_handles from the above queue + */ +#define handleq_add(kh) do { \ + assert(handleq.qlength == 0 ? TAILQ_FIRST(&handleq.tailq) == NULL : 1); \ + TAILQ_INSERT_TAIL(&handleq.tailq, kh, tq); \ + handleq.qlength++; \ +} while (0) + +#define handleq_remove(kh) do { \ + assert(handleq.qlength > 0); \ + assert(TAILQ_FIRST(&handleq.tailq) != NULL); \ + TAILQ_REMOVE(&handleq.tailq, kh, tq); \ + handleq.qlength--; \ + assert((handleq.qlength == 0) ^ (TAILQ_FIRST(&handleq.tailq) != NULL)); \ +} while (0) + +#define handleq_first() TAILQ_FIRST(&handleq.tailq) +#define handleq_next(kh) TAILQ_NEXT(kh, tq) + + +/* + * This is the event manager's handle for our netfd + */ +static event_handle_t *ev_netfd; + +/* + * This is a function that should be called if a new security_handle_t is + * created. If NULL, no new handles are created. + * It is passed the new handle and the received pkt + */ +static void (*accept_fn)(security_handle_t *, pkt_t *); + + +/* + * This is a structure used in encoding the cksum in a mutual-auth + * transaction. The checksum is placed in here first before encryption + * because encryption requires at least 8 bytes of data, and an unsigned + * long on most machines (32 bit ones) is 4 bytes. + */ +union mutual { + char pad[8]; + long cksum; +}; + +/* + * Private functions + */ +static unsigned long krb4_cksum(const char *); +static void krb4_getinst(const char *, char *, size_t); +static void host2key(const char *, const char *, des_cblock *); +static void init(void); +static void inithandle(struct krb4_handle *, struct hostent *, int, + const char *); +static void get_tgt(void); +static void killtickets(void); +static void recvpkt_callback(void *); +static void recvpkt_timeout(void *); +static int recv_security_ok(struct krb4_handle *, pkt_t *); +static void stream_read_callback(void *); +static void stream_read_sync_callback(void *); +static int net_write(int, const void *, size_t); +static int net_read(int, void *, size_t, int); + +static int add_ticket(struct krb4_handle *, const pkt_t *, dgram_t *); +static void add_mutual_auth(struct krb4_handle *, dgram_t *); +static int check_ticket(struct krb4_handle *, const pkt_t *, + const char *, unsigned long); +static int check_mutual_auth(struct krb4_handle *, const char *); + +static const char *pkthdr2str(const struct krb4_handle *, const pkt_t *); +static int str2pkthdr(const char *, pkt_t *, char *, size_t, int *); + +static const char *bin2astr(const unsigned char *, int); +static void astr2bin(const unsigned char *, unsigned char *, int *); + +static void encrypt_data(void *, size_t, des_cblock *); +static void decrypt_data(void *, size_t, des_cblock *); #define HOSTNAME_INSTANCE inst static char *ticketfilename = NULL; -int krb4_auth = 0; -int kencrypt = 0; -des_cblock session_key; -uint32_t auth_cksum; /* was 'long' on 32-bit platforms */ - -void krb4_killtickets(void) +static void +killtickets(void) { - if(ticketfilename != NULL) + if (ticketfilename != NULL) unlink(ticketfilename); amfree(ticketfilename); } -void kerberos_service_init() +/* + * Setup some things about krb4. This should only be called once. + */ +static void +init(void) { - int rc; - char hostname[MAX_HOSTNAME_LENGTH+1], inst[256], realm[256]; -#if defined(HAVE_PUTENV) - char *tkt_env = NULL; -#endif - char uid_str[NUM_STR_SIZE]; - char pid_str[NUM_STR_SIZE]; + char tktfile[256]; + int port; + static int beenhere = 0; - gethostname(hostname, sizeof(hostname)-1); - hostname[sizeof(hostname)-1] = '\0'; + if (beenhere) + return; + beenhere = 1; - if(ticketfilename == NULL) - atexit(krb4_killtickets); + gethostname(hostname, SIZEOF(hostname) - 1); + hostname[SIZEOF(hostname) - 1] = '\0'; - host2krbname(hostname, inst, realm); + if (atexit(killtickets) < 0) + error("could not setup krb4 exit handler: %s", strerror(errno)); /* * [XXX] It could be argued that if KRBTKFILE is set outside of amanda, @@ -71,589 +297,1503 @@ void kerberos_service_init() * This file also needs to be removed so that no extra tickets are * hanging around. */ - ap_snprintf(uid_str, sizeof(uid_str), "%ld", (long)getuid()); - ap_snprintf(pid_str, sizeof(pid_str), "%ld", (long)getpid()); - ticketfilename = newvstralloc(ticketfilename, - "/tmp/tkt", - uid_str, "-", pid_str, - ".amanda", - NULL); + snprintf(tktfile, SIZEOF(tktfile), "/tmp/tkt%ld-%ld.amanda", + (long)getuid(), (long)getpid()); + ticketfilename = stralloc(tktfile); + unlink(ticketfilename); krb_set_tkt_string(ticketfilename); #if defined(HAVE_PUTENV) - tkt_env = stralloc2("KRBTKFILE=", ticketfilename); - putenv(tkt_env); - amfree(tkt_env); + { + char *tkt_env = stralloc2("KRBTKFILE=", ticketfilename); + putenv(tkt_env); + } #else - setenv("KRBTKFILE",ticketfilename,1); + setenv("KRBTKFILE", ticketfile, 1); #endif + dgram_zero(&netfd); + dgram_bind(&netfd, &port); +} + +/* + * Get a ticket granting ticket and stuff it in the cache + */ +static void +get_tgt(void) +{ + char realm[REALM_SZ]; + int rc; + + strncpy(realm, krb_realmofhost(hostname), SIZEOF(realm) - 1); + realm[SIZEOF(realm) - 1] = '\0'; + rc = krb_get_svc_in_tkt(SERVER_HOST_PRINCIPLE, SERVER_HOST_INSTANCE, - realm, "krbtgt", realm, TICKET_LIFETIME, - SERVER_HOST_KEY_FILE); - if(rc) error("could not get krbtgt for %s.%s@%s from %s: %s", - SERVER_HOST_PRINCIPLE, SERVER_HOST_INSTANCE, realm, - SERVER_HOST_KEY_FILE, krb_err_txt[rc]); + realm, "krbtgt", realm, TICKET_LIFETIME, SERVER_HOST_KEY_FILE); + + if (rc != 0) { + error("could not get krbtgt for %s.%s@%s from %s: %s", + SERVER_HOST_PRINCIPLE, SERVER_HOST_INSTANCE, realm, + SERVER_HOST_KEY_FILE, krb_err_txt[rc]); + } krb_set_lifetime(TICKET_LIFETIME); } -uint32_t kerberos_cksum(str) -char *str; +/* + * krb4 version of a security handle allocator. Logically sets + * up a network "connection". + */ +static void +krb4_connect( + const char *hostname, + char * (*conf_fn)(char *, void *), + void (*fn)(void *, security_handle_t *, security_status_t), + void * arg, + void * datap) { - des_cblock seed; + struct krb4_handle *kh; + char handle[32]; + struct servent *se; + struct hostent *he; + int port; - memset(seed, 0, sizeof(seed)); - return quad_cksum(str, NULL, strlen(str), 1, seed); -} + assert(hostname != NULL); -struct hostent *host2krbname(alias, inst, realm) -char *alias, *inst, *realm; -{ - struct hostent *hp; - char *s, *d, *krb_realmofhost(); - char saved_hostname[1024]; + /* + * Make sure we're initted + */ + init(); - if((hp = gethostbyname(alias)) == 0) return 0; + kh = alloc(SIZEOF(*kh)); + security_handleinit(&kh->sech, &krb4_security_driver); - /* get inst name: like krb_get_phost, but avoid multiple gethostbyname */ + if ((he = gethostbyname(hostname)) == NULL) { + security_seterror(&kh->sech, + "%s: could not resolve hostname", hostname); + (*fn)(arg, &kh->sech, S_ERROR); + return; + } + if ((se = getservbyname(KAMANDA_SERVICE_NAME, "udp")) == NULL) + port = (int)htons(KAMANDA_SERVICE_DEFAULT); + else + port = se->s_port; + snprintf(handle, SIZEOF(handle), "%ld", (long)time(NULL)); + inithandle(kh, he, (int)port, handle); + (*fn)(arg, &kh->sech, S_OK); +} + +/* + * Setup to handle new incoming connections + */ +static void +krb4_accept( + const struct security_driver *driver, + int in, + int out, + void (*fn)(security_handle_t *, pkt_t *)) +{ - for(s = hp->h_name, d = inst; *s && *s != '.'; s++, d++) - *d = isupper(*s)? tolower(*s) : *s; - *d = '\0'; + /* + * Make sure we're initted + */ + init(); /* - * It isn't safe to pass hp->h_name to krb_realmofhost, since - * it might use gethostbyname internally. + * We assume that in and out both point to the same socket */ - bzero(saved_hostname, sizeof(saved_hostname)); - strncpy(saved_hostname, hp->h_name, sizeof(saved_hostname)-1); + dgram_socket(&netfd, in); - /* get realm name: krb_realmofhost always returns *something* */ - strcpy(realm, krb_realmofhost(saved_hostname)); + /* + * Assign the function and return. When they call recvpkt later, + * the recvpkt callback will call this function when it discovers + * new incoming connections + */ + accept_fn = fn; - return hp; + if (ev_netfd == NULL) + ev_netfd = event_register((event_id_t)netfd.socket, EV_READFD, + recvpkt_callback, NULL); } -void encrypt_data(data, length, key) -void *data; -int length; -des_cblock key; +/* + * Given a hostname and a port, setup a krb4_handle + */ +static void +inithandle( + struct krb4_handle *kh, + struct hostent * he, + int port, + const char * handle) { - des_key_schedule sched; - des_key_sched(key, sched); - des_pcbc_encrypt(data, data, length, sched, key, DES_ENCRYPT); -} + /* + * Get the instance and realm for this host + * (krb_realmofhost always returns something) + */ + krb4_getinst(he->h_name, kh->inst, SIZEOF(kh->inst)); + strncpy(kh->realm, krb_realmofhost(he->h_name), SIZEOF(kh->realm) - 1); + kh->realm[SIZEOF(kh->realm) - 1] = '\0'; + /* + * Save a copy of the hostname + */ + strncpy(kh->hostname, he->h_name, SIZEOF(kh->hostname) - 1); + kh->hostname[SIZEOF(kh->hostname) - 1] = '\0'; -void decrypt_data(data, length, key) -void *data; -int length; -des_cblock key; -{ - des_key_schedule sched; + /* + * We have no checksum or session key at this point + */ + kh->cksum = 0; + memset(kh->session_key, 0, SIZEOF(kh->session_key)); - des_key_sched(key, sched); - des_pcbc_encrypt(data, data, length, sched, key, DES_DECRYPT); + /* + * Setup our peer info. We don't do anything with the sequence yet, + * so just leave it at 0. + */ + kh->peer.sin_family = (sa_family_t)AF_INET; + kh->peer.sin_port = (in_port_t)port; + kh->peer.sin_addr = *(struct in_addr *)he->h_addr; + strncpy(kh->proto_handle, handle, SIZEOF(kh->proto_handle) - 1); + kh->proto_handle[SIZEOF(kh->proto_handle) - 1] = '\0'; + kh->sequence = 0; + kh->fn = NULL; + kh->arg = NULL; + kh->ev_timeout = NULL; } - /* - * struct timeval is a host structure, and may not be used in - * protocols, because members are defined as 'long', rather than - * uint32_t. + * frees a handle allocated by the above */ -typedef struct net_tv { - int32_t tv_sec; - int32_t tv_usec; -} net_tv; +static void +krb4_close( + void * inst) +{ + + krb4_recvpkt_cancel(inst); + amfree(inst); +} -int kerberos_handshake(fd, key) -int fd; -des_cblock key; +/* + * Transmit a packet. Add security information first. + */ +static ssize_t +krb4_sendpkt( + void * cookie, + pkt_t * pkt) { - int rc; - struct timeval local; - net_tv localenc, remote, rcvlocal; - struct timezone tz; - char *strerror(); - char *d; - int l, n, s; + struct krb4_handle *kh = cookie; + + assert(kh != NULL); + assert(pkt != NULL); /* - * There are two mutual authentication transactions going at once: - * one in which we prove the to peer that we are the legitimate - * party, and one in which the peer proves to us that that they - * are legitimate. - * - * In addition to protecting against spoofing, this exchange - * ensures that the two peers have the same keys, protecting - * against having data encrypted with one key and decrypted with - * another on the backup tape. + * Initialize this datagram */ + dgram_zero(&netfd); - gettimeofday(&local, &tz); + /* + * Add the header to the packet + */ + dgram_cat(&netfd, pkthdr2str(kh, pkt)); - /* - * Convert time to network order and sizes, encrypt, and send to - * peer as the first step in the peer proving to us that they are - * legitimate. - */ - localenc.tv_sec = (int32_t) local.tv_sec; - localenc.tv_usec = (int32_t) local.tv_usec; - localenc.tv_sec = htonl(localenc.tv_sec); - localenc.tv_usec = htonl(localenc.tv_usec); - assert(sizeof(localenc) == 8); - encrypt_data(&localenc, sizeof localenc, key); - - d = (char *)&localenc; - for(l = 0, n = sizeof(localenc); l < n; l += s) { - if((s = write(fd, d+l, n-l)) < 0) { - error("kerberos_handshake write error: [%s]", strerror(errno)); - } + /* + * Add the security info. This depends on which kind of packet we're + * sending. + */ + switch (pkt->type) { + case P_REQ: + /* + * Requests get sent with a ticket embedded in the header. The + * checksum is generated from the contents of the body. + */ + if (add_ticket(kh, pkt, &netfd) < 0) + return (-1); + break; + case P_REP: + /* + * Replies get sent with a mutual authenticator added. The + * mutual authenticator is the encrypted checksum from the + * ticket + 1 + */ + add_mutual_auth(kh, &netfd); + break; + case P_ACK: + case P_NAK: + default: + /* + * The other types have no security stuff added for krb4. + * Shamefull. + */ + break; } /* - * Read block from peer and decrypt. This is the first step in us - * proving to the peer that we are legitimate. + * Add the body, and send it */ - d = (char *)&remote; - assert(sizeof(remote) == 8); - for(l = 0, n = sizeof(remote); l < n; l += s) { - if((s = read(fd, d+l, n-l)) < 0) { - error("kerberos_handshake read error: [%s]", strerror(errno)); - } - } - if(l != n) { - error("kerberos_handshake read error: [short read]"); + dgram_cat(&netfd, pkt->body); + if (dgram_send_addr(kh->peer, &netfd) != 0) { + security_seterror(&kh->sech, + "send %s to %s failed: %s", pkt_type2str(pkt->type), + kh->hostname, strerror(errno)); + return (-1); } + return (0); +} - decrypt_data(&remote, sizeof remote, key); +/* + * Set up to receive a packet asyncronously, and call back when + * it has been read. + */ +static void +krb4_recvpkt( + void * cookie, + void (*fn)(void *, pkt_t *, security_status_t), + void * arg, + int timeout) +{ + struct krb4_handle *kh = cookie; - /* XXX do timestamp checking here */ + assert(netfd.socket >= 0); + assert(kh != NULL); /* - * Add 1.000001 seconds to the peer's timestamp, leaving it in - * network order, re-encrypt and send back. + * We register one event handler for our network fd which takes + * care of all of our async requests. When all async requests + * have either been satisfied or cancelled, we unregister our + * network event handler. */ - remote.tv_sec = ntohl(remote.tv_sec); - remote.tv_usec = ntohl(remote.tv_usec); - remote.tv_sec += 1; - remote.tv_usec += 1; - remote.tv_sec = htonl(remote.tv_sec); - remote.tv_usec = htonl(remote.tv_usec); - - encrypt_data(&remote, sizeof remote, key); - - d = (char *)&remote; - for(l = 0, n = sizeof(remote); l < n; l += s) { - if((s = write(fd, d+l, n-l)) < 0) { - error("kerberos_handshake write2 error: [%s]", strerror(errno)); - } + if (ev_netfd == NULL) { + assert(handleq.qlength == 0); + ev_netfd = event_register((event_id_t)netfd.socket, EV_READFD, + recvpkt_callback, NULL); } /* - * Read the peers reply, decrypt, convert to host order, and - * verify that the peer was able to add 1.000001 seconds, thus - * showing that it knows the DES key. + * Multiple recvpkt calls override previous ones + * If kh->fn is NULL then it is not in the queue. */ - d = (char *)&rcvlocal; - for(l = 0, n = sizeof(rcvlocal); l < n; l += s) { - if((s = read(fd, d+l, n-l)) < 0) { - error("kerberos_handshake read2 error: [%s]", strerror(errno)); - } + if (kh->fn == NULL) + handleq_add(kh); + if (kh->ev_timeout != NULL) + event_release(kh->ev_timeout); + if (timeout < 0) + kh->ev_timeout = NULL; + else + kh->ev_timeout = event_register((event_id_t)timeout, EV_TIME, + recvpkt_timeout, kh); + kh->fn = fn; + kh->arg = arg; +} + +/* + * Remove a async receive request from the queue + * If it is the last one to be removed, then remove the event handler + * for our network fd. + */ +static void +krb4_recvpkt_cancel( + void * cookie) +{ + struct krb4_handle *kh = cookie; + + assert(kh != NULL); + + if (kh->fn != NULL) { + handleq_remove(kh); + kh->fn = NULL; + kh->arg = NULL; } - if(l != n) { - error("kerberos_handshake read2 error: [short read]"); + if (kh->ev_timeout != NULL) + event_release(kh->ev_timeout); + kh->ev_timeout = NULL; + + if (handleq.qlength == 0 && accept_fn == NULL && + ev_netfd != NULL) { + event_release(ev_netfd); + ev_netfd = NULL; } +} - decrypt_data(&rcvlocal, sizeof rcvlocal, key); +/* + * Create the server end of a stream. For krb4, this means setup a tcp + * socket for receiving a connection. + */ +static void * +krb4_stream_server( + void * h) +{ + struct krb4_handle *kh = h; + struct krb4_stream *ks; + + assert(kh != NULL); + + ks = alloc(SIZEOF(*ks)); + security_streaminit(&ks->secstr, &krb4_security_driver); + ks->socket = stream_server(&ks->port, STREAM_BUFSIZE, STREAM_BUFSIZE, 1); + if (ks->socket < 0) { + security_seterror(&kh->sech, + "can't create server stream: %s", strerror(errno)); + amfree(ks); + return (NULL); + } + ks->fd = -1; + ks->krb4_handle = kh; + ks->ev_read = NULL; + return (ks); +} - rcvlocal.tv_sec = ntohl(rcvlocal.tv_sec); - rcvlocal.tv_usec = ntohl(rcvlocal.tv_usec); +/* + * Accept an incoming connection on a stream_server socket + */ +static int +krb4_stream_accept( + void * s) +{ + struct krb4_stream *ks = s; + struct krb4_handle *kh; + + assert(ks != NULL); + kh = ks->krb4_handle; + assert(kh != NULL); + assert(ks->socket >= 0); + assert(ks->fd == -1); + + ks->fd = stream_accept(ks->socket, 30, STREAM_BUFSIZE, STREAM_BUFSIZE); + if (ks->fd < 0) { + security_stream_seterror(&ks->secstr, + "can't accept new stream connection: %s", strerror(errno)); + return (-1); + } + return (0); +} - dbprintf(("handshake: %d %d %d %d\n", - local.tv_sec, local.tv_usec, - rcvlocal.tv_sec, rcvlocal.tv_usec)); +/* + * Return a connected stream. + */ +static void * +krb4_stream_client( + void * h, + int id) +{ + struct krb4_handle *kh = h; + struct krb4_stream *ks; + + assert(kh != NULL); + + ks = alloc(SIZEOF(*ks)); + security_streaminit(&ks->secstr, &krb4_security_driver); + ks->fd = stream_client(kh->hostname, id, STREAM_BUFSIZE, STREAM_BUFSIZE, + &ks->port, 0); + if (ks->fd < 0) { + security_seterror(&kh->sech, + "can't connect stream to %s port %d: %s", kh->hostname, id, + strerror(errno)); + amfree(ks); + return (NULL); + } - return (rcvlocal.tv_sec == (int32_t) (local.tv_sec + 1)) && - (rcvlocal.tv_usec == (int32_t) (local.tv_usec + 1)); + ks->socket = -1; /* we're a client */ + ks->krb4_handle = kh; + ks->ev_read = NULL; + return (ks); } -des_cblock *host2key(hostname) -char *hostname; +/* + * Close and unallocate resources for a stream. + */ +static void +krb4_stream_close( + void * s) { - static des_cblock key; - char inst[256], realm[256]; - CREDENTIALS cred; + struct krb4_stream *ks = s; - if(host2krbname(hostname, inst, realm)) - krb_get_cred(CLIENT_HOST_PRINCIPLE, CLIENT_HOST_INSTANCE, realm,&cred); + assert(ks != NULL); - memcpy(key, cred.session, sizeof key); - return &key; + if (ks->fd != -1) + aclose(ks->fd); + if (ks->socket != -1) + aclose(ks->socket); + krb4_stream_read_cancel(ks); + amfree(ks); } -int check_mutual_authenticator(key, pkt, p) -des_cblock *key; -pkt_t *pkt; -proto_t *p; +/* + * Authenticate a stream + * + * XXX this whole thing assumes the size of struct timeval is consistent, + * which is may not be! We need to extract the network byte order data + * into byte arrays and send those. + */ +static int +krb4_stream_auth( + void * s) { - char *astr = NULL; - union { - char pad[8]; - uint32_t i; - } mutual; - int len; - char *s, *fp; - int ch; + struct krb4_stream *ks = s; + struct krb4_handle *kh; + int fd; + struct timeval local, enc; + struct timezone tz; - if(pkt->security == NULL) { - fprintf(stderr," pkt->security is NULL\n"); - return 0; - } + assert(ks != NULL); + assert(ks->fd >= 0); + + fd = ks->fd; + kh = ks->krb4_handle; + + /* make sure we're open */ + assert(fd >= 0); + + /* make sure our timeval is what we're expecting, see above */ + assert(SIZEOF(struct timeval) == 8); - s = pkt->security; - ch = *s++; + /* + * Get the current time, put it in network byte order, encrypt it + * and present it to the other side. + */ + gettimeofday(&local, &tz); + enc.tv_sec = (long)htonl((uint32_t)local.tv_sec); + enc.tv_usec = (long)htonl((uint32_t)local.tv_usec); + encrypt_data(&enc, SIZEOF(enc), &kh->session_key); + if (net_write(fd, &enc, SIZEOF(enc)) < 0) { + security_stream_seterror(&ks->secstr, + "krb4 stream handshake write error: %s", strerror(errno)); + return (-1); + } - skip_whitespace(s, ch); - if(ch == '\0') { - fprintf(stderr,"pkt->security is actually %s\n", pkt->security); - return 0; + /* + * Read back the other side's presentation. Increment the seconds + * and useconds by one. Reencrypt, and present to the other side. + * Timeout in 10 seconds. + */ + if (net_read(fd, &enc, SIZEOF(enc), 60) < 0) { + security_stream_seterror(&ks->secstr, + "krb4 stream handshake read error: %s", strerror(errno)); + return (-1); } - fp = s-1; - skip_non_whitespace(s, ch); - s[-1] = '\0'; - if(strcmp(fp, "MUTUAL-AUTH") != 0) { - s[-1] = ch; - fprintf(stderr,"pkt->security is actually %s\n", pkt->security); - return 0; + decrypt_data(&enc, SIZEOF(enc), &kh->session_key); + /* XXX do timestamp checking here */ + enc.tv_sec = (long)htonl(ntohl((uint32_t)enc.tv_sec) + 1); + enc.tv_usec =(long)htonl(ntohl((uint32_t)enc.tv_usec) + 1); + encrypt_data(&enc, SIZEOF(enc), &kh->session_key); + + if (net_write(fd, &enc, SIZEOF(enc)) < 0) { + security_stream_seterror(&ks->secstr, + "krb4 stream handshake write error: %s", strerror(errno)); + return (-1); } - s[-1] = ch; - skip_whitespace(s, ch); - if(ch == '\0') { - fprintf(stderr,"pkt->security is actually %s\n", pkt->security); - return 0; + /* + * Read back the other side's processing of our data. + * If they incremented it properly, then succeed. + * Timeout in 10 seconds. + */ + if (net_read(fd, &enc, SIZEOF(enc), 60) < 0) { + security_stream_seterror(&ks->secstr, + "krb4 stream handshake read error: %s", strerror(errno)); + return (-1); } - astr = s-1; - while(ch && ch != '\n') ch = *s++; - s[-1] = '\0'; + decrypt_data(&enc, SIZEOF(enc), &kh->session_key); + if ((ntohl((uint32_t)enc.tv_sec) == (uint32_t)(local.tv_sec + 1)) && + (ntohl((uint32_t)enc.tv_usec) == (uint32_t)(local.tv_usec + 1))) + return (0); + + security_stream_seterror(&ks->secstr, + "krb4 handshake failed: sent %ld,%ld - recv %ld,%ld", + (long)(local.tv_sec + 1), (long)(local.tv_usec + 1), + (long)ntohl((uint32_t)enc.tv_sec), + (long)ntohl((uint32_t)enc.tv_usec)); + return (-1); +} - /* XXX - goddamn it this is a worm-hole */ - astr2bin(astr, (unsigned char *)mutual.pad, &len); +/* + * Returns the stream id for this stream. This is just the local + * port. + */ +static int +krb4_stream_id( + void * s) +{ + struct krb4_stream *ks = s; - s[-1] = ch; + assert(ks != NULL); - decrypt_data(&mutual, len, *key); - mutual.i = ntohl(mutual.i); - return mutual.i == p->auth_cksum + 1; + return (ks->port); } -char *get_krb_security(str, host_inst, realm, cksum) -char *str; -char *host_inst, *realm; -uint32_t *cksum; +/* + * Write a chunk of data to a stream. Blocks until completion. + */ +static int +krb4_stream_write( + void * s, + const void *buf, + size_t size) { - KTEXT_ST ticket; - int rc; - char inst[INST_SZ]; + struct krb4_stream *ks = s; + struct krb4_handle *kh = ks->krb4_handle; - *cksum = kerberos_cksum(str); + assert(ks != NULL); + assert(kh != NULL); -#if CLIENT_HOST_INSTANCE == HOSTNAME_INSTANCE -#undef HOSTNAME_INSTANCE -#define HOSTNAME_INSTANCE host_inst -#endif + if (net_write(ks->fd, buf, size) < 0) { + security_stream_seterror(&ks->secstr, + "write error on stream %d: %s", ks->fd, strerror(errno)); + return (-1); + } + return (0); +} + +/* + * Submit a request to read some data. Calls back with the given + * function and arg when completed. + */ +static void +krb4_stream_read( + void * s, + void (*fn)(void *, void *, ssize_t), + void * arg) +{ + struct krb4_stream *ks = s; + + assert(ks != NULL); /* - * the instance must be in writable memory of size INST_SZ - * krb_mk_req might change it + * Only one read request can be active per stream. */ - strncpy(inst, CLIENT_HOST_INSTANCE, sizeof(inst) - 1); - inst[sizeof(inst) - 1] = '\0'; - if((rc = krb_mk_req(&ticket, CLIENT_HOST_PRINCIPLE, inst, realm, *cksum))) { - if(rc == NO_TKT_FIL) { - /* It's been kdestroyed. Get a new one and try again */ - kerberos_service_init(); - rc = krb_mk_req(&ticket, CLIENT_HOST_PRINCIPLE, - CLIENT_HOST_INSTANCE, realm, *cksum); - } - if(rc) return NULL; - } - return stralloc2("SECURITY TICKET ", - bin2astr((unsigned char *)ticket.dat, ticket.length)); + if (ks->ev_read != NULL) + event_release(ks->ev_read); + + ks->ev_read = event_register((event_id_t)ks->fd, EV_READFD, + stream_read_callback, ks); + ks->fn = fn; + ks->arg = arg; } +/* + * Write a chunk of data to a stream. Blocks until completion. + */ +static ssize_t +krb4_stream_read_sync( + void * s, + void ** buf) +{ + struct krb4_stream *ks = s; + + (void)buf; /* Quiet unused variable warning */ + assert(ks != NULL); -int krb4_security_ok(addr, str, cksum, errstr) -struct sockaddr_in *addr; -char *str; -uint32_t cksum; -char **errstr; + if (ks->ev_read != NULL) + event_release(ks->ev_read); + + ks->ev_read = event_register((event_id_t)ks->fd, EV_READFD, + stream_read_sync_callback, ks); + event_wait(ks->ev_read); + return((ssize_t)ks->len); +} + +/* + * Callback for krb4_stream_read_sync + */ +static void +stream_read_sync_callback( + void * arg) { - KTEXT_ST ticket; - AUTH_DAT auth; - char *ticket_str, *user, inst[INST_SZ], hname[256]; - struct passwd *pwptr; - int myuid, rc; - char *s; - int ch; + struct krb4_stream *ks = arg; + ssize_t n; - /* extract the ticket string from the message */ + assert(ks != NULL); + assert(ks->fd != -1); - s = str; - ch = *s++; + /* + * Remove the event first, and then call the callback. + * We remove it first because we don't want to get in their + * way if they reschedule it. + */ + krb4_stream_read_cancel(ks); + n = read(ks->fd, ks->databuf, sizeof(ks->databuf)); + if (n < 0) + security_stream_seterror(&ks->secstr, + strerror(errno)); + ks->len = (int)n; +} - skip_whitespace(s, ch); - if(ch == '\0') { - *errstr = newstralloc(*errstr, "[bad krb4 security line]"); - return 0; +/* + * Cancel a previous stream read request. It's ok if we didn't have a read + * scheduled. + */ +static void +krb4_stream_read_cancel( + void * s) +{ + struct krb4_stream *ks = s; + + assert(ks != NULL); + + if (ks->ev_read != NULL) { + event_release(ks->ev_read); + ks->ev_read = NULL; } -#define sc "TICKET" - if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) { - *errstr = newstralloc(*errstr, "[bad krb4 security line]"); - return 0; +} + +/* + * Callback for krb4_stream_read + */ +static void +stream_read_callback( + void * arg) +{ + struct krb4_stream *ks = arg; + ssize_t n; + + assert(ks != NULL); + assert(ks->fd != -1); + + /* + * Remove the event first, and then call the callback. + * We remove it first because we don't want to get in their + * way if they reschedule it. + */ + krb4_stream_read_cancel(ks); + n = read(ks->fd, ks->databuf, SIZEOF(ks->databuf)); + if (n < 0) + security_stream_seterror(&ks->secstr, + strerror(errno)); + (*ks->fn)(ks->arg, ks->databuf, n); +} + +/* + * The callback for recvpkt() for the event handler + * Determines if this packet is for this security handle, + * and does the real callback if so. + */ +static void +recvpkt_callback( + void * cookie) +{ + char handle[32]; + struct sockaddr_in peer; + pkt_t pkt; + int sequence; + struct krb4_handle *kh; + struct hostent *he; + void (*fn)(void *, pkt_t *, security_status_t); + void *arg; + + assert(cookie == NULL); + + /* + * Find the handle that this packet is associated with. We + * need to peek at the packet to see what is in it, but we + * want to save the actual reading for later. + */ + dgram_zero(&netfd); + if (dgram_recv(&netfd, 0, &peer) < 0) + return; + if (str2pkthdr(netfd.cur, &pkt, handle, SIZEOF(handle), &sequence) < 0) + return; + + for (kh = handleq_first(); kh != NULL; kh = handleq_next(kh)) { + if (strcmp(kh->proto_handle, handle) == 0 && + memcmp(&kh->peer.sin_addr, &peer.sin_addr, + SIZEOF(peer.sin_addr)) == 0 && + kh->peer.sin_port == peer.sin_port) { + kh->sequence = sequence; + + /* + * We need to cancel the recvpkt request before calling + * the callback because the callback may reschedule us. + */ + fn = kh->fn; + arg = kh->arg; + krb4_recvpkt_cancel(kh); + if (recv_security_ok(kh, &pkt) < 0) + (*fn)(arg, NULL, S_ERROR); + else + (*fn)(arg, &pkt, S_OK); + return; + } } - s += sizeof(sc)-1; - ch = s[-1]; -#undef sc - skip_whitespace(s, ch); - ticket_str = s - 1; - skip_line(s, ch); - s[-1] = '\0'; + /* + * If we didn't find a handle, then check for a new incoming packet. + * If no accept handler was setup, then just return. + */ + if (accept_fn == NULL) + return; - /* convert to binary ticket */ + he = gethostbyaddr((void *)&peer.sin_addr, SIZEOF(peer.sin_addr), AF_INET); + if (he == NULL) + return; + kh = alloc(SIZEOF(*kh)); + security_handleinit(&kh->sech, &krb4_security_driver); + inithandle(kh, he, (int)peer.sin_port, handle); + + /* + * Check the security of the packet. If it is bad, then pass NULL + * to the accept function instead of a packet. + */ + if (recv_security_ok(kh, &pkt) < 0) + (*accept_fn)(&kh->sech, NULL); + else + (*accept_fn)(&kh->sech, &pkt); +} + +/* + * This is called when a handle times out before receiving a packet. + */ +static void +recvpkt_timeout( + void * cookie) +{ + struct krb4_handle *kh = cookie; + void (*fn)(void *, pkt_t *, security_status_t); + void *arg; - astr2bin(ticket_str, (unsigned char *)ticket.dat, &ticket.length); + assert(kh != NULL); - /* consult kerberos server */ + assert(kh->ev_timeout != NULL); + fn = kh->fn; + arg = kh->arg; + krb4_recvpkt_cancel(kh); + (*fn)(arg, NULL, S_TIMEOUT); +} +/* + * Add a ticket to the message + */ +static int +add_ticket( + struct krb4_handle *kh, + const pkt_t * pkt, + dgram_t * msg) +{ + char inst[INST_SZ]; + KTEXT_ST ticket; + char *security; + int rc; + + kh->cksum = (long)krb4_cksum(pkt->body); #if CLIENT_HOST_INSTANCE == HOSTNAME_INSTANCE - if (gethostname(hname, sizeof(hname)) < 0) { - *errstr = newvstralloc(*errstr, - "[kerberos error: can't get hostname: ", strerror(errno), "}", - NULL); - return 0; - } -#undef HOSTNAME_INSTANCE -#define HOSTNAME_INSTANCE krb_get_phost(hname) + /* + * User requested that all instances be based on the target + * hostname. + */ + strncpy(inst, kh->inst, SIZEOF(inst) - 1); +#else + /* + * User requested a fixed instance. + */ + strncpy(inst, CLIENT_HOST_INSTANCE, SIZEOF(inst) - 1); #endif + inst[SIZEOF(inst) - 1] = '\0'; /* - * the instance must be in writable memory of size INST_SZ - * krb_rd_req might change it. + * Get a ticket with the user-defined service and instance, + * and using the checksum of the body of the request packet. */ - strncpy(inst, CLIENT_HOST_INSTANCE, sizeof(inst) - 1); - inst[sizeof(inst) - 1] = '\0'; - rc = krb_rd_req(&ticket, CLIENT_HOST_PRINCIPLE, inst, - addr->sin_addr.s_addr, &auth, CLIENT_HOST_KEY_FILE); - if(rc) { - *errstr = newvstralloc(*errstr, - "[kerberos error: ", krb_err_txt[rc], "]", - NULL); - return 0; + rc = krb_mk_req(&ticket, CLIENT_HOST_PRINCIPLE, inst, kh->realm, + kh->cksum); + if (rc == NO_TKT_FIL) { + /* It's been kdestroyed. Get a new one and try again */ + get_tgt(); + rc = krb_mk_req(&ticket, CLIENT_HOST_PRINCIPLE, inst, kh->realm, + kh->cksum); + } + if (rc != 0) { + security_seterror(&kh->sech, + "krb_mk_req failed: %s (%d)", error_message(rc), rc); + return (-1); } + /* + * We now have the ticket. Put it into the packet, and send + * it. + */ + security = vstralloc("SECURITY TICKET ", + bin2astr(ticket.dat, ticket.length), "\n", NULL); + dgram_cat(msg, security); + amfree(security); + + return (0); +} + +/* + * Add the mutual authenticator. This is the checksum from + * the req, + 1 + */ +static void +add_mutual_auth( + struct krb4_handle *kh, + dgram_t * msg) +{ + union mutual mutual; + char *security; + + assert(kh != NULL); + assert(msg != NULL); + assert(kh->cksum != 0); + assert(kh->session_key[0] != '\0'); + + memset(&mutual, 0, SIZEOF(mutual)); + mutual.cksum = (unsigned long)htonl((uint32_t)kh->cksum + 1); + encrypt_data(&mutual, SIZEOF(mutual), &kh->session_key); + + security = vstralloc("SECURITY MUTUAL-AUTH ", + bin2astr((unsigned char *)mutual.pad, + (int)sizeof(mutual.pad)), "\n", NULL); + dgram_cat(msg, security); + amfree(security); +} + +/* + * Check the security of a received packet. Returns negative on error + * or security violation and otherwise returns 0 and fills in the + * passed packet. + */ +static int +recv_security_ok( + struct krb4_handle *kh, + pkt_t * pkt) +{ + char *tok, *security, *body; + unsigned long cksum; - /* verify checksum */ + assert(kh != NULL); + assert(pkt != NULL); + + /* + * Set this preemptively before we mangle the body. + */ + security_seterror(&kh->sech, + "bad %s SECURITY line from %s: '%s'", pkt_type2str(pkt->type), + kh->hostname, pkt->body); - dbprintf(("msg checksum %d auth checksum %d\n", - cksum, auth.checksum)); - if(cksum != auth.checksum) { - *errstr = newstralloc(*errstr, "[kerberos error: checksum mismatch]"); - dbprintf(("checksum error: exp %d got %d\n", - auth.checksum, cksum)); - return 0; + /* + * The first part of the body should be the security info. Deal with it. + * We only need to do this on a few packet types. + * + * First, parse the SECURITY line in the packet, if it exists. + * Increment the cur pointer past it to the data section after + * parsing is finished. + */ + if (strncmp(pkt->body, "SECURITY", SIZEOF("SECURITY") - 1) == 0) { + tok = strtok(pkt->body, " "); + assert(strcmp(tok, "SECURITY") == 0); + /* security info goes until the newline */ + security = strtok(NULL, "\n"); + body = strtok(NULL, ""); + /* + * If the body is f-ked, then try to recover. + */ + if (body == NULL) { + if (security != NULL) + body = security + strlen(security) + 2; + else + body = pkt->body; + } + } else { + security = NULL; + body = pkt->body; } - /* save key/cksum for mutual auth and dump encryption */ + /* + * Get a checksum of the non-security parts of the body + */ + cksum = krb4_cksum(body); - memcpy(session_key, auth.session, sizeof(session_key)); - auth_cksum = auth.checksum; + /* + * Now deal with the data we did (or didn't) parse above + */ + switch (pkt->type) { + case P_REQ: + /* + * Request packets have a ticket after the security tag + * Get the ticket, make sure the checksum agrees with the + * checksum of the request we sent. + * + * Must look like: SECURITY TICKET [ticket str] + */ + + /* there must be some security info */ + if (security == NULL) + return (-1); + + /* second word must be TICKET */ + if ((tok = strtok(security, " ")) == NULL) + return (-1); + if (strcmp(tok, "TICKET") != 0) { + security_seterror(&kh->sech, + "REQ SECURITY line parse error, expecting TICKET, got %s", tok); + return (-1); + } - /* lookup our local user name */ + /* the third word is the encoded ticket */ + if ((tok = strtok(NULL, "")) == NULL) + return (-1); + if (check_ticket(kh, pkt, tok, cksum) < 0) + return (-1); + + /* we're good to go */ + break; + + case P_REP: + /* + * Reply packets check the mutual authenticator for this host. + * + * Must look like: SECURITY MUTUAL-AUTH [mutual auth str] + */ + if (security == NULL) + return (-1); + if ((tok = strtok(security, " ")) == NULL) + return (-1); + if (strcmp(tok, "MUTUAL-AUTH") != 0) { + security_seterror(&kh->sech, + "REP SECURITY line parse error, expecting MUTUAL-AUTH, got %s", + tok); + return (-1); + } + if ((tok = strtok(NULL, "")) == NULL) + return (-1); + if (check_mutual_auth(kh, tok) < 0) + return (-1); + + case P_ACK: + case P_NAK: + default: + /* + * These packets have no security. They should, but such + * is life. We can't change it without breaking compatibility. + * + * XXX Should we complain if argc > 0? (ie, some security info was + * sent?) + */ + break; + + } -#ifdef FORCE_USERID /* - * if FORCE_USERID is set, then we want to check the uid that we're - * forcing ourselves to. Since we'll need root access to grab at the - * srvtab file, we're actually root, although we'll be changing into - * CLIENT_LOGIN once we're done the kerberos authentication. + * If there is security info at the front of the packet, we need + * to shift the rest of the data up and nuke it. */ - if((pwptr = getpwnam(CLIENT_LOGIN)) == NULL) - error("error [getpwnam(%s) fails]", CLIENT_LOGIN); + if (body != pkt->body) + memmove(pkt->body, body, strlen(body) + 1); + return (0); +} + +/* + * Check the ticket in a REQ packet for authenticity + */ +static int +check_ticket( + struct krb4_handle *kh, + const pkt_t * pkt, + const char * ticket_str, + unsigned long cksum) +{ + char inst[INST_SZ]; + KTEXT_ST ticket; + AUTH_DAT auth; + struct passwd *pwd; + char *user; + int rc; + + assert(kh != NULL); + assert(pkt != NULL); + assert(ticket_str != NULL); + + ticket.length = (int)sizeof(ticket.dat); + astr2bin((unsigned char *)ticket_str, ticket.dat, &ticket.length); + assert(ticket.length > 0); + + /* get a copy of the instance into writable memory */ +#if CLIENT_HOST_INSTANCE == HOSTNAME_INSTANCE + strncpy(inst, krb_get_phost(hostname), SIZEOF(inst) - 1); #else - myuid = getuid(); - if((pwptr = getpwuid(myuid)) == NULL) - error("error [getpwuid(%d) fails]", myuid); + strncpy(inst, CLIENT_HOST_INSTANCE, SIZEOF(inst) - 1); #endif + inst[SIZEOF(inst) - 1] = '\0'; - /* check the .klogin file */ + /* get the checksum out of the ticket */ + rc = krb_rd_req(&ticket, CLIENT_HOST_PRINCIPLE, inst, + kh->peer.sin_addr.s_addr, &auth, CLIENT_HOST_KEY_FILE); + if (rc != 0) { + security_seterror(&kh->sech, + "krb_rd_req failed for %s: %s (%d)", kh->hostname, + error_message(rc), rc); + return (-1); + } + + /* verify and save the checksum and session key */ + if (auth.checksum != cksum) { + security_seterror(&kh->sech, + "krb4 checksum mismatch for %s (remote=%lu, local=%lu)", + kh->hostname, (long)auth.checksum, cksum); + return (-1); + } + kh->cksum = (unsigned long)cksum; + memcpy(kh->session_key, auth.session, SIZEOF(kh->session_key)); /* - * some implementations of kerberos will call one of the getpw() - * routines (getpwuid(), I think), which overwrites the value of - * pwptr->pw_name if the user you want to check disagrees with - * who has the current uid. (as in the case when we're still running - * as root, and we have FORCE_USERID set). + * If FORCE_USERID is set, then we need to specifically + * check the userid we're forcing ourself to. Otherwise, + * just check the login we're currently setuid to. */ - user = stralloc(pwptr->pw_name); +#ifdef FORCE_USERID + if ((pwd = getpwnam(CLIENT_LOGIN)) == NULL) + error("error [getpwnam(%s) fails]", CLIENT_LOGIN); +#else + if ((pwd = getpwuid(getuid())) == NULL) + error("error [getpwuid(%d) fails]", getuid()); +#endif + + /* save the username in case it's overwritten */ + user = stralloc(pwd->pw_name); - if(kuserok(&auth, user)) { - *errstr = newvstralloc(*errstr, - "[", - "access as ", user, " not allowed from ", - auth.pname, ".", auth.pinst, "@", auth.prealm, - "]", NULL); - dbprintf(("kuserok check failed: %s\n", *errstr)); + /* check the klogin file */ + if (kuserok(&auth, user)) { + security_seterror(&kh->sech, + "access as %s not allowed from %s.%s@%s", user, auth.pname, + auth.pinst, auth.prealm); amfree(user); - return 0; + return (-1); } amfree(user); - dbprintf(("krb4 security check passed\n")); - return 1; + /* it's good */ + return (0); } -/* ---------------- */ +/* + * Verify that the packet received is secure by verifying that it has + * the same checksum as our request + 1. + */ +static int +check_mutual_auth( + struct krb4_handle *kh, + const char * mutual_auth_str) +{ + union mutual mutual; + int len; -/* XXX - I'm getting astrs with the high bit set in the debug output!?? */ + assert(kh != NULL); + assert(mutual_auth_str != NULL); + assert(kh->inst[0] != '\0'); + /* we had better have a checksum from a request we sent */ + assert(kh->cksum != 0); + + /* convert the encoded string into binary data */ + len = (int)sizeof(mutual); + astr2bin((unsigned char *)mutual_auth_str, (unsigned char *)&mutual, &len); + + /* unencrypt the string using the key in the ticket file */ + host2key(kh->hostname, kh->inst, &kh->session_key); + decrypt_data(&mutual, (size_t)len, &kh->session_key); + mutual.cksum = (unsigned long)ntohl((uint32_t)mutual.cksum); + + /* the data must be the same as our request cksum + 1 */ + if (mutual.cksum != (kh->cksum + 1)) { + security_seterror(&kh->sech, + "krb4 checksum mismatch from %s (remote=%lu, local=%lu)", + kh->hostname, mutual.cksum, kh->cksum + 1); + return (-1); + } -#define hex_digit(d) ("0123456789ABCDEF"[(d)]) -#define unhex_digit(h) (((h) - '0') > 9? ((h) - 'A' + 10) : ((h) - '0')) + return (0); +} -char *bin2astr(buf, len) -unsigned char *buf; -int len; +/* + * Convert a pkt_t into a header string for our packet + */ +static const char * +pkthdr2str( + const struct krb4_handle * kh, + const pkt_t * pkt) { - char *str, *q; - unsigned char *p; - int slen, i, needs_quote; + static char retbuf[256]; - /* first pass, calculate string len */ + assert(kh != NULL); + assert(pkt != NULL); - slen = needs_quote = 0; p = buf; - if(*p == '"') needs_quote = 1; /* special case */ - for(i=0;itype), + kh->proto_handle, kh->sequence); + + /* check for truncation. If only we had asprintf()... */ + assert(retbuf[strlen(retbuf) - 1] == '\n'); + + return (retbuf); +} + +/* + * Parses out the header line in 'str' into the pkt and handle + * Returns negative on parse error. + */ +static int +str2pkthdr( + const char *origstr, + pkt_t * pkt, + char * handle, + size_t handlesize, + int * sequence) +{ + char *str; + const char *tok; + + assert(origstr != NULL); + assert(pkt != NULL); + + str = stralloc(origstr); + + /* "Amanda %d.%d HANDLE %s SEQ %d\n" */ + + /* Read in "Amanda" */ + if ((tok = strtok(str, " ")) == NULL || strcmp(tok, "Amanda") != 0) + goto parse_error; + + /* nothing is done with the major/minor numbers currently */ + if ((tok = strtok(NULL, " ")) == NULL || strchr(tok, '.') == NULL) + goto parse_error; + + /* Read in the packet type */ + if ((tok = strtok(NULL, " ")) == NULL) + goto parse_error; + pkt_init(pkt, pkt_str2type(tok), ""); + if (pkt->type == (pktype_t)-1) + goto parse_error; + + /* Read in "HANDLE" */ + if ((tok = strtok(NULL, " ")) == NULL || strcmp(tok, "HANDLE") != 0) + goto parse_error; + + /* parse the handle */ + if ((tok = strtok(NULL, " ")) == NULL) + goto parse_error; + strncpy(handle, tok, handlesize - 1); + handle[handlesize - 1] = '\0'; + + /* Read in "SEQ" */ + if ((tok = strtok(NULL, " ")) == NULL || strcmp(tok, "SEQ") != 0) + goto parse_error; + + /* parse the sequence number */ + if ((tok = strtok(NULL, "\n")) == NULL) + goto parse_error; + *sequence = atoi(tok); + + /* Save the body, if any */ + if ((tok = strtok(NULL, "")) != NULL) + pkt_cat(pkt, tok); + + amfree(str); + return (0); + +parse_error: +#if 0 /* XXX we have no way of passing this back up */ + security_seterror(&kh->sech, + "parse error in packet header : '%s'", origstr); +#endif + amfree(str); + return (-1); +} + +static void +host2key( + const char *hostname, + const char *inst, + des_cblock *key) +{ + char realm[256]; + CREDENTIALS cred; + + strncpy(realm, krb_realmofhost((char *)hostname), SIZEOF(realm) - 1); + realm[SIZEOF(realm) - 1] = '\0'; +#if CLIENT_HOST_INSTANCE != HOSTNAME_INSTANCE + inst = CLIENT_HOST_INSTANCE +#endif + krb_get_cred(CLIENT_HOST_PRINCIPLE, (char *)inst, realm, &cred); + memcpy(key, cred.session, SIZEOF(des_cblock)); +} + + +/* + * Convert a chunk of data into a string. + */ +static const char * +bin2astr( + const unsigned char *buf, + int len) +{ + static const char tohex[] = "0123456789ABCDEF"; + static char *str = NULL; + char *q; + const unsigned char *p; + size_t slen; + int i; - /* 2nd pass, allocate string and fill it in */ + /* + * calculate output string len + * We quote everything, so each input byte == 3 output chars, plus + * two more for quotes + */ + slen = ((size_t)len * 3) + 2; - str = (char *)alloc(slen+1); + /* allocate string and fill it in */ + if (str != NULL) + amfree(str); + str = alloc(slen + 1); p = buf; q = str; - if(needs_quote) *q++ = '"'; - for(i=0;i> 4) & 0xF); - *q++ = hex_digit(*p & 0xF); - p++; - } + *q++ = '"'; + for (i = 0; i < len; i++) { + *q++ = '$'; + *q++ = tohex[(*p >> 4) & 0xF]; + *q++ = tohex[*p & 0xF]; + p++; } - if(needs_quote) *q++ = '"'; + *q++ = '"'; *q = '\0'; - if(q-str != slen) - printf("bin2str: hmmm.... calculated %d got %d\n", - slen, q-str); - return str; + + /* make sure we didn't overrun our allocated buffer */ + assert((size_t)(q - str) == slen); + + return (str); } -void astr2bin(astr, buf, lenp) -char *astr; -unsigned char *buf; -int *lenp; +/* + * Convert an encoded string into a block of data bytes + */ +static void +astr2bin( + const unsigned char *astr, + unsigned char * buf, + int * lenp) { - char *p; + const unsigned char *p; unsigned char *q; +#define fromhex(h) (isdigit((int)h) ? (h) - '0' : (h) - 'A' + 10) - p = astr; q = buf; + /* + * Skip leading quote, if any + */ + if (*astr == '"') + astr++; - if(*p != '"') { - /* strcpy, but without the null */ - while(*p) *q++ = *p++; - *lenp = q-buf; - return; + /* + * Run through the string. Anything starting with a $ is a three + * char representation of this byte. Everything else is literal. + */ + for (p = astr, q = buf; *p != '"' && *p != '\0'; ) { + if (*p != '$') { + *q++ = *p++; + } else { + *q++ = (fromhex(p[1]) << 4) + fromhex(p[2]); + p += 3; + } + if ((int)(q - buf) >= *lenp) + break; } + *lenp = q - buf; +} - p++; - while(*p != '"') { - if(*p != '$') - *q++ = *p++; - else { - *q++ = (unhex_digit(p[1]) << 4) + unhex_digit(p[2]); - p += 3; +static unsigned long +krb4_cksum( + const char *str) +{ + des_cblock seed; + + memset(seed, 0, SIZEOF(seed)); + /* + * The first arg is an unsigned char * in some krb4 implementations, + * and in others, it's a des_cblock *. Just make it void here + * to shut them all up. + */ + return (quad_cksum((void *)str, NULL, (long)strlen(str), 1, &seed)); +} + +static void +krb4_getinst( + const char *hname, + char * inst, + size_t size) +{ + + /* + * Copy the first part of the hostname up to the first '.' into + * the buffer provided. Leave room for a NULL. + */ + while (size > 1 && *hname != '\0' && *hname != '.') { + *inst++ = isupper((int)*hname) ? tolower((int)*hname) : *hname; + hname++; + size--; + } + *inst = '\0'; +} + +static void +encrypt_data( + void * data, + size_t length, + des_cblock *key) +{ + des_key_schedule sched; + + /* + * First arg is des_cblock * in some places, and just des_cblock in + * others. Since des_cblock is a char array, they are equivalent. + * Just cast it to void * to keep both compilers quiet. typedefing + * arrays should be outlawed. + */ + des_key_sched((void *)key, sched); + des_pcbc_encrypt(data, data, (long)length, sched, key, DES_ENCRYPT); +} + + +static void +decrypt_data( + void * data, + size_t length, + des_cblock *key) +{ + des_key_schedule sched; + + des_key_sched((void *)key, sched); + des_pcbc_encrypt(data, data, (long)length, sched, key, DES_DECRYPT); +} + +/* + * like write(), but always writes out the entire buffer. + */ +static int +net_write( + int fd, + const void *vbuf, + size_t size) +{ + const char *buf = vbuf; /* so we can do ptr arith */ + ssize_t n; + + while (size > 0) { + n = write(fd, buf, size); + if (n < 0) + return (-1); + buf += n; + size -= n; + } + return (0); +} + +/* + * Like read(), but waits until the entire buffer has been filled. + */ +static int +net_read( + int fd, + void * vbuf, + size_t size, + int timeout) +{ + char *buf = vbuf; /* ptr arith */ + ssize_t n; + int neof = 0; + fd_set readfds; + struct timeval tv; + + while (size > 0) { + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + tv.tv_sec = timeout; + tv.tv_usec = 0; + switch (select(fd + 1, &readfds, NULL, NULL, &tv)) { + case 0: + errno = ETIMEDOUT; + /* FALLTHROUGH */ + case -1: + return (-1); + case 1: + assert(FD_ISSET(fd, &readfds)); + break; + default: + assert(0); + break; + } + n = read(fd, buf, size); + if (n < 0) + return (-1); + /* we only tolerate so many eof's */ + if (n == 0 && ++neof > 1024) { + errno = EIO; + return (-1); } + buf += n; + size -= n; } - if(p-astr+1 != strlen(astr)) - printf("astr2bin: hmmm... short inp exp %d got %d\n", - strlen(astr), p-astr+1); - *lenp = q-buf; + return (0); } +#if 0 /* -------------------------- */ /* debug routines */ -void -print_hex(str,buf,len) -char *str; -unsigned char *buf; -int len; +static void +print_hex( + const char * str, + const unsigned char * buf, + size_t len) { int i; - printf("%s:", str); + dbprintf(("%s:", str)); for(i=0;ilength, tktp->mbz); + dbprintf(("%s: length %d chk %lX\n", str, tktp->length, tktp->mbz)); print_hex("ticket data", tktp->dat, tktp->length); fflush(stdout); } -void -print_auth(authp) -AUTH_DAT *authp; +static void +print_auth( + AUTH_DAT *authp) { printf("\nAuth Data:\n"); printf(" Principal \"%s\" Instance \"%s\" Realm \"%s\"\n", authp->pname, authp->pinst, authp->prealm); - printf(" cksum %d life %d keylen %d\n", authp->checksum, - authp->life, sizeof(authp->session)); - print_hex("session key", authp->session, sizeof(authp->session)); + printf(" cksum %d life %d keylen %ld\n", authp->checksum, + authp->life, SIZEOF(authp->session)); + print_hex("session key", authp->session, SIZEOF(authp->session)); fflush(stdout); } -void -print_credentials(credp) -CREDENTIALS *credp; +static void +print_credentials( + CREDENTIALS *credp) { printf("\nCredentials:\n"); printf(" service \"%s\" instance \"%s\" realm \"%s\" life %d kvno %d\n", credp->service, credp->instance, credp->realm, credp->lifetime, credp->kvno); - print_hex("session key", credp->session, sizeof(credp->session)); + print_hex("session key", credp->session, SIZEOF(credp->session)); print_hex("ticket", credp->ticket_st.dat, credp->ticket_st.length); fflush(stdout); } +#endif + +#else + +void krb4_security_dummy(void); + +void +krb4_security_dummy(void) +{ +} + +#endif /* KRB4_SECURITY */