/*
* 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
* 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.9 2006/02/21 04:13:55 ktill Exp $
+ *
* krb4-security.c - helper functions for kerberos v4 security.
*/
+
+#include "config.h"
+#ifdef KRB4_SECURITY
+
+#include <des.h>
+#include <krb.h>
+
#include "amanda.h"
-#include "krb4-security.h"
+#include "dgram.h"
+#include "event.h"
+#include "packet.h"
+#include "queue.h"
+#include "security.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 P((int));
+int kuserok P((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) P((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 */
+ void (*fn) P((void *, void *, int)); /* 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 P((const char *,
+ char *(*)(char *, void *),
+ void (*)(void *, security_handle_t *, security_status_t), void *));
+static void krb4_accept P((int, int, void (*)(security_handle_t *, pkt_t *)));
+static void krb4_close P((void *));
+static int krb4_sendpkt P((void *, pkt_t *));
+static void krb4_recvpkt P((void *,
+ void (*)(void *, pkt_t *, security_status_t), void *, int));
+static void krb4_recvpkt_cancel P((void *));
+
+static void *krb4_stream_server P((void *));
+static int krb4_stream_accept P((void *));
+static void *krb4_stream_client P((void *, int));
+static void krb4_stream_close P((void *));
+static int krb4_stream_auth P((void *));
+static int krb4_stream_id P((void *));
+static int krb4_stream_write P((void *, const void *, size_t));
+static void krb4_stream_read P((void *, void (*)(void *, void *, int),
+ void *));
+static void krb4_stream_read_cancel P((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_cancel,
+};
+
+/*
+ * 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) P((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];
+ unsigned long cksum;
+};
+
+/*
+ * Private functions
+ */
+static unsigned long krb4_cksum P((const char *));
+static void krb4_getinst P((const char *, char *, size_t));
+static void host2key P((const char *, const char *, des_cblock *));
+static void init P((void));
+static void inithandle P((struct krb4_handle *, struct hostent *, int,
+ const char *));
+static void get_tgt P((void));
+static void killtickets P((void));
+static void recvpkt_callback P((void *));
+static void recvpkt_timeout P((void *));
+static int recv_security_ok P((struct krb4_handle *, pkt_t *));
+static void stream_read_callback P((void *));
+static int net_write P((int, const void *, size_t));
+static int net_read P((int, void *, size_t, int));
+
+static int add_ticket P((struct krb4_handle *, const pkt_t *, dgram_t *));
+static void add_mutual_auth P((struct krb4_handle *, dgram_t *));
+static int check_ticket P((struct krb4_handle *, const pkt_t *,
+ const char *, unsigned long));
+static int check_mutual_auth P((struct krb4_handle *, const char *));
+
+static const char *pkthdr2str P((const struct krb4_handle *, const pkt_t *));
+static int str2pkthdr P((const char *, pkt_t *, char *, size_t, int *));
+
+static const char *bin2astr P((const unsigned char *, int));
+static void astr2bin P((const char *, unsigned char *, int *));
+
+static void encrypt_data P((void *, int, des_cblock *));
+static void decrypt_data P((void *, int, 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()
{
- 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,
* 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()
+{
+ 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(hostname, conf_fn, fn, arg)
+ const char *hostname;
+ char *(*conf_fn) P((char *, void *));
+ void (*fn) P((void *, security_handle_t *, security_status_t));
+ void *arg;
{
- 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 = htons(KAMANDA_SERVICE_DEFAULT);
+ else
+ port = se->s_port;
+ snprintf(handle, sizeof(handle), "%ld", (long)time(NULL));
+ inithandle(kh, he, port, handle);
+ (*fn)(arg, &kh->sech, S_OK);
+}
- for(s = hp->h_name, d = inst; *s && *s != '.'; s++, d++)
- *d = isupper(*s)? tolower(*s) : *s;
- *d = '\0';
+/*
+ * Setup to handle new incoming connections
+ */
+static void
+krb4_accept(in, out, fn)
+ int in, out;
+ void (*fn) P((security_handle_t *, pkt_t *));
+{
/*
- * It isn't safe to pass hp->h_name to krb_realmofhost, since
- * it might use gethostbyname internally.
+ * Make sure we're initted
*/
- bzero(saved_hostname, sizeof(saved_hostname));
- strncpy(saved_hostname, hp->h_name, sizeof(saved_hostname)-1);
+ init();
- /* get realm name: krb_realmofhost always returns *something* */
- strcpy(realm, krb_realmofhost(saved_hostname));
+ /*
+ * We assume that in and out both point to the same socket
+ */
+ dgram_socket(&netfd, in);
+
+ /*
+ * 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(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(kh, he, port, handle)
+ 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 = AF_INET;
+ kh->peer.sin_port = 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(inst)
+ void *inst;
+{
-int kerberos_handshake(fd, key)
-int fd;
-des_cblock key;
+ krb4_recvpkt_cancel(inst);
+ amfree(inst);
+}
+
+/*
+ * Transmit a packet. Add security information first.
+ */
+static int
+krb4_sendpkt(cookie, pkt)
+ 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(cookie, fn, arg, timeout)
+ void *cookie, *arg;
+ void (*fn) P((void *, pkt_t *, security_status_t));
+ 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(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(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(cookie)
+ void *cookie;
+{
+ struct krb4_handle *kh = cookie;
+
+ assert(kh != NULL);
+
+ if (kh->fn != NULL) {
+ handleq_remove(kh);
+ kh->fn = NULL;
+ kh->arg = NULL;
+ }
+ 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;
+ }
+}
+
+/*
+ * Create the server end of a stream. For krb4, this means setup a tcp
+ * socket for receiving a connection.
+ */
+static void *
+krb4_stream_server(h)
+ 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);
+ if (ks->socket < 0) {
+ security_seterror(&kh->sech,
+ "can't create server stream: %s", strerror(errno));
+ amfree(ks);
+ return (NULL);
}
- if(l != n) {
- error("kerberos_handshake read2 error: [short read]");
+ ks->fd = -1;
+ ks->krb4_handle = kh;
+ ks->ev_read = NULL;
+ return (ks);
+}
+
+/*
+ * Accept an incoming connection on a stream_server socket
+ */
+static int
+krb4_stream_accept(s)
+ 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, -1, -1);
+ if (ks->fd < 0) {
+ security_stream_seterror(&ks->secstr,
+ "can't accept new stream connection: %s", strerror(errno));
+ return (-1);
}
+ return (0);
+}
- decrypt_data(&rcvlocal, sizeof rcvlocal, key);
+/*
+ * Return a connected stream.
+ */
+static void *
+krb4_stream_client(h, id)
+ void *h;
+ int id;
+{
+ struct krb4_handle *kh = h;
+ struct krb4_stream *ks;
- rcvlocal.tv_sec = ntohl(rcvlocal.tv_sec);
- rcvlocal.tv_usec = ntohl(rcvlocal.tv_usec);
+ assert(kh != NULL);
- dbprintf(("handshake: %d %d %d %d\n",
- local.tv_sec, local.tv_usec,
- rcvlocal.tv_sec, rcvlocal.tv_usec));
+ if (id < 0) {
+ security_seterror(&kh->sech,
+ "%d: invalid security stream id", id);
+ return (NULL);
+ }
- return (rcvlocal.tv_sec == (int32_t) (local.tv_sec + 1)) &&
- (rcvlocal.tv_usec == (int32_t) (local.tv_usec + 1));
+ 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);
+ }
+
+ 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(s)
+ 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(s)
+ 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);
- s = pkt->security;
- ch = *s++;
+ /* make sure our timeval is what we're expecting, see above */
+ assert(sizeof(struct timeval) == 8);
- skip_whitespace(s, ch);
- if(ch == '\0') {
- fprintf(stderr,"pkt->security is actually %s\n", pkt->security);
- return 0;
+ /*
+ * 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 = htonl(local.tv_sec);
+ enc.tv_usec = htonl(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);
}
- 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;
+
+ /*
+ * 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), 10) < 0) {
+ security_stream_seterror(&ks->secstr,
+ "krb4 stream handshake read error: %s", strerror(errno));
+ return (-1);
+ }
+ decrypt_data(&enc, sizeof(enc), &kh->session_key);
+ /* XXX do timestamp checking here */
+ enc.tv_sec = htonl(ntohl(enc.tv_sec) + 1);
+ enc.tv_usec = htonl(ntohl(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), 10) < 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(enc.tv_sec) == local.tv_sec + 1 &&
+ ntohl(enc.tv_usec) == 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(enc.tv_sec), (long)ntohl(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(s)
+ 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(s, buf, size)
+ 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(s, fn, arg)
+ void *s, *arg;
+ void (*fn) P((void *, void *, int));
+{
+ 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;
+ if (ks->ev_read != NULL)
+ event_release(ks->ev_read);
+
+ ks->ev_read = event_register(ks->fd, EV_READFD, stream_read_callback, ks);
+ ks->fn = fn;
+ ks->arg = arg;
+}
+
+/*
+ * Cancel a previous stream read request. It's ok if we didn't have a read
+ * scheduled.
+ */
+static void
+krb4_stream_read_cancel(s)
+ void *s;
+{
+ struct krb4_stream *ks = s;
+
+ assert(ks != NULL);
+
+ if (ks->ev_read != NULL) {
+ event_release(ks->ev_read);
+ ks->ev_read = NULL;
}
- return stralloc2("SECURITY TICKET ",
- bin2astr((unsigned char *)ticket.dat, ticket.length));
}
+/*
+ * Callback for krb4_stream_read
+ */
+static void
+stream_read_callback(arg)
+ void *arg;
+{
+ struct krb4_stream *ks = arg;
+ int 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);
+}
-int krb4_security_ok(addr, str, cksum, errstr)
-struct sockaddr_in *addr;
-char *str;
-uint32_t cksum;
-char **errstr;
+/*
+ * 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(cookie)
+ void *cookie;
{
- 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;
+ char handle[32];
+ struct sockaddr_in peer;
+ pkt_t pkt;
+ int sequence;
+ struct krb4_handle *kh;
+ struct hostent *he;
+ void (*fn) P((void *, pkt_t *, security_status_t));
+ void *arg;
- /* extract the ticket string from the message */
+ assert(cookie == NULL);
- s = str;
- ch = *s++;
+ /*
+ * 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;
- skip_whitespace(s, ch);
- if(ch == '\0') {
- *errstr = newstralloc(*errstr, "[bad krb4 security line]");
- return 0;
- }
-#define sc "TICKET"
- if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
- *errstr = newstralloc(*errstr, "[bad krb4 security line]");
- return 0;
+ 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, peer.sin_port, handle);
- astr2bin(ticket_str, (unsigned char *)ticket.dat, &ticket.length);
+ /*
+ * 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);
+}
- /* consult kerberos server */
+/*
+ * This is called when a handle times out before receiving a packet.
+ */
+static void
+recvpkt_timeout(cookie)
+ void *cookie;
+{
+ struct krb4_handle *kh = cookie;
+ void (*fn) P((void *, pkt_t *, security_status_t));
+ void *arg;
-#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)
-#endif
+ assert(kh != NULL);
+ 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(kh, pkt, msg)
+ struct krb4_handle *kh;
+ const pkt_t *pkt;
+ dgram_t *msg;
+{
+ char inst[INST_SZ];
+ KTEXT_ST ticket;
+ char *security;
+ int rc;
+
+ kh->cksum = krb4_cksum(pkt->body);
+#if CLIENT_HOST_INSTANCE == HOSTNAME_INSTANCE
/*
- * the instance must be in writable memory of size INST_SZ
- * krb_rd_req might change it.
+ * 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';
- 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;
+
+ /*
+ * Get a ticket with the user-defined service and instance,
+ * and using the checksum of the body of the request packet.
+ */
+ 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);
- /* verify checksum */
+ return (0);
+}
- dbprintf(("msg checksum %d auth checksum %d\n",
- cksum, auth.checksum));
+/*
+ * Add the mutual authenticator. This is the checksum from
+ * the req, + 1
+ */
+static void
+add_mutual_auth(kh, msg)
+ 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 = htonl(kh->cksum + 1);
+ encrypt_data(&mutual, sizeof(mutual), &kh->session_key);
+
+ security = vstralloc("SECURITY MUTUAL-AUTH ",
+ bin2astr(mutual.pad, 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(kh, pkt)
+ struct krb4_handle *kh;
+ pkt_t *pkt;
+{
+ char *tok, *security, *body;
+ unsigned long cksum;
- if(cksum != auth.checksum) {
- *errstr = newstralloc(*errstr, "[kerberos error: checksum mismatch]");
- dbprintf(("checksum error: exp %d got %d\n",
- auth.checksum, cksum));
- return 0;
+ 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);
+
+
+ /*
+ * 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(kh, pkt, ticket_str, cksum)
+ 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 = sizeof(ticket.dat);
+ astr2bin(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 = 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(kh, mutual_auth_str)
+ struct krb4_handle *kh;
+ const char *mutual_auth_str;
+{
+ union mutual mutual;
+ int len;
+
+ 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 = sizeof(mutual);
+ astr2bin(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, len, &kh->session_key);
+ mutual.cksum = ntohl(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);
+ }
+
+ return (0);
}
-/* ---------------- */
+/*
+ * Convert a pkt_t into a header string for our packet
+ */
+static const char *
+pkthdr2str(kh, pkt)
+ const struct krb4_handle *kh;
+ const pkt_t *pkt;
+{
+ static char retbuf[256];
+
+ assert(kh != NULL);
+ assert(pkt != NULL);
-/* XXX - I'm getting astrs with the high bit set in the debug output!?? */
+ snprintf(retbuf, sizeof(retbuf), "Amanda %d.%d %s HANDLE %s SEQ %d\n",
+ VERSION_MAJOR, VERSION_MINOR, pkt_type2str(pkt->type),
+ kh->proto_handle, kh->sequence);
-#define hex_digit(d) ("0123456789ABCDEF"[(d)])
-#define unhex_digit(h) (((h) - '0') > 9? ((h) - 'A' + 10) : ((h) - '0'))
+ /* check for truncation. If only we had asprintf()... */
+ assert(retbuf[strlen(retbuf) - 1] == '\n');
-char *bin2astr(buf, len)
-unsigned char *buf;
-int len;
+ return (retbuf);
+}
+
+/*
+ * Parses out the header line in 'str' into the pkt and handle
+ * Returns negative on parse error.
+ */
+static int
+str2pkthdr(origstr, pkt, handle, handlesize, sequence)
+ const char *origstr;
+ pkt_t *pkt;
+ char *handle;
+ size_t handlesize;
+ int *sequence;
{
- char *str, *q;
- unsigned char *p;
- int slen, i, needs_quote;
+ char *str;
+ const char *tok;
- /* first pass, calculate string len */
+ assert(origstr != NULL);
+ assert(pkt != NULL);
- slen = needs_quote = 0; p = buf;
- if(*p == '"') needs_quote = 1; /* special case */
- for(i=0;i<len;i++) {
- if(!isgraph(*p)) needs_quote = 1;
- if(isprint(*p) && *p != '$' && *p != '"')
- slen += 1;
- else
- slen += 3;
- p++;
- }
- if(needs_quote) slen += 2;
+ str = stralloc(origstr);
+
+ /* "Amanda %d.%d <ACK,NAK,...> 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;
- /* 2nd pass, allocate string and fill it in */
+ /* Read in "HANDLE" */
+ if ((tok = strtok(NULL, " ")) == NULL || strcmp(tok, "HANDLE") != 0)
+ goto parse_error;
- str = (char *)alloc(slen+1);
+ /* 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(hostname, inst, key)
+ const char *hostname, *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(buf, len)
+ const unsigned char *buf;
+ int len;
+{
+ static const char tohex[] = "0123456789ABCDEF";
+ static char *str = NULL;
+ char *q;
+ const unsigned char *p;
+ int slen, i;
+
+ /*
+ * calculate output string len
+ * We quote everything, so each input byte == 3 output chars, plus
+ * two more for quotes
+ */
+ slen = (len * 3) + 2;
+
+ /* 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<len;i++) {
- if(isprint(*p) && *p != '$' && *p != '"')
- *q++ = *p++;
- else {
- *q++ = '$';
- *q++ = hex_digit((*p >> 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(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(astr, buf, lenp)
+ const char *astr;
+ unsigned char *buf;
+ int *lenp;
{
- char *p;
+ const 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 (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(str)
+ 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, strlen(str), 1, &seed));
+}
+
+static void
+krb4_getinst(hname, inst, size)
+ 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(data, length, key)
+ void *data;
+ int 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, length, sched, key, DES_ENCRYPT);
+}
+
+
+static void
+decrypt_data(data, length, key)
+ void *data;
+ int length;
+ des_cblock *key;
+{
+ des_key_schedule sched;
+
+ des_key_sched((void *)key, sched);
+ des_pcbc_encrypt(data, data, length, sched, key, DES_DECRYPT);
+}
+
+/*
+ * like write(), but always writes out the entire buffer.
+ */
+static int
+net_write(fd, vbuf, size)
+ int fd;
+ const void *vbuf;
+ size_t size;
+{
+ const char *buf = vbuf; /* so we can do ptr arith */
+ int 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(fd, vbuf, size, timeout)
+ int fd;
+ void *vbuf;
+ size_t size;
+ int timeout;
+{
+ char *buf = vbuf; /* ptr arith */
+ int n, 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
+static void
print_hex(str,buf,len)
-char *str;
-unsigned char *buf;
+const char *str;
+const unsigned char *buf;
int len;
{
int i;
- printf("%s:", str);
+ dbprintf(("%s:", str));
for(i=0;i<len;i++) {
- if(i%25 == 0) putchar('\n');
- printf(" %02X", buf[i]);
+ if(i%25 == 0) dbprintf(("\n"));
+ dbprintf((" %02X", buf[i]));
}
- putchar('\n');
+ dbprintf(("\n"));
}
-void
+static void
print_ticket(str, tktp)
-char *str;
+const char *str;
KTEXT tktp;
{
- int i;
- printf("%s: length %d chk %lX\n", str, tktp->length, 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
+static void
print_auth(authp)
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,
+ 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
+static void
print_credentials(credp)
CREDENTIALS *credp;
{
print_hex("ticket", credp->ticket_st.dat, credp->ticket_st.length);
fflush(stdout);
}
+#endif
+
+#else
+void krb4_security_dummy (void) {}
+#endif /* KRB4_SECURITY */