X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=common-src%2Fkrb5-security.c;h=5b3d76a416afe55584d4e0c245fbc3caaf0c9350;hb=d3b2175e084f88c8736ad7073eacbf4670147aec;hp=b7e6e710caaccad17d03783922071156b5aeafff;hpb=34197d9f46a5f4e944378cbb65fca32ee0eec7b9;p=debian%2Famanda diff --git a/common-src/krb5-security.c b/common-src/krb5-security.c index b7e6e71..5b3d76a 100644 --- a/common-src/krb5-security.c +++ b/common-src/krb5-security.c @@ -28,13 +28,13 @@ * $Id: krb5-security.c,v 1.22 2006/06/16 10:55:05 martinea Exp $ * * krb5-security.c - kerberos V5 security module + * + * XXX still need to check for initial keyword on connect so we can skip + * over shell garbage and other stuff that krb5 might want to spew out. */ -#include "config.h" -#ifdef KRB5_SECURITY #include "amanda.h" #include "util.h" -#include "arglist.h" #include "event.h" #include "packet.h" #include "queue.h" @@ -43,7 +43,13 @@ #include "stream.h" #include "version.h" -#define BROKEN_MEMORY_CCACHE +#ifdef KRB5_HEIMDAL_INCLUDES +#include "com_err.h" +#endif + +#ifdef KRB5_SECURITY + +#define BROKEN_MEMORY_CCACHE #ifdef BROKEN_MEMORY_CCACHE /* @@ -59,7 +65,6 @@ #endif /* HAVE_ON_EXIT */ #endif /* ! HAVE_ATEXIT */ #endif - #ifndef KRB5_HEIMDAL_INCLUDES #include #else @@ -68,22 +73,17 @@ #include #ifndef KRB5_ENV_CCNAME -#define KRB5_ENV_CCNAME "KRB5CCNAME" +#define KRB5_ENV_CCNAME "KRB5CCNAME" #endif -/*#define KRB5_DEBUG*/ +#define k5printf(x) auth_debug(1,x) -#ifdef KRB5_DEBUG -#define k5printf(x) dbprintf(x) -#else -#define k5printf(x) -#endif /* * consider undefining when kdestroy() is fixed. The current version does * not work under krb5-1.2.4 in rh7.3, perhaps others. */ -#define KDESTROY_VIA_UNLINK 1 +#define KDESTROY_VIA_UNLINK 1 /* * Define this if you want all network traffic encrypted. This will @@ -91,56 +91,52 @@ * * It would be nice if we could do this on a filesystem-by-filesystem basis. */ -/*#define AMANDA_KRB5_ENCRYPT*/ +/*#define AMANDA_KRB5_ENCRYPT*/ /* * Where the keytab lives, if defined. Otherwise it expects something in the * config file. */ -/* #define AMANDA_KEYTAB "/.amanda-v5-keytab" */ +/* #define AMANDA_KEYTAB "/.amanda-v5-keytab" */ /* * The name of the principal we authenticate with, if defined. Otherwise * it expects something in the config file. */ -/* #define AMANDA_PRINCIPAL "service/amanda" */ +/* #define AMANDA_PRINCIPAL "service/amanda" */ /* * The lifetime of our tickets in seconds. This may or may not need to be * configurable. */ -#define AMANDA_TKT_LIFETIME (12*60*60) +#define AMANDA_TKT_LIFETIME (12*60*60) + /* * The name of the service in /etc/services. This probably shouldn't be * configurable. */ -#define AMANDA_KRB5_SERVICE_NAME "k5amanda" +#define AMANDA_KRB5_SERVICE_NAME "k5amanda" /* * The default port to use if above entry in /etc/services doesn't exist */ -#define AMANDA_KRB5_DEFAULT_PORT 10082 +#define AMANDA_KRB5_DEFAULT_PORT 10082 /* * The timeout in seconds for each step of the GSS negotiation phase */ -#define GSS_TIMEOUT 30 +#define GSS_TIMEOUT 30 /* * The largest buffer we can send/receive. */ -#define AMANDA_MAX_TOK_SIZE (MAX_TAPE_BLOCK_BYTES * 4) - -/* - * Magic values for krb5_conn->handle - */ -#define H_EOF -1 /* this connection has been shut down */ +#define AMANDA_MAX_TOK_SIZE (MAX_TAPE_BLOCK_BYTES * 4) /* * This is the tcp stream buffer size */ -#define KRB5_STREAM_BUFSIZE (MAX_TAPE_BLOCK_BYTES * 2) +#define KRB5_STREAM_BUFSIZE (MAX_TAPE_BLOCK_BYTES * 2) /* * This is the max number of outgoing connections we can have at once. @@ -148,194 +144,86 @@ * to contact everything. We need to limit this to avoid blowing * the max number of open file descriptors a process can have. */ -#define AMANDA_KRB5_MAXCONN 40 +#define AMANDA_KRB5_MAXCONN 40 -/* - * This is a frame read off of the connection. Each frame has an - * associated handle and a gss_buffer which contains a len,value pair. - */ -struct krb5_frame { - int handle; /* proto handle */ - gss_buffer_desc tok; /* token */ - TAILQ_ENTRY(krb5_frame) tq; /* queue handle */ -}; /* - * This is a krb5 connection to a host. We should only have - * one connection per host. + * Number of seconds krb5 has to start up */ -struct krb5_conn { - int fd; /* tcp connection */ - struct { /* buffer read() calls */ - char buf[KRB5_STREAM_BUFSIZE]; /* buffer */ - size_t left; /* unread data */ - ssize_t size; /* size of last read */ - } readbuf; - enum { unauthed, authed } state; - event_handle_t *ev_read; /* read (EV_READFD) handle */ - int ev_read_refcnt; /* number of readers */ - char hostname[MAX_HOSTNAME_LENGTH+1]; /* human form of above */ - char *errmsg; /* error passed up */ - gss_ctx_id_t gss_context; /* GSSAPI context */ - int refcnt; /* number of handles using */ - TAILQ_HEAD(, krb5_frame) frameq; /* queue of read frames */ - TAILQ_ENTRY(krb5_conn) tq; /* queue handle */ -}; - - -struct krb5_stream; +#define CONNECT_TIMEOUT 20 /* - * This is the private handle data. + * Cache the local hostname */ -struct krb5_handle { - security_handle_t sech; /* MUST be first */ - char *hostname; /* ptr to kc->hostname */ - struct krb5_stream *ks; /* virtual stream we xmit over */ - - union { - void (*recvpkt)(void *, pkt_t *, security_status_t); - /* func to call when packet recvd */ - void (*connect)(void *, security_handle_t *, security_status_t); - /* func to call when connected */ - } fn; - void *arg; /* argument to pass function */ - void *datap; /* argument to pass function */ - event_handle_t *ev_wait; /* wait handle for connects */ - char *(*conf_fn)(char *, void *); /* used to get config info */ - event_handle_t *ev_timeout; /* timeout handle for recv */ -}; +static char myhostname[MAX_HOSTNAME_LENGTH+1]; -/* - * This is the internal security_stream data for krb5. - */ -struct krb5_stream { - security_stream_t secstr; /* MUST be first */ - struct krb5_conn *kc; /* physical connection */ - int handle; /* protocol handle */ - event_handle_t *ev_read; /* read (EV_WAIT) event handle */ - void (*fn)(void *, void *, ssize_t);/* read event fn */ - void *arg; /* arg for previous */ - char buf[KRB5_STREAM_BUFSIZE]; - ssize_t len; -}; /* * Interface functions */ -static ssize_t krb5_sendpkt(void *, pkt_t *); -static int krb5_stream_accept(void *); -static int krb5_stream_auth(void *); -static int krb5_stream_id(void *); -static int krb5_stream_write(void *, const void *, size_t); -static void * krb5_stream_client(void *, int); -static void * krb5_stream_server(void *); -static void krb5_accept(const struct security_driver *, int, int, - void (*)(security_handle_t *, pkt_t *)); -static void krb5_close(void *); -static void krb5_connect(const char *, char *(*)(char *, void *), - void (*)(void *, security_handle_t *, security_status_t), - void *, void *); -static void krb5_recvpkt(void *, void (*)(void *, pkt_t *, security_status_t), - void *, int); -static void krb5_recvpkt_cancel(void *); -static void krb5_stream_close(void *); -static void krb5_stream_read(void *, void (*)(void *, void *, ssize_t), void *); -static ssize_t krb5_stream_read_sync(void *, void **); -static void krb5_stream_read_cancel(void *); +static void krb5_accept(const struct security_driver *, int, int, + void (*)(security_handle_t *, pkt_t *)); +static void krb5_connect(const char *, + char *(*)(char *, void *), + void (*)(void *, security_handle_t *, security_status_t), void *, void *); + +static void krb5_init(void); +#ifdef BROKEN_MEMORY_CCACHE +static void cleanup(void); +#endif +static const char *get_tgt(char *keytab_name, char *principal_name); +static int gss_server(struct tcp_conn *); +static int gss_client(struct sec_handle *); +static const char *gss_error(OM_uint32, OM_uint32); +static char *krb5_checkuser(char *host, char *name, char *realm); + +#ifdef AMANDA_KRB5_ENCRYPT +static int k5_encrypt(void *cookie, void *buf, ssize_t buflen, + void **encbuf, ssize_t *encbuflen); +static int k5_decrypt(void *cookie, void *buf, ssize_t buflen, + void **encbuf, ssize_t *encbuflen); +#endif /* * This is our interface to the outside world. */ const security_driver_t krb5_security_driver = { - "krb5", + "KRB5", krb5_connect, krb5_accept, - krb5_close, - krb5_sendpkt, - krb5_recvpkt, - krb5_recvpkt_cancel, - krb5_stream_server, - krb5_stream_accept, - krb5_stream_client, - krb5_stream_close, - krb5_stream_auth, - krb5_stream_id, - krb5_stream_write, - krb5_stream_read, - krb5_stream_read_sync, - krb5_stream_read_cancel, - sec_close_connection_none, -}; - -/* - * Cache the local hostname - */ -static char hostname[MAX_HOSTNAME_LENGTH+1]; - -/* - * This is a queue of open connections - */ -static struct { - TAILQ_HEAD(, krb5_conn) tailq; - int qlength; -} krb5_connq = { - TAILQ_HEAD_INITIALIZER(krb5_connq.tailq), 0 + sec_close, + stream_sendpkt, + stream_recvpkt, + stream_recvpkt_cancel, + tcpma_stream_server, + tcpma_stream_accept, + tcpma_stream_client, + tcpma_stream_close, + sec_stream_auth, + sec_stream_id, + tcpm_stream_write, + tcpm_stream_read, + tcpm_stream_read_sync, + tcpm_stream_read_cancel, + tcpm_close_connection, +#ifdef AMANDA_KRB5_ENCRYPT + k5_encrypt, + k5_decrypt, +#else + NULL, + NULL, +#endif }; -#define krb5_connq_first() TAILQ_FIRST(&krb5_connq.tailq) -#define krb5_connq_next(kc) TAILQ_NEXT(kc, tq) -#define krb5_connq_append(kc) do { \ - TAILQ_INSERT_TAIL(&krb5_connq.tailq, kc, tq); \ - krb5_connq.qlength++; \ -} while (0) -#define krb5_connq_remove(kc) do { \ - assert(krb5_connq.qlength > 0); \ - TAILQ_REMOVE(&krb5_connq.tailq, kc, tq); \ - krb5_connq.qlength--; \ -} while (0) static int newhandle = 1; -/* - * 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 *); - /* * Local functions */ -static void init(void); -#ifdef BROKEN_MEMORY_CCACHE -static void cleanup(void); -#endif -static const char *get_tgt(char *, char *); -static void open_callback(void *); -static void connect_callback(void *); -static void connect_timeout(void *); -static int send_token(struct krb5_conn *, int, const gss_buffer_desc *); -static ssize_t recv_token(struct krb5_conn *, int *, gss_buffer_desc *, int); -static void recvpkt_callback(void *, void *, ssize_t); -static void recvpkt_timeout(void *); -static void stream_read_callback(void *); -static void stream_read_sync_callback2(void *, void *, ssize_t); -static int gss_server(struct krb5_conn *); -static int gss_client(struct krb5_handle *); -static const char *gss_error(OM_uint32, OM_uint32); - -#ifdef AMANDA_KRB5_ENCRYPT -static int kdecrypt(struct krb5_stream *, gss_buffer_desc *, gss_buffer_desc *); -static int kencrypt(struct krb5_stream *, gss_buffer_desc *, gss_buffer_desc *); -#endif -static struct krb5_conn *conn_get(const char *); -static void conn_put(struct krb5_conn *); -static void conn_read(struct krb5_conn *); -static void conn_read_cancel(struct krb5_conn *); -static void conn_read_callback(void *); -static int conn_run_frameq(struct krb5_conn *, struct krb5_stream *); -static char * krb5_checkuser(char *, char *, char *); +static int runkrb5(struct sec_handle *); +char *keytab_name; +char *principal_name; /* * krb5 version of a security handle allocator. Logically sets @@ -349,181 +237,113 @@ krb5_connect( void * arg, void * datap) { - struct krb5_handle *kh; - struct hostent *he; - struct servent *se; - int fd; - int port; - const char *err; - char *keytab_name = NULL; - char *principal_name = NULL; + struct sec_handle *rh; + int result; + struct addrinfo hints; + struct addrinfo *res = NULL; + assert(fn != NULL); assert(hostname != NULL); + (void)conf_fn; /* Quiet unused parameter warning */ + (void)datap; /* Quiet unused parameter warning */ - k5printf(("krb5_connect: %s\n", hostname)); + k5printf(("%s: krb5: krb5_connect: %s\n", debug_prefix_time(NULL), + hostname)); - /* - * Make sure we're initted - */ - init(); + krb5_init(); + + rh = alloc(sizeof(*rh)); + security_handleinit(&rh->sech, &krb5_security_driver); + rh->hostname = NULL; + rh->rs = NULL; + rh->ev_timeout = NULL; + rh->rc = NULL; + +#ifdef WORKING_IPV6 + hints.ai_flags = AI_CANONNAME | AI_V4MAPPED | AI_ALL; + hints.ai_family = AF_UNSPEC; +#else + hints.ai_flags = AI_CANONNAME; + hints.ai_family = AF_INET; +#endif + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + result = getaddrinfo(hostname, NULL, &hints, &res); +#ifdef WORKING_IPV6 + if (result != 0) { + hints.ai_flags = AI_CANONNAME; + hints.ai_family = AF_UNSPEC; + result = getaddrinfo(hostname, NULL, &hints, &res); + } +#endif + if(result != 0) { + dbprintf(("krb5_connect: getaddrinfo(%s): %s\n", hostname, gai_strerror(result))); + security_seterror(&rh->sech, "getaddrinfo(%s): %s\n", hostname, + gai_strerror(result)); + (*fn)(arg, &rh->sech, S_ERROR); + return; + } + + rh->hostname = stralloc(res->ai_canonname); /* will be replaced */ + rh->rs = tcpma_stream_client(rh, newhandle++); + rh->rc->recv_security_ok = NULL; + rh->rc->prefix_packet = NULL; + + if (rh->rs == NULL) + goto error; - kh = alloc(SIZEOF(*kh)); - security_handleinit(&kh->sech, &krb5_security_driver); - kh->hostname = NULL; - kh->ks = NULL; - kh->ev_wait = NULL; - kh->ev_timeout = NULL; + amfree(rh->hostname); + rh->hostname = stralloc(rh->rs->rc->hostname); #ifdef AMANDA_KEYTAB keytab_name = AMANDA_KEYTAB; #else if(conf_fn) { - keytab_name = conf_fn("krb5keytab", datap); + keytab_name = conf_fn("krb5keytab", datap); } #endif #ifdef AMANDA_PRINCIPAL principal_name = AMANDA_PRINCIPAL; #else if(conf_fn) { - principal_name = conf_fn("krb5principal", datap); + principal_name = conf_fn("krb5principal", datap); } #endif - if ((err = get_tgt(keytab_name, principal_name)) != NULL) { - security_seterror(&kh->sech, "%s: could not get TGT: %s", - hostname, err); - (*fn)(arg, &kh->sech, S_ERROR); - return; - } - - if ((he = gethostbyname(hostname)) == NULL) { - security_seterror(&kh->sech, - "%s: could not resolve hostname", hostname); - (*fn)(arg, &kh->sech, S_ERROR); - return; - } - kh->fn.connect = fn; - kh->conf_fn = conf_fn; - kh->arg = arg; - kh->datap = datap; - kh->hostname = stralloc(he->h_name); - kh->ks = krb5_stream_client(kh, newhandle++); - - if (kh->ks == NULL) - goto error; - - fd = kh->ks->kc->fd; - - if (fd < 0) { - /* - * We need to open a new connection. See if we have too - * many connections open. - */ - if (krb5_connq.qlength > AMANDA_KRB5_MAXCONN) { - k5printf(("krb5_connect: too many conections (%d), delaying %s\n", - krb5_connq.qlength, kh->hostname)); - krb5_stream_close(kh->ks); - kh->ev_wait = event_register((event_id_t)open_callback, - EV_WAIT, open_callback, kh); - return; - } - - if ((se = getservbyname(AMANDA_KRB5_SERVICE_NAME, "tcp")) == NULL) - port = htons(AMANDA_KRB5_DEFAULT_PORT); - else - port = se->s_port; - - /* - * Get a non-blocking socket. - */ - fd = stream_client(kh->hostname, ntohs(port), KRB5_STREAM_BUFSIZE, - KRB5_STREAM_BUFSIZE, NULL, 1); - if (fd < 0) { - security_seterror(&kh->sech, - "can't connect to %s:%d: %s", hostname, ntohs(port), - strerror(errno)); + /* + * We need to open a new connection. + * + * XXX need to eventually limit number of outgoing connections here. + */ + if(rh->rc->read == -1) { + if (runkrb5(rh) < 0) goto error; - } - kh->ks->kc->fd = fd; + rh->rc->refcnt++; } + /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. + * + * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ - kh->ev_wait = event_register((event_id_t)fd, EV_WRITEFD, - connect_callback, kh); - kh->ev_timeout = event_register((event_id_t)GSS_TIMEOUT, EV_TIME, - connect_timeout, kh); + rh->fn.connect = fn; + rh->arg = arg; + rh->rs->ev_read = event_register((event_id_t)(rh->rs->rc->write), + EV_WRITEFD, sec_connect_callback, rh); + rh->ev_timeout = event_register(CONNECT_TIMEOUT, EV_TIME, + sec_connect_timeout, rh); return; error: - (*fn)(arg, &kh->sech, S_ERROR); -} - -/* - * Called when there are not too many connections open such that - * we can open more. - */ -static void -open_callback( - void * cookie) -{ - struct krb5_handle *kh = cookie; - - event_release(kh->ev_wait); - - k5printf(("krb5: open_callback: possible connections available, retry %s\n", - kh->hostname)); - krb5_connect(kh->hostname, kh->conf_fn, kh->fn.connect, kh->arg,kh->datap); - amfree(kh->hostname); - amfree(kh); -} - -/* - * Called when a tcp connection is finished connecting and is ready - * to be authenticated. - */ -static void -connect_callback( - void * cookie) -{ - struct krb5_handle *kh = cookie; - - event_release(kh->ev_wait); - kh->ev_wait = NULL; - event_release(kh->ev_timeout); - kh->ev_timeout = NULL; - - if (kh->ks->kc->state == unauthed) { - if (gss_client(kh) < 0) { - (*kh->fn.connect)(kh->arg, &kh->sech, S_ERROR); - return; - } - kh->ks->kc->state = authed; - } - assert(kh->ks->kc->gss_context != GSS_C_NO_CONTEXT); - - (*kh->fn.connect)(kh->arg, &kh->sech, S_OK); -} - -/* - * Called if a connection times out before completion. - */ -static void -connect_timeout( - void * cookie) -{ - struct krb5_handle *kh = cookie; - - event_release(kh->ev_wait); - kh->ev_wait = NULL; - event_release(kh->ev_timeout); - kh->ev_timeout = NULL; - - (*kh->fn.connect)(kh->arg, &kh->sech, S_TIMEOUT); + (*fn)(arg, &rh->sech, S_ERROR); } /* @@ -536,849 +356,133 @@ krb5_accept( int out, void (*fn)(security_handle_t *, pkt_t *)) { - struct sockaddr_in sin; + struct sockaddr_storage sin; socklen_t len; - struct krb5_conn *kc; - struct hostent *he; + struct tcp_conn *rc; + char hostname[NI_MAXHOST]; + int result; + char *errmsg = NULL; - /* - * Make sure we're initted - */ - init(); - - /* shut up compiler */ - driver=driver; - out=out; + krb5_init(); - len = SIZEOF(sin); - if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) + len = sizeof(sin); + if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) { + dbprintf(("%s: getpeername returned: %s\n", debug_prefix_time(NULL), + strerror(errno))); return; - he = gethostbyaddr((void *)&sin.sin_addr, SIZEOF(sin.sin_addr), AF_INET); - if (he == NULL) - return; - - kc = conn_get(he->h_name); - kc->fd = in; - if (gss_server(kc) < 0) - error("gss_server failed: %s\n", kc->errmsg); - kc->state = authed; - accept_fn = fn; - conn_read(kc); -} - -/* - * Locate an existing connection to the given host, or create a new, - * unconnected entry if none exists. The caller is expected to check - * for the lack of a connection (kc->fd == -1) and set one up. - */ -static struct krb5_conn * -conn_get( - const char * hostname) -{ - struct krb5_conn *kc; - - k5printf(("krb5: conn_get: %s\n", hostname)); - - for (kc = krb5_connq_first(); kc != NULL; kc = krb5_connq_next(kc)) { - if (strcasecmp(hostname, kc->hostname) == 0) - break; - } - - if (kc != NULL) { - kc->refcnt++; - k5printf(("krb5: conn_get: exists, refcnt to %s is now %d\n", - kc->hostname, kc->refcnt)); - return (kc); } - - k5printf(("krb5: conn_get: creating new handle\n")); - /* - * We can't be creating a new handle if we are the client - */ - assert(accept_fn == NULL); - kc = alloc(SIZEOF(*kc)); - kc->fd = -1; - kc->readbuf.left = 0; - kc->readbuf.size = 0; - kc->state = unauthed; - kc->ev_read = NULL; - strncpy(kc->hostname, hostname, SIZEOF(kc->hostname) - 1); - kc->hostname[SIZEOF(kc->hostname) - 1] = '\0'; - kc->errmsg = NULL; - kc->gss_context = GSS_C_NO_CONTEXT; - /* - * [XXX] this is set to 2 in order to force the connection to stay - * open and process more protocol requests. (basically consistant - * with bsd-security.c, and theoretically krb4-security.c. This - * needs to be addressed in a cleaner way. - */ - kc->refcnt = 2; - TAILQ_INIT(&kc->frameq); - krb5_connq_append(kc); - return (kc); -} - -/* - * Delete a reference to a connection, and close it if it is the last - * reference. - */ -static void -conn_put( - struct krb5_conn * kc) -{ - OM_uint32 min_stat; - struct krb5_frame *kf; - - assert(kc->refcnt > 0); - if (--kc->refcnt > 0) { - k5printf(("krb5: conn_put: decrementing refcnt for %s to %d\n", - kc->hostname, kc->refcnt)); + if ((result = getnameinfo((struct sockaddr *)&sin, len, + hostname, NI_MAXHOST, NULL, 0, 0) != 0)) { + dbprintf(("%s: getnameinfo failed: %s\n", + debug_prefix_time(NULL), gai_strerror(result))); return; } - k5printf(("krb5: conn_put: closing connection to %s\n", kc->hostname)); - if (kc->fd != -1) - aclose(kc->fd); - if (kc->ev_read != NULL) - event_release(kc->ev_read); - if (kc->errmsg != NULL) - amfree(kc->errmsg); - gss_delete_sec_context(&min_stat, &kc->gss_context, GSS_C_NO_BUFFER); - while ((kf = TAILQ_FIRST(&kc->frameq)) != NULL) { - TAILQ_REMOVE(&kc->frameq, kf, tq); - if (kf->tok.value != NULL) - amfree(kf->tok.value); - amfree(kf); - } - krb5_connq_remove(kc); - amfree(kc); - /* signal that a connection is available */ - event_wakeup((event_id_t)open_callback); -} - -/* - * Turn on read events for a conn. Or, increase a refcnt if we are - * already receiving read events. - */ -static void -conn_read( - struct krb5_conn *kc) -{ - - if (kc->ev_read != NULL) { - kc->ev_read_refcnt++; - k5printf(("krb5: conn_read: incremented refcnt to %d for %s\n", - kc->ev_read_refcnt, kc->hostname)); - return; - } - k5printf(("krb5: conn_read registering event handler for %s\n", - kc->hostname)); - kc->ev_read = event_register((event_id_t)kc->fd, EV_READFD, conn_read_callback, kc); - kc->ev_read_refcnt = 1; -} - -static void -conn_read_cancel( - struct krb5_conn * kc) -{ - - if (--kc->ev_read_refcnt > 0) { - k5printf(("krb5: conn_read_cancel: decremented refcnt to %d for %s\n", - kc->ev_read_refcnt, kc->hostname)); + if (check_name_give_sockaddr(hostname, + (struct sockaddr *)&sin, &errmsg) < 0) { + amfree(errmsg); return; } - k5printf(("krb5: conn_read_cancel: releasing event handler for %s\n", - kc->hostname)); - event_release(kc->ev_read); - kc->ev_read = NULL; -} - -/* - * frees a handle allocated by the above - */ -static void -krb5_close( - void * inst) -{ - struct krb5_handle *kh = inst; - - assert(kh != NULL); - k5printf(("krb5: closing handle to %s\n", kh->hostname)); - - if (kh->ks != NULL) { - /* This may be null if we get here on an error */ - krb5_recvpkt_cancel(kh); - security_stream_close(&kh->ks->secstr); - } - amfree(kh->hostname); - amfree(kh); + rc = sec_tcp_conn_get(hostname, 0); + rc->recv_security_ok = NULL; + rc->prefix_packet = NULL; + memcpy(&rc->peer, &sin, sizeof(rc->peer)); + rc->read = in; + rc->write = out; + rc->driver = driver; + if (gss_server(rc) < 0) + error("gss_server failed: %s\n", rc->errmsg); + rc->accept_fn = fn; + sec_tcp_conn_read(rc); } /* - * Transmit a packet. Encrypt first. - */ -static ssize_t -krb5_sendpkt( - void * cookie, - pkt_t * pkt) -{ - struct krb5_handle *kh = cookie; - gss_buffer_desc tok; - int rval; - unsigned char *buf; - - assert(kh != NULL); - assert(pkt != NULL); - - k5printf(("krb5: sendpkt: enter\n")); - - if (pkt->body[0] == '\0') { - tok.length = 1; - tok.value = alloc(SIZEOF(pkt->type)); - memcpy(tok.value, &pkt->type, sizeof(unsigned char)); - } else { - tok.length = strlen(pkt->body) + 2; - tok.value = alloc(tok.length); - buf = tok.value; - *buf++ = (unsigned char)pkt->type; - strncpy((char *)buf, pkt->body, tok.length - 2); - buf[tok.length - 2] = '\0'; - } - - k5printf(("krb5: sendpkt: %s (%d) pkt_t (len %d) contains:\n\n\"%s\"\n\n", - pkt_type2str(pkt->type), pkt->type, strlen(pkt->body), pkt->body)); - - rval = krb5_stream_write(kh->ks, tok.value, tok.length); - if (rval < 0) - security_seterror(&kh->sech, security_stream_geterror(&kh->ks->secstr)); - /*@ignore@*/ - amfree(tok.value); - /*@end@*/ - return (rval); -} - -/* - * Set up to receive a packet asyncronously, and call back when - * it has been read. - */ -static void -krb5_recvpkt( - void * cookie, - void (*fn)(void *, pkt_t *, security_status_t), - void * arg, - int timeout) -{ - struct krb5_handle *kh = cookie; - - assert(kh != NULL); - - k5printf(("krb5: recvpkt registered for %s\n", kh->hostname)); - - /* - * Reset any pending timeout on this handle - */ - if (kh->ev_timeout != NULL) - event_release(kh->ev_timeout); - - /* - * Negative timeouts mean no 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.recvpkt = fn; - kh->arg = arg; - krb5_stream_read(kh->ks, recvpkt_callback, kh); -} - -/* - * Remove a async receive request from the queue - */ -static void -krb5_recvpkt_cancel( - void * cookie) -{ - struct krb5_handle *kh = cookie; - - k5printf(("krb5: cancelling recvpkt for %s\n", kh->hostname)); - - assert(kh != NULL); - - krb5_stream_read_cancel(kh->ks); - if (kh->ev_timeout != NULL) { - event_release(kh->ev_timeout); - kh->ev_timeout = NULL; - } -} - -/* - * This is called when a handle is woken up because data read off of the - * net is for it. - */ -static void -recvpkt_callback( - void *cookie, - void *buf, - ssize_t bufsize) -{ - pkt_t pkt; - struct krb5_handle *kh = cookie; - - assert(kh != NULL); - - /* - * We need to cancel the recvpkt request before calling - * the callback because the callback may reschedule us. - */ - krb5_recvpkt_cancel(kh); - - switch (bufsize) { - case 0: - security_seterror(&kh->sech, - "EOF on read from %s", kh->hostname); - (*kh->fn.recvpkt)(kh->arg, NULL, S_ERROR); - return; - case -1: - security_seterror(&kh->sech, security_stream_geterror(&kh->ks->secstr)); - (*kh->fn.recvpkt)(kh->arg, NULL, S_ERROR); - return; - default: - parse_pkt(&pkt, buf, (size_t)bufsize); - k5printf(("krb5: received %s pkt (%d) from %s, contains:\n\n\"%s\"\n\n", - pkt_type2str(pkt.type), pkt.type, kh->hostname, pkt.body)); - (*kh->fn.recvpkt)(kh->arg, &pkt, S_OK); - return; - } -} - -/* - * This is called when a handle times out before receiving a packet. - */ -static void -recvpkt_timeout( - void * cookie) -{ - struct krb5_handle *kh = cookie; - - assert(kh != NULL); - - k5printf(("krb5: recvpkt timeout for %s\n", kh->hostname)); - - krb5_recvpkt_cancel(kh); - (*kh->fn.recvpkt)(kh->arg, NULL, S_TIMEOUT); -} - -/* - * Create the server end of a stream. For krb5, this means setup a stream - * object and allocate a new handle for it. - */ -static void * -krb5_stream_server( - void * h) -{ - struct krb5_handle *kh = h; - struct krb5_stream *ks; - - assert(kh != NULL); - - ks = alloc(SIZEOF(*ks)); - security_streaminit(&ks->secstr, &krb5_security_driver); - ks->kc = conn_get(kh->hostname); - /* - * Stream should already be setup! - */ - if (ks->kc->fd < 0) { - conn_put(ks->kc); - amfree(ks); - security_seterror(&kh->sech, "lost connection"); - return (NULL); - } - /* - * so as not to conflict with the amanda server's handle numbers, - * we start at 5000 and work down - */ - ks->handle = (int)(5000 - newhandle++); - ks->ev_read = NULL; - k5printf(("krb5: stream_server: created stream %d\n", ks->handle)); - return (ks); -} - -/* - * Accept an incoming connection on a stream_server socket - * Nothing needed for krb5. + * Forks a krb5 to the host listed in rc->hostname + * Returns negative on error, with an errmsg in rc->errmsg. */ static int -krb5_stream_accept( - void * s) +runkrb5( + struct sec_handle * rh) { + struct servent * sp; + int server_socket; + in_port_t my_port, port; + uid_t euid; + struct tcp_conn * rc = rh->rc; + const char *err; - /* shut up compiler */ - s = s; - - return (0); -} - -/* - * Return a connected stream. For krb5, this means setup a stream - * with the supplied handle. - */ -static void * -krb5_stream_client( - void * h, - int id) -{ - struct krb5_handle *kh = h; - struct krb5_stream *ks; + if ((sp = getservbyname(AMANDA_KRB5_SERVICE_NAME, "tcp")) == NULL) + port = htons(AMANDA_KRB5_DEFAULT_PORT); + else + port = sp->s_port; - assert(kh != NULL); + euid = geteuid(); - if (id <= 0) { - security_seterror(&kh->sech, - "%d: invalid security stream id", id); - return (NULL); + if ((err = get_tgt(keytab_name, principal_name)) != NULL) { + security_seterror(&rh->sech, "%s: could not get TGT: %s", + rc->hostname, err); + return -1; } - ks = alloc(SIZEOF(*ks)); - security_streaminit(&ks->secstr, &krb5_security_driver); - ks->handle = (int)id; - ks->ev_read = NULL; - ks->kc = conn_get(kh->hostname); - - k5printf(("krb5: stream_client: connected to stream %d\n", id)); - - return (ks); -} - -/* - * Close and unallocate resources for a stream. - */ -static void -krb5_stream_close( - void * s) -{ - struct krb5_stream *ks = s; - - assert(ks != NULL); - - k5printf(("krb5: stream_close: closing stream %d\n", ks->handle)); - - krb5_stream_read_cancel(ks); - conn_put(ks->kc); - amfree(ks); -} - -/* - * Authenticate a stream - * Nothing needed for krb5. The tcp connection is authenticated - * on startup. - */ -static int -krb5_stream_auth( - void * s) -{ - /* shut up compiler */ - s = s; - - return (0); -} - -/* - * Returns the stream id for this stream. This is just the local - * port. - */ -static int -krb5_stream_id( - void * s) -{ - struct krb5_stream *ks = s; - - assert(ks != NULL); - - return (ks->handle); -} - -/* - * Write a chunk of data to a stream. Blocks until completion. - */ -static int -krb5_stream_write( - void * s, - const void *buf, - size_t size) -{ - struct krb5_stream *ks = s; - gss_buffer_desc tok; -#ifdef AMANDA_KRB5_ENCRYPT - gss_buffer_desc enctok; - OM_uint32 min_stat; -#endif - int rc; - - assert(ks != NULL); - - k5printf(("krb5: stream_write: writing %d bytes to %s:%d\n", size, - ks->kc->hostname, ks->handle)); - - tok.length = size; - tok.value = (void *)buf; /* safe to discard const */ -#ifdef AMANDA_KRB5_ENCRYPT - if (kencrypt(ks, &tok, &enctok) < 0) - return (-1); - rc = send_token(ks->kc, ks->handle, &enctok); -#else - rc = send_token(ks->kc, ks->handle, &tok); -#endif - if (rc < 0) - security_stream_seterror(&ks->secstr, ks->kc->errmsg); -#ifdef AMANDA_KRB5_ENCRYPT - gss_release_buffer(&min_stat, &enctok); -#endif - return (rc); -} - -/* - * Submit a request to read some data. Calls back with the given - * function and arg when completed. - */ -static void -krb5_stream_read( - void * s, - void (*fn)(void *, void *, ssize_t), - void * arg) -{ - struct krb5_stream *ks = s; - - assert(ks != NULL); - - /* - * Only one read request can be active per stream. - */ - ks->fn = fn; - ks->arg = arg; - - /* - * First see if there's any queued frames for this stream. - * If so, we're done. - */ - if (conn_run_frameq(ks->kc, ks) > 0) - return; + server_socket = stream_client(rc->hostname, + (in_port_t)(ntohs(port)), + STREAM_BUFSIZE, + STREAM_BUFSIZE, + &my_port, + 0); - if (ks->ev_read == NULL) { - ks->ev_read = event_register((event_id_t)ks->kc, EV_WAIT, - stream_read_callback, ks); - conn_read(ks->kc); + if(server_socket < 0) { + security_seterror(&rh->sech, + "%s", strerror(errno)); + + return -1; } -} - -/* - * Submit a request to read some data. Calls back with the given - * function and arg when completed. - */ -static ssize_t -krb5_stream_read_sync( - void * s, - void **buf) -{ - struct krb5_stream *ks = s; - - assert(ks != NULL); - - /* - * Only one read request can be active per stream. - */ - ks->fn = stream_read_sync_callback2; - ks->arg = ks; - - /* - * First see if there's any queued frames for this stream. - * If so, we're done. - */ - if (conn_run_frameq(ks->kc, ks) > 0) - return ks->len; - - if (ks->ev_read != NULL) - event_release(ks->ev_read); - - ks->ev_read = event_register((event_id_t)ks->kc, EV_WAIT, - stream_read_callback, ks); - conn_read(ks->kc); - event_wait(ks->ev_read); - buf = (void **)&ks->buf; - return ks->len; -} - - -/* - * Callback for krb5_stream_read_sync - */ -static void -stream_read_sync_callback2( - void * arg, - void * buf, - ssize_t size) -{ - struct krb5_stream *ks = arg; - - assert(ks != NULL); - - k5printf(("krb5: stream_read_sync_callback2: handle %d\n", ks->handle)); - - memcpy(ks->buf, buf, (size_t)size); - ks->len = size; -} - -/* - * Cancel a previous stream read request. It's ok if we didn't have a read - * scheduled. - */ -static void -krb5_stream_read_cancel( - void * s) -{ - struct krb5_stream *ks = s; + seteuid(euid); - assert(ks != NULL); + rc->read = rc->write = server_socket; - if (ks->ev_read != NULL) { - event_release(ks->ev_read); - ks->ev_read = NULL; - conn_read_cancel(ks->kc); + if (gss_client(rh) < 0) { + return -1; } -} - -/* - * Callback for krb5_stream_read - */ -static void -stream_read_callback( - void * arg) -{ - struct krb5_stream *ks = arg; - - assert(ks != NULL); - k5printf(("krb5: stream_read_callback: handle %d\n", ks->handle)); - - conn_run_frameq(ks->kc, ks); + return 0; } -/* - * Run down a list of queued frames for a krb5_conn, and if we find one - * that matches the passed handle, fire the read event. Only - * process one frame. - * - * Returns 1 if a frame was found and processed. - */ -static int -conn_run_frameq( - /*@keep@*/ struct krb5_conn * kc, - /*@keep@*/ struct krb5_stream * ks) -{ - struct krb5_frame *kf, *nextkf; - gss_buffer_desc *enctok, *dectok; -#ifdef AMANDA_KRB5_ENCRYPT - OM_uint32 min_stat; - gss_buffer_desc tok; -#endif - - /* - * Iterate through all of the frames in the queue. If one - * is for us, process it. If we hit an EOF frame, shut down. - * Stop after processing one frame, because we are only supposed - * to return one read request. - */ - for (kf = TAILQ_FIRST(&kc->frameq); kf != NULL; kf = nextkf) { - nextkf = TAILQ_NEXT(kf, tq); - - if (kf->handle != ks->handle && kf->handle != H_EOF) { - k5printf(("krb5: conn_frameq_run: not for us (handle %d)\n", - kf->handle)); - continue; - } - /* - * We want all listeners to see the EOF, so never remove it. - * It will get cleaned up when the connection is closed - * in conn_put(). - */ - if (kf->handle != H_EOF) - TAILQ_REMOVE(&kc->frameq, kf, tq); - - /* - * 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. - */ - krb5_stream_read_cancel(ks); - - enctok = &kf->tok; - - if (enctok->length == 0) { - assert(kf->handle == H_EOF); - k5printf(("krb5: stream_read_callback: EOF\n")); - (*ks->fn)(ks->arg, NULL, 0); - return (1); /* stop after EOF */ - } - -#ifdef AMANDA_KRB5_ENCRYPT - dectok = &tok; - if (kdecrypt(ks, enctok, &tok) < 0) { - k5printf(("krb5: stream_read_callback: kdecrypt error\n")); - (*ks->fn)(ks->arg, NULL, -1); - } else -#else - dectok = enctok; -#endif - { - k5printf(("krb5: stream_read_callback: read %d bytes from %s:%d\n", - dectok->length, ks->kc->hostname, ks->handle)); - (*ks->fn)(ks->arg, dectok->value, (ssize_t)dectok->length); -#ifdef AMANDA_KRB5_ENCRYPT - gss_release_buffer(&min_stat, dectok); -#endif - } - amfree(enctok->value); - amfree(kf); - return (1); /* stop after one frame */ - } - return (0); -} - -/* - * The callback for the netfd for the event handler - * Determines if this packet is for this security handle, - * and does the real callback if so. - */ -static void -conn_read_callback( - void * cookie) -{ - struct krb5_conn *kc = cookie; - struct krb5_handle *kh; - struct krb5_frame *kf; - pkt_t pkt; - gss_buffer_desc *dectok; - int rc; -#ifdef AMANDA_KRB5_ENCRYPT - gss_buffer_desc tok; - OM_uint32 min_stat; -#endif - - assert(cookie != NULL); - - k5printf(("krb5: conn_read_callback\n")); - - kf = alloc(SIZEOF(*kf)); - TAILQ_INSERT_TAIL(&kc->frameq, kf, tq); - - /* Read the data off the wire. If we get errors, shut down. */ - rc = recv_token(kc, &kf->handle, &kf->tok, 5); - k5printf(("krb5: conn_read_callback: recv_token returned %d handle = %d\n", - rc, kf->handle)); - if (rc <= 0) { - kf->tok.value = NULL; - kf->tok.length = 0; - kf->handle = H_EOF; - rc = event_wakeup((event_id_t)kc); - k5printf(("krb5: conn_read_callback: event_wakeup return %d\n", rc)); - return; - } - - /* If there are events waiting on this handle, we're done */ - rc = event_wakeup((event_id_t)kc); - k5printf(("krb5: conn_read_callback: event_wakeup return %d\n", rc)); - if (rc > 0) - return; - - /* - * If there is no accept fn registered, then just leave the - * packet queued. The caller may register a function later. - */ - if (accept_fn == NULL) { - k5printf(("krb5: no accept_fn so leaving packet queued.\n")); - return; - } - - kh = alloc(SIZEOF(*kh)); - security_handleinit(&kh->sech, &krb5_security_driver); - kh->hostname = stralloc(kc->hostname); - kh->ks = krb5_stream_client(kh, kf->handle); - kh->ev_wait = NULL; - kh->ev_timeout = NULL; - - TAILQ_REMOVE(&kc->frameq, kf, tq); - k5printf(("krb5: new connection\n")); -#ifdef AMANDA_KRB5_ENCRYPT - dectok = &tok; - rc = kdecrypt(kh->ks, &kf->tok, dectok); -#else - dectok = &kf->tok; -#endif - -#ifdef AMANDA_KRB5_ENCRYPT - if (rc < 0) { - security_seterror(&kh->sech, security_geterror(&kh->ks->secstr)); - (*accept_fn)(&kh->sech, NULL); - } else -#endif - { - parse_pkt(&pkt, dectok->value, dectok->length); -#ifdef AMANDA_KRB5_ENCRYPT - gss_release_buffer(&min_stat, dectok); -#endif - (*accept_fn)(&kh->sech, &pkt); - } - amfree(kf->tok.value); - amfree(kf); - - /* - * We can only accept one connection per process, since we're tcp - * based and run out of inetd. So, delete our accept reference once - * we've gotten the first connection. - */ - - /* - * [XXX] actually, the protocol has been changed to have multiple - * requests in one session be possible. By not resetting accept_fn, - * this will caused them to be properly processed. this needs to be - * addressed in a much cleaner way. - */ - if (accept_fn != NULL) - conn_put(kc); - /* accept_fn = NULL; */ -} /* * Negotiate a krb5 gss context from the client end. */ static int gss_client( - struct krb5_handle *kh) + struct sec_handle *rh) { - struct krb5_stream *ks = kh->ks; - struct krb5_conn *kc = ks->kc; - gss_buffer_desc send_tok, recv_tok; + struct sec_stream *rs = rh->rs; + struct tcp_conn *rc = rs->rc; + gss_buffer_desc send_tok, recv_tok, AA; + gss_OID doid; OM_uint32 maj_stat, min_stat; unsigned int ret_flags; - int rc, rval = -1; + int rval = -1; + int rvalue; gss_name_t gss_name; + char *errmsg = NULL; k5printf(("gss_client\n")); - send_tok.value = vstralloc("host/", ks->kc->hostname, NULL); + send_tok.value = vstralloc("host/", rs->rc->hostname, NULL); send_tok.length = strlen(send_tok.value) + 1; maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID, &gss_name); if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { - security_seterror(&kh->sech, "can't import name %s: %s", + security_seterror(&rh->sech, "can't import name %s: %s", (char *)send_tok.value, gss_error(maj_stat, min_stat)); amfree(send_tok.value); return (-1); } amfree(send_tok.value); - kc->gss_context = GSS_C_NO_CONTEXT; + rc->gss_context = GSS_C_NO_CONTEXT; + maj_stat = gss_display_name(&min_stat, gss_name, &AA, &doid); + dbprintf(("gss_name %s\n", (char *)AA.value)); /* * Perform the context-establishement loop. @@ -1399,7 +503,7 @@ gss_client( min_stat = 0; maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, - &kc->gss_context, + &rc->gss_context, gss_name, GSS_C_NULL_OID, (OM_uint32)GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG, @@ -1414,19 +518,18 @@ gss_client( amfree(recv_tok.value); recv_tok.length = 0; } - if (maj_stat != (OM_uint32)GSS_S_COMPLETE && maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) { - security_seterror(&kh->sech, - "error getting gss context: %s", - gss_error(maj_stat, min_stat)); + security_seterror(&rh->sech, + "error getting gss context: %s %s", + gss_error(maj_stat, min_stat), (char *)send_tok.value); goto done; } /* * Send back the response */ - if (send_tok.length != 0 && send_token(kc, ks->handle, &send_tok) < 0) { - security_seterror(&kh->sech, kc->errmsg); + if (send_tok.length != 0 && tcpm_send_token(rc, rc->write, rs->handle, &errmsg, send_tok.value, send_tok.length) < 0) { + security_seterror(&rh->sech, rc->errmsg); gss_release_buffer(&min_stat, &send_tok); goto done; } @@ -1438,17 +541,21 @@ gss_client( if (maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) break; - if ((rc = recv_token(kc, NULL, &recv_tok, GSS_TIMEOUT)) <= 0) { - if (rc < 0) - security_seterror(&kh->sech, - "recv error in gss loop: %s", kc->errmsg); + rvalue = tcpm_recv_token(rc, rc->read, &rc->handle, &rc->errmsg, + (void *)&recv_tok.value, + (ssize_t *)&recv_tok.length, 60); + if (rvalue <= 0) { + if (rvalue < 0) + security_seterror(&rh->sech, + "recv error in gss loop: %s", rc->errmsg); else - security_seterror(&kh->sech, "EOF in gss loop"); + security_seterror(&rh->sech, "EOF in gss loop"); goto done; } } rval = 0; + rc->auth = 1; done: gss_release_name(&min_stat, &gss_name); return (rval); @@ -1459,21 +566,23 @@ done: */ static int gss_server( - struct krb5_conn * kc) + struct tcp_conn *rc) { OM_uint32 maj_stat, min_stat, ret_flags; - gss_buffer_desc send_tok, recv_tok; + gss_buffer_desc send_tok, recv_tok, AA; gss_OID doid; gss_name_t gss_name; gss_cred_id_t gss_creds; char *p, *realm, *msg; uid_t euid; - int rc, rval = -1; + int rval = -1; + int rvalue; char errbuf[256]; + char *errmsg = NULL; k5printf(("gss_server\n")); - assert(kc != NULL); + assert(rc != NULL); /* * We need to be root while in gss_acquire_cred() to read the host key @@ -1493,7 +602,8 @@ gss_server( goto out; } - send_tok.value = vstralloc("host/", hostname, NULL); + rc->gss_context = GSS_C_NO_CONTEXT; + send_tok.value = vstralloc("host/", myhostname, NULL); send_tok.length = strlen(send_tok.value) + 1; for (p = send_tok.value; *p != '\0'; p++) { if (isupper((int)*p)) @@ -1511,11 +621,13 @@ gss_server( } amfree(send_tok.value); + maj_stat = gss_display_name(&min_stat, gss_name, &AA, &doid); + dbprintf(("gss_name %s\n", (char *)AA.value)); maj_stat = gss_acquire_cred(&min_stat, gss_name, 0, GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &gss_creds, NULL, NULL); if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { snprintf(errbuf, SIZEOF(errbuf), - "can't acquire creds for host key host/%s: %s", hostname, + "can't acquire creds for host key host/%s: %s", myhostname, gss_error(maj_stat, min_stat)); gss_release_name(&min_stat, &gss_name); seteuid(euid); @@ -1524,17 +636,21 @@ gss_server( gss_release_name(&min_stat, &gss_name); for (recv_tok.length = 0;;) { - if ((rc = recv_token(kc, NULL, &recv_tok, GSS_TIMEOUT)) <= 0) { - if (rc < 0) { + recv_tok.value = NULL; + rvalue = tcpm_recv_token(rc, rc->read, &rc->handle, &rc->errmsg, + (char **)&recv_tok.value, + (ssize_t *)&recv_tok.length, 60); + if (rvalue <= 0) { + if (rvalue < 0) { snprintf(errbuf, SIZEOF(errbuf), - "recv error in gss loop: %s", kc->errmsg); - amfree(kc->errmsg); + "recv error in gss loop: %s", rc->errmsg); + amfree(rc->errmsg); } else snprintf(errbuf, SIZEOF(errbuf), "EOF in gss loop"); goto out; } - maj_stat = gss_accept_sec_context(&min_stat, &kc->gss_context, + maj_stat = gss_accept_sec_context(&min_stat, &rc->gss_context, gss_creds, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &gss_name, &doid, &send_tok, &ret_flags, NULL, NULL); @@ -1547,10 +663,10 @@ gss_server( } amfree(recv_tok.value); - if (send_tok.length > 0 && send_token(kc, 0, &send_tok) < 0) { - strncpy(errbuf, kc->errmsg, SIZEOF(errbuf) - 1); + if (send_tok.length != 0 && tcpm_send_token(rc, rc->write, 0, &errmsg, send_tok.value, send_tok.length) < 0) { + strncpy(errbuf, rc->errmsg, SIZEOF(errbuf) - 1); errbuf[SIZEOF(errbuf) - 1] = '\0'; - amfree(kc->errmsg); + amfree(rc->errmsg); gss_release_buffer(&min_stat, &send_tok); goto out; } @@ -1587,7 +703,7 @@ gss_server( /* * If the principal doesn't match, complain */ - if ((msg = krb5_checkuser(kc->hostname, send_tok.value, realm)) != NULL) { + if ((msg = krb5_checkuser(rc->hostname, send_tok.value, realm)) != NULL) { snprintf(errbuf, SIZEOF(errbuf), "access not allowed from %s: %s", (char *)send_tok.value, msg); amfree(send_tok.value); @@ -1598,8 +714,11 @@ gss_server( rval = 0; out: seteuid(euid); - if (rval != 0) - kc->errmsg = stralloc(errbuf); + if (rval != 0) { + rc->errmsg = stralloc(errbuf); + } else { + rc->auth = 1; + } k5printf(("gss_server returning %d\n", rval)); return (rval); } @@ -1608,18 +727,18 @@ out: * Setup some things about krb5. This should only be called once. */ static void -init(void) +krb5_init(void) { static int beenhere = 0; - struct hostent *he; char *p; + char *myfqhostname=NULL; if (beenhere) return; beenhere = 1; #ifndef BROKEN_MEMORY_CCACHE - setenv(KRB5_ENV_CCNAME, "MEMORY:amanda_ccache", 1); + putenv(stralloc("KRB5_ENV_CCNAME=MEMORY:amanda_ccache")); #else /* * MEMORY ccaches seem buggy and cause a lot of internal heap @@ -1631,26 +750,34 @@ init(void) */ atexit(cleanup); { - char ccache[64]; - snprintf(ccache, SIZEOF(ccache), "FILE:/tmp/amanda_ccache.%ld.%ld", - (long)geteuid(), (long)getpid()); - setenv(KRB5_ENV_CCNAME, ccache, 1); + char *ccache; + ccache = malloc(128); + snprintf(ccache, SIZEOF(ccache), + "KRB5_ENV_CCNAME=FILE:/tmp/amanda_ccache.%ld.%ld", + (long)geteuid(), (long)getpid()); + putenv(ccache); } #endif - gethostname(hostname, SIZEOF(hostname) - 1); - hostname[SIZEOF(hostname) - 1] = '\0'; + gethostname(myhostname, SIZEOF(myhostname) - 1); + myhostname[SIZEOF(myhostname) - 1] = '\0'; + /* - * In case it isn't fully qualified, do a DNS lookup. + * In case it isn't fully qualified, do a DNS lookup. Ignore + * any errors (this is best-effort). */ - if ((he = gethostbyname(hostname)) != NULL) - strncpy(hostname, he->h_name, SIZEOF(hostname) - 1); + if (try_resolving_hostname(myhostname, &myfqhostname) == 0 + && myfqhostname != NULL) { + strncpy(myhostname, myfqhostname, SIZEOF(myhostname)-1); + myhostname[SIZEOF(myhostname)-1] = '\0'; + amfree(myfqhostname); + } /* * Lowercase the results. We assume all host/ principals will be * lowercased. */ - for (p = hostname; *p != '\0'; p++) { + for (p = myhostname; *p != '\0'; p++) { if (isupper((int)*p)) *p = tolower(*p); } @@ -1683,17 +810,20 @@ get_tgt( krb5_error_code ret; krb5_principal client = NULL, server = NULL; krb5_creds creds; - krb5_keytab keytab = NULL; + krb5_keytab keytab; krb5_ccache ccache; krb5_timestamp now; +#ifdef KRB5_HEIMDAL_INCLUDES + krb5_data tgtname = { KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME }; +#else krb5_data tgtname = { 0, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME }; +#endif static char *error = NULL; if (error != NULL) { amfree(error); error = NULL; } - if ((ret = krb5_init_context(&context)) != 0) { error = vstralloc("error initializing krb5 context: ", error_message(ret), NULL); @@ -1716,7 +846,7 @@ get_tgt( * Resolve keytab file into a keytab object */ if ((ret = krb5_kt_resolve(context, keytab_name, &keytab)) != 0) { - error = vstralloc("error resolving keytab ", keytab, ": ", + error = vstralloc("error resolving keytab ", keytab_name, ": ", error_message(ret), NULL); return (error); } @@ -1732,6 +862,15 @@ get_tgt( return (error); } +#ifdef KRB5_HEIMDAL_INCLUDES + ret = krb5_build_principal_ext(context, &server, + krb5_realm_length(*krb5_princ_realm(context, client)), + krb5_realm_data(*krb5_princ_realm(context, client)), + tgtname.length, tgtname.data, + krb5_realm_length(*krb5_princ_realm(context, client)), + krb5_realm_data(*krb5_princ_realm(context, client)), + 0); +#else ret = krb5_build_principal_ext(context, &server, krb5_princ_realm(context, client)->length, krb5_princ_realm(context, client)->data, @@ -1739,6 +878,7 @@ get_tgt( krb5_princ_realm(context, client)->length, krb5_princ_realm(context, client)->data, 0); +#endif if (ret != 0) { error = vstralloc("error while building server name: ", error_message(ret), NULL); @@ -1798,6 +938,7 @@ cleanup2: return (error); } +#ifndef KDESTROY_VIA_UNLINK /* * get rid of tickets */ @@ -1821,6 +962,7 @@ cleanup: krb5_free_context(context); return; } +#endif /* * Formats an error from the gss api @@ -1846,166 +988,88 @@ gss_error( return ((const char *)msg.value); } -/* - * Transmits a gss_buffer_desc over a krb5_handle, adding - * the necessary headers to allow the remote end to decode it. - * Encryption must be done by the caller. - */ +#ifdef AMANDA_KRB5_ENCRYPT static int -send_token( - struct krb5_conn * kc, - int handle, - const gss_buffer_desc * tok) +k5_encrypt( + void *cookie, + void *buf, + ssize_t buflen, + void **encbuf, + ssize_t *encbuflen) { - OM_uint32 netlength, nethandle; - struct iovec iov[3]; - - k5printf(("krb5: send_token: writing %d bytes to %s\n", tok->length, - kc->hostname)); - - if (tok->length > AMANDA_MAX_TOK_SIZE) { - kc->errmsg = newvstralloc(kc->errmsg, "krb5 write error to ", - kc->hostname, ": token too large", NULL); - return (-1); - } - - /* - * Format is: - * 32 bit length (network byte order) - * 32 bit handle (network byte order) - * data - */ - netlength = (OM_uint32)htonl(tok->length); - iov[0].iov_base = (void *)&netlength; - iov[0].iov_len = SIZEOF(netlength); + struct tcp_conn *rc = cookie; + gss_buffer_desc dectok; + gss_buffer_desc enctok; + OM_uint32 maj_stat, min_stat; + int conf_state; - nethandle = (OM_uint32)htonl((uint32_t)handle); - iov[1].iov_base = (void *)&nethandle; - iov[1].iov_len = SIZEOF(nethandle); + k5printf(("krb5: k5_encrypt: enter %p\n", rc)); - iov[2].iov_base = (void *)tok->value; - iov[2].iov_len = tok->length; + dectok.length = buflen; + dectok.value = buf; - if (net_writev(kc->fd, iov, 3) < 0) { - kc->errmsg = newvstralloc(kc->errmsg, "krb5 write error to ", - kc->hostname, ": ", strerror(errno), NULL); - return (-1); + if (rc->auth == 1) { + assert(rc->gss_context != GSS_C_NO_CONTEXT); + maj_stat = gss_seal(&min_stat, rc->gss_context, 1, + GSS_C_QOP_DEFAULT, &dectok, &conf_state, &enctok); + if (maj_stat != (OM_uint32)GSS_S_COMPLETE || conf_state == 0) { + k5printf(("krb5 encrypt error to %s: %s\n", + rc->hostname, gss_error(maj_stat, min_stat))); + return (-1); + } + k5printf(("krb5: k5_encrypt: give %zu bytes\n", enctok.length)); + *encbuf = enctok.value; + *encbuflen = enctok.length; + } else { + *encbuf = buf; + *encbuflen = buflen; } + k5printf(("krb5: k5_encrypt: exit\n")); return (0); } -static ssize_t -recv_token( - struct krb5_conn * kc, - int * handle, - gss_buffer_desc * gtok, - int timeout) -{ - OM_uint32 netint[2]; - - assert(kc->fd >= 0); - assert(gtok != NULL); - - k5printf(("krb5: recv_token: reading from %s\n", kc->hostname)); - - switch (net_read(kc->fd, &netint, SIZEOF(netint), timeout)) { - case -1: - kc->errmsg = newvstralloc(kc->errmsg, "recv error: ", strerror(errno), - NULL); - k5printf(("krb5 recv_token error return: %s\n", kc->errmsg)); - return (-1); - case 0: - gtok->length = 0; - return (0); - default: - break; - } - gtok->length = ntohl(netint[0]); - - if (gtok->length > AMANDA_MAX_TOK_SIZE) { - kc->errmsg = newstralloc(kc->errmsg, "recv error: buffer too large"); - k5printf(("krb5 recv_token error return: %s\n", kc->errmsg)); - return (-1); - } - - if (handle != NULL) - *handle = ntohl(netint[1]); - - gtok->value = alloc(gtok->length); - switch (net_read(kc->fd, gtok->value, gtok->length, timeout)) { - case -1: - kc->errmsg = newvstralloc(kc->errmsg, "recv error: ", strerror(errno), - NULL); - k5printf(("krb5 recv_token error return: %s\n", kc->errmsg)); - amfree(gtok->value); - return (-1); - case 0: - amfree(gtok->value); - gtok->length = 0; - break; - default: - break; - } - - k5printf(("krb5: recv_token: read %d bytes from %s\n", gtok->length, - kc->hostname)); - return ((ssize_t)gtok->length); -} - -#ifdef AMANDA_KRB5_ENCRYPT static int -kencrypt( - struct krb5_stream *ks, - gss_buffer_desc * tok, - gss_buffer_desc * enctok) +k5_decrypt( + void *cookie, + void *buf, + ssize_t buflen, + void **decbuf, + ssize_t *decbuflen) { - int conf_state; + struct tcp_conn *rc = cookie; + gss_buffer_desc enctok; + gss_buffer_desc dectok; OM_uint32 maj_stat, min_stat; + int conf_state, qop_state; - assert(ks->kc->gss_context != GSS_C_NO_CONTEXT); - maj_stat = gss_seal(&min_stat, ks->kc->gss_context, 1, - GSS_C_QOP_DEFAULT, tok, &conf_state, enctok); - if (maj_stat != (OM_uint32)GSS_S_COMPLETE || conf_state == 0) { - security_stream_seterror(&ks->secstr, - "krb5 encryption failed to %s: %s", - ks->kc->hostname, gss_error(maj_stat, min_stat)); - return (-1); - } - return (0); -} + k5printf(("krb5: k5_decrypt: enter\n")); -static int -kdecrypt( - struct krb5_stream *ks, - gss_buffer_desc * enctok, - gss_buffer_desc * tok) -{ - OM_uint32 maj_stat, min_stat; - int conf_state, qop_state; + if (rc->auth == 1) { + enctok.length = buflen; + enctok.value = buf; - k5printf(("krb5: kdecrypt: decrypting %d bytes\n", enctok->length)); + k5printf(("krb5: k5_decrypt: decrypting %zu bytes\n", enctok.length)); - assert(ks->kc->gss_context != GSS_C_NO_CONTEXT); - maj_stat = gss_unseal(&min_stat, ks->kc->gss_context, enctok, tok, - &conf_state, &qop_state); - if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { - security_stream_seterror(&ks->secstr, "krb5 decrypt error from %s: %s", - ks->kc->hostname, gss_error(maj_stat, min_stat)); - return (-1); + assert(rc->gss_context != GSS_C_NO_CONTEXT); + maj_stat = gss_unseal(&min_stat, rc->gss_context, &enctok, &dectok, + &conf_state, &qop_state); + if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { + k5printf(("krb5 decrypt error from %s: %s\n", + rc->hostname, gss_error(maj_stat, min_stat))); + return (-1); + } + k5printf(("krb5: k5_decrypt: give %zu bytes\n", dectok.length)); + *decbuf = dectok.value; + *decbuflen = dectok.length; + } else { + *decbuf = buf; + *decbuflen = buflen; } + k5printf(("krb5: k5_decrypt: exit\n")); return (0); } #endif -/* - * hackish, but you can #undef AMANDA_PRINCIPAL here, and you can both - * hardcode a principal in your build and use the .k5amandahosts. This is - * available because sites that run pre-releases of amanda 2.5.0 before - * this feature was there do not behave this way... - */ - -/*#undef AMANDA_PRINCIPAL*/ - /* * check ~/.k5amandahosts to see if this principal is allowed in. If it's * hardcoded, then we don't check the realm