X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=common-src%2Fkrb5-security.c;h=c3075fa9bc2456d0aafd8afec557e4138f42d337;hb=b116e9366c7b2ea2c2eb53b0a13df4090e176235;hp=b7e6e710caaccad17d03783922071156b5aeafff;hpb=12179dea039515c06168c0037d048566a3f623de;p=debian%2Famanda diff --git a/common-src/krb5-security.c b/common-src/krb5-security.c index b7e6e71..c3075fa 100644 --- a/common-src/krb5-security.c +++ b/common-src/krb5-security.c @@ -28,22 +28,25 @@ * $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" #include "security.h" #include "security-util.h" #include "stream.h" -#include "version.h" +#include "sockaddr-util.h" + +#ifdef KRB5_HEIMDAL_INCLUDES +#include "com_err.h" +#endif -#define BROKEN_MEMORY_CCACHE +#define BROKEN_MEMORY_CCACHE #ifdef BROKEN_MEMORY_CCACHE /* @@ -59,7 +62,6 @@ #endif /* HAVE_ON_EXIT */ #endif /* ! HAVE_ATEXIT */ #endif - #ifndef KRB5_HEIMDAL_INCLUDES #include #else @@ -68,1317 +70,398 @@ #include #ifndef KRB5_ENV_CCNAME -#define KRB5_ENV_CCNAME "KRB5CCNAME" -#endif - -/*#define KRB5_DEBUG*/ - -#ifdef KRB5_DEBUG -#define k5printf(x) dbprintf(x) -#else -#define k5printf(x) +#define KRB5_ENV_CCNAME "KRB5CCNAME" #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 this if you want all network traffic encrypted. This will - * extract a serious performance hit. - * - * It would be nice if we could do this on a filesystem-by-filesystem basis. - */ -/*#define AMANDA_KRB5_ENCRYPT*/ +#define KDESTROY_VIA_UNLINK 1 /* * 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 - -/* - * 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 GSS_TIMEOUT 30 /* * This is the tcp stream buffer size - */ -#define KRB5_STREAM_BUFSIZE (MAX_TAPE_BLOCK_BYTES * 2) - -/* - * This is the max number of outgoing connections we can have at once. - * planner/amcheck/etc will open a bunch of connections as it tries - * 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 - -/* - * 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. - */ -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; - -/* - * This is the private handle data. - */ -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 */ -}; - -/* - * 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 *); - -/* - * This is our interface to the outside world. - */ -const security_driver_t krb5_security_driver = { - "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 -}; -#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 *); - - -/* - * krb5 version of a security handle allocator. Logically sets - * up a network "connection". - */ -static void -krb5_connect( - const char *hostname, - char * (*conf_fn)(char *, void *), - void (*fn)(void *, security_handle_t *, security_status_t), - 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; - - assert(hostname != NULL); - - k5printf(("krb5_connect: %s\n", hostname)); - - /* - * Make sure we're initted - */ - init(); - - 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; - -#ifdef AMANDA_KEYTAB - keytab_name = AMANDA_KEYTAB; -#else - if(conf_fn) { - keytab_name = conf_fn("krb5keytab", datap); - } -#endif -#ifdef AMANDA_PRINCIPAL - principal_name = AMANDA_PRINCIPAL; -#else - if(conf_fn) { - 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)); - goto error; - } - kh->ks->kc->fd = fd; - } - /* - * 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. - * 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); - - 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); -} - -/* - * Setup to handle new incoming connections - */ -static void -krb5_accept( - const struct security_driver *driver, - int in, - int out, - void (*fn)(security_handle_t *, pkt_t *)) -{ - struct sockaddr_in sin; - socklen_t len; - struct krb5_conn *kc; - struct hostent *he; - - /* - * Make sure we're initted - */ - init(); - - /* shut up compiler */ - driver=driver; - out=out; - - len = SIZEOF(sin); - if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) - 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)); - 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)); - 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); -} - -/* - * 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. - */ -static int -krb5_stream_accept( - void * s) -{ - - /* 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; - - assert(kh != NULL); - - if (id <= 0) { - security_seterror(&kh->sech, - "%d: invalid security stream id", id); - return (NULL); - } - - 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; - - 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); - } -} + */ +#define KRB5_STREAM_BUFSIZE (32768 * 2) /* - * Submit a request to read some data. Calls back with the given - * function and arg when completed. + * This is the max number of outgoing connections we can have at once. + * planner/amcheck/etc will open a bunch of connections as it tries + * to contact everything. We need to limit this to avoid blowing + * the max number of open file descriptors a process can have. */ -static ssize_t -krb5_stream_read_sync( - void * s, - void **buf) -{ - struct krb5_stream *ks = s; +#define AMANDA_KRB5_MAXCONN 40 - assert(ks != NULL); - /* - * Only one read request can be active per stream. - */ - ks->fn = stream_read_sync_callback2; - ks->arg = ks; +/* + * Number of seconds krb5 has to start up + */ +#define CONNECT_TIMEOUT 20 - /* - * 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; -} +/* + * Cache the local hostname + */ +static char myhostname[MAX_HOSTNAME_LENGTH+1]; /* - * Callback for krb5_stream_read_sync + * Interface functions */ -static void -stream_read_sync_callback2( - void * arg, - void * buf, - ssize_t size) -{ - struct krb5_stream *ks = arg; +static void krb5_accept(const struct security_driver *, + char *(*)(char *, void *), + int, int, + void (*)(security_handle_t *, pkt_t *), + void *); +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); - assert(ks != NULL); +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); - k5printf(("krb5: stream_read_sync_callback2: handle %d\n", ks->handle)); +static ssize_t krb5_tcpm_recv_token(struct tcp_conn *rc, int fd, int *handle, + char **errmsg, char **buf, ssize_t *size, + int timeout); +/* + * This is our interface to the outside world. + */ +const security_driver_t krb5_security_driver = { + "KRB5", + krb5_connect, + krb5_accept, + sec_get_authenticated_peer_name_hostname, + 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, + k5_encrypt, + k5_decrypt, +}; - memcpy(ks->buf, buf, (size_t)size); - ks->len = size; -} +static int newhandle = 1; /* - * Cancel a previous stream read request. It's ok if we didn't have a read - * scheduled. + * Local functions */ -static void -krb5_stream_read_cancel( - void * s) -{ - struct krb5_stream *ks = s; - - assert(ks != NULL); +static int runkrb5(struct sec_handle *); - if (ks->ev_read != NULL) { - event_release(ks->ev_read); - ks->ev_read = NULL; - conn_read_cancel(ks->kc); - } -} +char *keytab_name; +char *principal_name; /* - * Callback for krb5_stream_read + * krb5 version of a security handle allocator. Logically sets + * up a network "connection". */ static void -stream_read_callback( - void * arg) +krb5_connect( + const char *hostname, + char * (*conf_fn)(char *, void *), + void (*fn)(void *, security_handle_t *, security_status_t), + void * arg, + void * datap) { - struct krb5_stream *ks = arg; + struct sec_handle *rh; + int result; + char *canonname; - assert(ks != NULL); - - k5printf(("krb5: stream_read_callback: handle %d\n", ks->handle)); + assert(fn != NULL); + assert(hostname != NULL); - conn_run_frameq(ks->kc, ks); -} + auth_debug(1, "krb5: krb5_connect: %s\n", hostname); -/* - * 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 + krb5_init(); - /* - * 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); + rh = alloc(sizeof(*rh)); + security_handleinit(&rh->sech, &krb5_security_driver); + rh->hostname = NULL; + rh->rs = NULL; + rh->ev_timeout = NULL; + rh->rc = NULL; - 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); + result = resolve_hostname(hostname, 0, NULL, &canonname); + if(result != 0) { + dbprintf(_("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); + security_seterror(&rh->sech, _("resolve_hostname(%s): %s\n"), hostname, + gai_strerror(result)); + (*fn)(arg, &rh->sech, S_ERROR); + return; + } + if (canonname == NULL) { + dbprintf(_("resolve_hostname(%s) did not return a canonical name\n"), hostname); + security_seterror(&rh->sech, + _("resolve_hostname(%s) did not return a canonical name\n"), hostname); + (*fn)(arg, &rh->sech, S_ERROR); + return; + } - /* - * 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); + rh->hostname = canonname; /* will be replaced */ + canonname = NULL; /* steal reference */ + rh->rs = tcpma_stream_client(rh, newhandle++); + rh->rc->conf_fn = conf_fn; + rh->rc->datap = datap; + rh->rc->recv_security_ok = NULL; + rh->rc->prefix_packet = NULL; - enctok = &kf->tok; + if (rh->rs == NULL) + goto error; - 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 */ - } + amfree(rh->hostname); + rh->hostname = stralloc(rh->rs->rc->hostname); -#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 +#ifdef AMANDA_KEYTAB + keytab_name = AMANDA_KEYTAB; #else - dectok = enctok; + if(conf_fn) { + keytab_name = conf_fn("krb5keytab", datap); + } #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); +#ifdef AMANDA_PRINCIPAL + principal_name = AMANDA_PRINCIPAL; +#else + if(conf_fn) { + principal_name = conf_fn("krb5principal", datap); + } #endif - } - amfree(enctok->value); - amfree(kf); - return (1); /* stop after one frame */ + + /* + * 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; + rh->rc->refcnt++; } - return (0); + + /* + * 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. + */ + 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); + + amfree(canonname); + return; + +error: + amfree(canonname); + (*fn)(arg, &rh->sech, S_ERROR); } /* - * 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. + + * Setup to handle new incoming connections */ static void -conn_read_callback( - void * cookie) +krb5_accept( + const struct security_driver *driver, + char *(*conf_fn)(char *, void *), + int in, + int out, + void (*fn)(security_handle_t *, pkt_t *), + void *datap) { - 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 + sockaddr_union sin; + socklen_t_equiv len; + struct tcp_conn *rc; + char hostname[NI_MAXHOST]; + int result; + char *errmsg = NULL; + + krb5_init(); + + len = sizeof(sin); + if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) { + dbprintf(_("getpeername returned: %s\n"), + strerror(errno)); + return; - assert(cookie != NULL); + } + if ((result = getnameinfo((struct sockaddr *)&sin, len, + hostname, NI_MAXHOST, NULL, 0, 0) != 0)) { + dbprintf(_("getnameinfo failed: %s\n"), + gai_strerror(result)); + return; + } + if (check_name_give_sockaddr(hostname, + (struct sockaddr *)&sin, &errmsg) < 0) { + dbprintf(_("check_name_give_sockaddr(%s): %s\n"), + hostname, errmsg); + amfree(errmsg); + return; + } - k5printf(("krb5: conn_read_callback\n")); - kf = alloc(SIZEOF(*kf)); - TAILQ_INSERT_TAIL(&kc->frameq, kf, tq); + rc = sec_tcp_conn_get(hostname, 0); + rc->conf_fn = conf_fn; + rc->datap = datap; + rc->recv_security_ok = NULL; + rc->prefix_packet = NULL; + copy_sockaddr(&rc->peer, &sin); + 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); +} - /* 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; +/* + * Forks a krb5 to the host listed in rc->hostname + * Returns negative on error, with an errmsg in rc->errmsg. + */ +static int +runkrb5( + struct sec_handle * rh) +{ + struct servent * sp; + int server_socket; + in_port_t my_port, port; + struct tcp_conn * rc = rh->rc; + const char *err; + + if ((sp = getservbyname(AMANDA_KRB5_SERVICE_NAME, "tcp")) == NULL) + port = htons(AMANDA_KRB5_DEFAULT_PORT); + else + port = sp->s_port; + + if ((err = get_tgt(keytab_name, principal_name)) != NULL) { + security_seterror(&rh->sech, "%s: could not get TGT: %s", + rc->hostname, err); + return -1; } - /* 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; + set_root_privs(1); + server_socket = stream_client(rc->hostname, + (in_port_t)(ntohs(port)), + STREAM_BUFSIZE, + STREAM_BUFSIZE, + &my_port, + 0); + set_root_privs(0); - /* - * 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; + if(server_socket < 0) { + security_seterror(&rh->sech, + "%s", strerror(errno)); + + return -1; } - 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 + rc->read = rc->write = server_socket; -#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); + if (gss_client(rh) < 0) { + return -1; } - 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; */ + return 0; } + + /* + * 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")); + auth_debug(1, "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 +482,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 +497,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, "%s", rc->errmsg); gss_release_buffer(&min_stat, &send_tok); goto done; } @@ -1438,17 +520,22 @@ 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 = krb5_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,41 +546,36 @@ 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")); + auth_debug(1, "gss_server\n"); - assert(kc != NULL); + assert(rc != NULL); /* * We need to be root while in gss_acquire_cred() to read the host key * out of the default keytab. We also need to be root in * gss_accept_context() thanks to the replay cache code. */ - euid = geteuid(); - if (getuid() != 0) { - snprintf(errbuf, SIZEOF(errbuf), - "real uid is %ld, needs to be 0 to read krb5 host key", - (long)getuid()); - goto out; - } - if (seteuid(0) < 0) { - snprintf(errbuf, SIZEOF(errbuf), - "can't seteuid to uid 0: %s", strerror(errno)); + if (!set_root_privs(0)) { + g_snprintf(errbuf, SIZEOF(errbuf), + _("can't take root privileges to read krb5 host key: %s"), strerror(errno)); 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)) @@ -1502,55 +584,63 @@ gss_server( maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID, &gss_name); if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { - seteuid(euid); - snprintf(errbuf, SIZEOF(errbuf), - "can't import name %s: %s", (char *)send_tok.value, + set_root_privs(0); + g_snprintf(errbuf, SIZEOF(errbuf), + _("can't import name %s: %s"), (char *)send_tok.value, gss_error(maj_stat, min_stat)); amfree(send_tok.value); goto out; } 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, + g_snprintf(errbuf, SIZEOF(errbuf), + _("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); + set_root_privs(0); goto out; } 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) { - snprintf(errbuf, SIZEOF(errbuf), - "recv error in gss loop: %s", kc->errmsg); - amfree(kc->errmsg); + recv_tok.value = NULL; + rvalue = krb5_tcpm_recv_token(rc, rc->read, &rc->handle, + &rc->errmsg, + /* (void *) is to avoid type-punning warning */ + (char **)(void *)&recv_tok.value, + (ssize_t *)&recv_tok.length, 60); + if (rvalue <= 0) { + if (rvalue < 0) { + g_snprintf(errbuf, SIZEOF(errbuf), + _("recv error in gss loop: %s"), rc->errmsg); + amfree(rc->errmsg); } else - snprintf(errbuf, SIZEOF(errbuf), "EOF in gss loop"); + g_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); if (maj_stat != (OM_uint32)GSS_S_COMPLETE && maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) { - snprintf(errbuf, SIZEOF(errbuf), - "error accepting context: %s", gss_error(maj_stat, min_stat)); + g_snprintf(errbuf, SIZEOF(errbuf), + _("error accepting context: %s"), gss_error(maj_stat, min_stat)); amfree(recv_tok.value); goto out; } 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; } @@ -1567,8 +657,8 @@ gss_server( maj_stat = gss_display_name(&min_stat, gss_name, &send_tok, &doid); if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { - snprintf(errbuf, SIZEOF(errbuf), - "can't display gss name: %s", gss_error(maj_stat, min_stat)); + g_snprintf(errbuf, SIZEOF(errbuf), + _("can't display gss name: %s"), gss_error(maj_stat, min_stat)); gss_release_name(&min_stat, &gss_name); goto out; } @@ -1576,8 +666,8 @@ gss_server( /* get rid of the realm */ if ((p = strchr(send_tok.value, '@')) == NULL) { - snprintf(errbuf, SIZEOF(errbuf), - "malformed gss name: %s", (char *)send_tok.value); + g_snprintf(errbuf, SIZEOF(errbuf), + _("malformed gss name: %s"), (char *)send_tok.value); amfree(send_tok.value); goto out; } @@ -1587,9 +677,9 @@ gss_server( /* * If the principal doesn't match, complain */ - if ((msg = krb5_checkuser(kc->hostname, send_tok.value, realm)) != NULL) { - snprintf(errbuf, SIZEOF(errbuf), - "access not allowed from %s: %s", (char *)send_tok.value, msg); + if ((msg = krb5_checkuser(rc->hostname, send_tok.value, realm)) != NULL) { + g_snprintf(errbuf, SIZEOF(errbuf), + _("access not allowed from %s: %s"), (char *)send_tok.value, msg); amfree(send_tok.value); goto out; } @@ -1597,10 +687,13 @@ gss_server( rval = 0; out: - seteuid(euid); - if (rval != 0) - kc->errmsg = stralloc(errbuf); - k5printf(("gss_server returning %d\n", rval)); + set_root_privs(0); + if (rval != 0) { + rc->errmsg = stralloc(errbuf); + } else { + rc->auth = 1; + } + auth_debug(1, _("gss_server returning %d\n"), rval); return (rval); } @@ -1608,18 +701,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 +724,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); + g_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 (resolve_hostname(myhostname, SOCK_STREAM, NULL, &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); } @@ -1662,7 +763,7 @@ cleanup(void) { #ifdef KDESTROY_VIA_UNLINK char ccache[64]; - snprintf(ccache, SIZEOF(ccache), "/tmp/amanda_ccache.%ld.%ld", + g_snprintf(ccache, SIZEOF(ccache), "/tmp/amanda_ccache.%ld.%ld", (long)geteuid(), (long)getpid()); unlink(ccache); #else @@ -1683,32 +784,35 @@ 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); + error = vstrallocf(_("error initializing krb5 context: %s"), + error_message(ret)); return (error); } /*krb5_init_ets(context);*/ if(!keytab_name) { - error = vstralloc("error -- no krb5 keytab defined", NULL); + error = vstrallocf(_("error -- no krb5 keytab defined")); return(error); } if(!principal_name) { - error = vstralloc("error -- no krb5 principal defined", NULL); + error = vstrallocf(_("error -- no krb5 principal defined")); return(error); } @@ -1716,8 +820,8 @@ 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_message(ret), NULL); + error = vstrallocf(_("error resolving keytab %s: %s"), keytab_name, + error_message(ret)); return (error); } @@ -1727,11 +831,20 @@ get_tgt( */ ret = krb5_parse_name(context, principal_name, &client); if (ret != 0) { - error = vstralloc("error parsing ", principal_name, ": ", - error_message(ret), NULL); + error = vstrallocf(_("error parsing %s: %s"), principal_name, + error_message(ret)); 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,16 +852,16 @@ 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); + error = vstrallocf(_("error while building server name: %s"), + error_message(ret)); return (error); } ret = krb5_timeofday(context, &now); if (ret != 0) { - error = vstralloc("error getting time of day: ", error_message(ret), - NULL); + error = vstrallocf(_("error getting time of day: %s"), error_message(ret)); return (error); } @@ -1766,24 +879,22 @@ get_tgt( keytab, 0, &creds, 0); if (ret != 0) { - error = vstralloc("error getting ticket for ", principal_name, - ": ", error_message(ret), NULL); + error = vstrallocf(_("error getting ticket for %s: %s"), + principal_name, error_message(ret)); goto cleanup2; } if ((ret = krb5_cc_default(context, &ccache)) != 0) { - error = vstralloc("error initializing ccache: ", error_message(ret), - NULL); + error = vstrallocf(_("error initializing ccache: %s"), error_message(ret)); goto cleanup; } if ((ret = krb5_cc_initialize(context, ccache, client)) != 0) { - error = vstralloc("error initializing ccache: ", error_message(ret), - NULL); + error = vstrallocf(_("error initializing ccache: %s"), error_message(ret)); goto cleanup; } if ((ret = krb5_cc_store_cred(context, ccache, &creds)) != 0) { - error = vstralloc("error storing creds in ccache: ", - error_message(ret), NULL); + error = vstrallocf(_("error storing creds in ccache: %s"), + error_message(ret)); /* FALLTHROUGH */ } krb5_cc_close(context, ccache); @@ -1798,6 +909,7 @@ cleanup2: return (error); } +#ifndef KDESTROY_VIA_UNLINK /* * get rid of tickets */ @@ -1821,6 +933,7 @@ cleanup: krb5_free_context(context); return; } +#endif /* * Formats an error from the gss api @@ -1846,165 +959,95 @@ 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. - */ -static int -send_token( - struct krb5_conn * kc, - int handle, - const gss_buffer_desc * tok) -{ - 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); - - nethandle = (OM_uint32)htonl((uint32_t)handle); - iov[1].iov_base = (void *)&nethandle; - iov[1].iov_len = SIZEOF(nethandle); - - iov[2].iov_base = (void *)tok->value; - iov[2].iov_len = tok->length; - - if (net_writev(kc->fd, iov, 3) < 0) { - kc->errmsg = newvstralloc(kc->errmsg, "krb5 write error to ", - kc->hostname, ": ", strerror(errno), NULL); - return (-1); - } - 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_encrypt( + void *cookie, + void *buf, + ssize_t buflen, + void **encbuf, + ssize_t *encbuflen) { - int conf_state; + struct tcp_conn *rc = cookie; + gss_buffer_desc dectok; + gss_buffer_desc enctok; OM_uint32 maj_stat, min_stat; + int conf_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); + if (rc->conf_fn && rc->conf_fn("kencrypt", rc->datap)) { + auth_debug(1, _("krb5: k5_encrypt: enter %p\n"), rc); + + dectok.length = buflen; + dectok.value = buf; + + 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) { + auth_debug(1, _("krb5 encrypt error to %s: %s\n"), + rc->hostname, gss_error(maj_stat, min_stat)); + return (-1); + } + auth_debug(1, _("krb5: k5_encrypt: give %zu bytes\n"), + enctok.length); + *encbuf = enctok.value; + *encbuflen = enctok.length; + } else { + *encbuf = buf; + *encbuflen = buflen; + } + auth_debug(1, _("krb5: k5_encrypt: exit\n")); } return (0); } + static int -kdecrypt( - struct krb5_stream *ks, - gss_buffer_desc * enctok, - gss_buffer_desc * tok) +k5_decrypt( + void *cookie, + void *buf, + ssize_t buflen, + void **decbuf, + ssize_t *decbuflen) { + struct tcp_conn *rc = cookie; + gss_buffer_desc enctok; + gss_buffer_desc dectok; OM_uint32 maj_stat, min_stat; int conf_state, qop_state; - k5printf(("krb5: kdecrypt: decrypting %d 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); + if (rc->conf_fn && rc->conf_fn("kencrypt", rc->datap)) { + auth_debug(1, _("krb5: k5_decrypt: enter\n")); + if (rc->auth == 1) { + enctok.length = buflen; + enctok.value = buf; + + auth_debug(1, _("krb5: k5_decrypt: decrypting %zu bytes\n"), enctok.length); + + 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) { + auth_debug(1, _("krb5 decrypt error from %s: %s\n"), + rc->hostname, gss_error(maj_stat, min_stat)); + return (-1); + } + auth_debug(1, _("krb5: k5_decrypt: give %zu bytes\n"), + dectok.length); + *decbuf = dectok.value; + *decbuflen = dectok.length; + } else { + *decbuf = buf; + *decbuflen = buflen; + } + auth_debug(1, _("krb5: k5_decrypt: exit\n")); + } else { + *decbuf = buf; + *decbuflen = buflen; } 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 @@ -2019,25 +1062,23 @@ krb5_checkuser( char * host, if(strcmp(name, AMANDA_PRINCIPAL) == 0) { return(NULL); } else { - return(vstralloc("does not match compiled in default")); + return(vstrallocf(_("does not match compiled in default"))); } #else struct passwd *pwd; char *ptmp; - char *result = "generic error"; /* default is to not permit */ + char *result = _("generic error"); /* default is to not permit */ FILE *fp = NULL; struct stat sbuf; uid_t localuid; char *line = NULL; char *filehost = NULL, *fileuser = NULL, *filerealm = NULL; - char n1[NUM_STR_SIZE]; - char n2[NUM_STR_SIZE]; assert( host != NULL); assert( name != NULL); if((pwd = getpwnam(CLIENT_LOGIN)) == NULL) { - result = vstralloc("can not find user ", CLIENT_LOGIN, NULL); + result = vstrallocf(_("can not find user %s"), CLIENT_LOGIN); } localuid = pwd->pw_uid; @@ -2048,7 +1089,7 @@ krb5_checkuser( char * host, #endif if(!ptmp) { - result = vstralloc("could not find home directory for ", CLIENT_LOGIN, NULL); + result = vstrallocf(_("could not find home directory for %s"), CLIENT_LOGIN); goto common_exit; } @@ -2061,40 +1102,36 @@ krb5_checkuser( char * host, * the destination user mimicing the .k5login functionality. */ if(strcmp(name, CLIENT_LOGIN) != 0) { - result = vstralloc(name, " does not match ", - CLIENT_LOGIN, NULL); + result = vstrallocf(_("%s does not match %s"), + name, CLIENT_LOGIN); return result; } result = NULL; goto common_exit; } - k5printf(("opening ptmp: %s\n", (ptmp)?ptmp: "NULL!")); + auth_debug(1, _("opening ptmp: %s\n"), (ptmp)?ptmp: "NULL!"); if((fp = fopen(ptmp, "r")) == NULL) { - result = vstralloc("can not open ", ptmp, NULL); + result = vstrallocf(_("can not open %s"), ptmp); return result; } - k5printf(("opened ptmp\n")); + auth_debug(1, _("opened ptmp\n")); if (fstat(fileno(fp), &sbuf) != 0) { - result = vstralloc("cannot fstat ", ptmp, ": ", strerror(errno), NULL); + result = vstrallocf(_("cannot fstat %s: %s"), ptmp, strerror(errno)); goto common_exit; } if (sbuf.st_uid != localuid) { - snprintf(n1, SIZEOF(n1), "%ld", (long) sbuf.st_uid); - snprintf(n2, SIZEOF(n2), "%ld", (long) localuid); - result = vstralloc(ptmp, ": ", - "owned by id ", n1, - ", should be ", n2, - NULL); + result = vstrallocf(_("%s is owned by %ld, should be %ld"), + ptmp, (long)sbuf.st_uid, (long)localuid); goto common_exit; } if ((sbuf.st_mode & 077) != 0) { - result = stralloc2(ptmp, - ": incorrect permissions; file must be accessible only by its owner"); + result = vstrallocf( + _("%s: incorrect permissions; file must be accessible only by its owner"), ptmp); goto common_exit; - } + } while ((line = agets(fp)) != NULL) { if (line[0] == '\0') { @@ -2102,9 +1139,6 @@ krb5_checkuser( char * host, continue; } -#if defined(SHOW_SECURITY_DETAIL) /* { */ - k5printf(("%s: processing line: <%s>\n", debug_prefix(NULL), line)); -#endif /* } */ /* if there's more than one column, then it's the host */ if( (filehost = strtok(line, " \t")) == NULL) { amfree(line); @@ -2124,7 +1158,7 @@ krb5_checkuser( char * host, amfree(line); continue; } else { - k5printf(("found a host match\n")); + auth_debug(1, _("found a host match\n")); } if( (filerealm = strchr(fileuser, '@')) != NULL) { @@ -2139,9 +1173,9 @@ krb5_checkuser( char * host, * You likely only get this far if you've turned on cross-realm auth * anyway... */ - k5printf(("comparing %s %s\n", fileuser, name)); + auth_debug(1, _("comparing %s %s\n"), fileuser, name); if(strcmp(fileuser, name) == 0) { - k5printf(("found a match!\n")); + auth_debug(1, _("found a match!\n")); if(realm && filerealm && (strcmp(realm, filerealm)!=0)) { amfree(line); continue; @@ -2152,7 +1186,7 @@ krb5_checkuser( char * host, } amfree(line); } - result = vstralloc("no match in ", ptmp, NULL); + result = vstrallocf(_("no match in %s"), ptmp); common_exit: afclose(fp); @@ -2160,13 +1194,121 @@ common_exit: #endif /* AMANDA_PRINCIPAL */ } -#else - -void krb5_security_dummy(void); +/* + * return -1 on error + * return 0 on EOF: *handle = H_EOF && *size = 0 if socket closed + * return 0 on EOF: *handle = handle && *size = 0 if stream closed + * return size : *handle = handle && *size = size for data read + */ -void -krb5_security_dummy(void) +static ssize_t +krb5_tcpm_recv_token( + struct tcp_conn *rc, + int fd, + int * handle, + char ** errmsg, + char ** buf, + ssize_t * size, + int timeout) { + unsigned int netint[2]; + + assert(SIZEOF(netint) == 8); + + switch (net_read(fd, &netint, SIZEOF(netint), timeout)) { + case -1: + if (errmsg) + *errmsg = newvstrallocf(*errmsg, _("recv error: %s"), strerror(errno)); + auth_debug(1, _("krb5_tcpm_recv_token: A return(-1)\n")); + return (-1); + case 0: + *size = 0; + *handle = H_EOF; + *errmsg = newvstrallocf(*errmsg, _("SOCKET_EOF")); + auth_debug(1, _("krb5_tcpm_recv_token: A return(0)\n")); + return (0); + default: + break; + } + + *size = (ssize_t)ntohl(netint[0]); + *handle = (int)ntohl(netint[1]); + /* amanda protocol packet can be above NETWORK_BLOCK_BYTES */ + if (*size > 128*NETWORK_BLOCK_BYTES || *size < 0) { + if (isprint((int)(*size ) & 0xFF) && + isprint((int)(*size >> 8 ) & 0xFF) && + isprint((int)(*size >> 16) & 0xFF) && + isprint((int)(*size >> 24) & 0xFF) && + isprint((*handle ) & 0xFF) && + isprint((*handle >> 8 ) & 0xFF) && + isprint((*handle >> 16) & 0xFF) && + isprint((*handle >> 24) & 0xFF)) { + char s[101]; + int i; + s[0] = ((int)(*size) >> 24) & 0xFF; + s[1] = ((int)(*size) >> 16) & 0xFF; + s[2] = ((int)(*size) >> 8) & 0xFF; + s[3] = ((int)(*size) ) & 0xFF; + s[4] = (*handle >> 24) & 0xFF; + s[5] = (*handle >> 16) & 0xFF; + s[6] = (*handle >> 8 ) & 0xFF; + s[7] = (*handle ) & 0xFF; + i = 8; s[i] = ' '; + while(i<100 && isprint((int)s[i]) && s[i] != '\n') { + switch(net_read(fd, &s[i], 1, 0)) { + case -1: s[i] = '\0'; break; + case 0: s[i] = '\0'; break; + default: + dbprintf(_("read: %c\n"), s[i]); i++; s[i]=' '; + break; + } + } + s[i] = '\0'; + *errmsg = newvstrallocf(*errmsg, _("krb5_tcpm_recv_token: invalid size: %s"), s); + dbprintf(_("krb5_tcpm_recv_token: invalid size %s\n"), s); + } else { + *errmsg = newvstrallocf(*errmsg, _("krb5_tcpm_recv_token: invalid size")); + dbprintf(_("krb5_tcpm_recv_token: invalid size %zd\n"), *size); + } + *size = -1; + return -1; + } + amfree(*buf); + *buf = alloc((size_t)*size); + + if(*size == 0) { + auth_debug(1, _("krb5_tcpm_recv_token: read EOF from %d\n"), *handle); + *errmsg = newvstrallocf(*errmsg, _("EOF")); + return 0; + } + switch (net_read(fd, *buf, (size_t)*size, timeout)) { + case -1: + if (errmsg) + *errmsg = newvstrallocf(*errmsg, _("recv error: %s"), strerror(errno)); + auth_debug(1, _("krb5_tcpm_recv_token: B return(-1)\n")); + return (-1); + case 0: + *size = 0; + *errmsg = newvstrallocf(*errmsg, _("SOCKET_EOF")); + auth_debug(1, _("krb5_tcpm_recv_token: B return(0)\n")); + return (0); + default: + break; + } + + auth_debug(1, _("krb5_tcpm_recv_token: read %zd bytes from %d\n"), *size, *handle); + + if (*size > 0 && rc->driver->data_decrypt != NULL) { + void *decbuf; + ssize_t decsize; + rc->driver->data_decrypt(rc, *buf, *size, &decbuf, &decsize); + if (*buf != (char *)decbuf) { + amfree(*buf); + *buf = (char *)decbuf; + } + *size = decsize; + } + + return((*size)); } -#endif /* KRB5_SECURITY */