X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=common-src%2Fkrb5-security.c;h=c3075fa9bc2456d0aafd8afec557e4138f42d337;hb=HEAD;hp=e1e78a0ca0138748c28a1930ebda8e5d0f823550;hpb=eefb15c5c04acb3c75f0c704ea664feb1bbae75c;p=debian%2Famanda diff --git a/common-src/krb5-security.c b/common-src/krb5-security.c index e1e78a0..c3075fa 100644 --- a/common-src/krb5-security.c +++ b/common-src/krb5-security.c @@ -25,23 +25,28 @@ */ /* - * $Id: krb5-security.c,v 1.12 2006/02/21 04:13:55 ktill Exp $ + * $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 "arglist.h" +#include "util.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 /* @@ -57,7 +62,6 @@ #endif /* HAVE_ON_EXIT */ #endif /* ! HAVE_ATEXIT */ #endif - #ifndef KRB5_HEIMDAL_INCLUDES #include #else @@ -66,79 +70,54 @@ #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) +#define KRB5_STREAM_BUFSIZE (32768 * 2) /* * This is the max number of outgoing connections we can have at once. @@ -146,1159 +125,343 @@ * 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) P((void *, pkt_t *, security_status_t)); - /* func to call when packet recvd */ - void (*connect) P((void *, security_handle_t *, security_status_t)); - /* func to call when connected */ - } fn; - void *arg; /* argument to pass function */ - event_handle_t *ev_wait; /* wait handle for connects */ - char *(*conf_fn) P((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) P((void *, void *, int)); /* read event fn */ - void *arg; /* arg for previous */ -}; /* * Interface functions */ -static int krb5_sendpkt P((void *, pkt_t *)); -static int krb5_stream_accept P((void *)); -static int krb5_stream_auth P((void *)); -static int krb5_stream_id P((void *)); -static int krb5_stream_write P((void *, const void *, size_t)); -static void *krb5_stream_client P((void *, int)); -static void *krb5_stream_server P((void *)); -static void krb5_accept P((int, int, - void (*)(security_handle_t *, pkt_t *))); -static void krb5_close P((void *)); -static void krb5_connect P((const char *, +static void krb5_accept(const struct security_driver *, char *(*)(char *, void *), - void (*)(void *, security_handle_t *, security_status_t), void *)); -static void krb5_recvpkt P((void *, - void (*)(void *, pkt_t *, security_status_t), void *, int)); -static void krb5_recvpkt_cancel P((void *)); -static void krb5_stream_close P((void *)); -static void krb5_stream_read P((void *, void (*)(void *, void *, int), - void *)); -static void krb5_stream_read_cancel P((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); + +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); + +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", 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_cancel, -}; - -/* - * 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; -} connq = { - TAILQ_HEAD_INITIALIZER(connq.tailq), 0 + 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, }; -#define connq_first() TAILQ_FIRST(&connq.tailq) -#define connq_next(kc) TAILQ_NEXT(kc, tq) -#define connq_append(kc) do { \ - TAILQ_INSERT_TAIL(&connq.tailq, kc, tq); \ - connq.qlength++; \ -} while (0) -#define connq_remove(kc) do { \ - assert(connq.qlength > 0); \ - TAILQ_REMOVE(&connq.tailq, kc, tq); \ - 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) P((security_handle_t *, pkt_t *)); - /* * Local functions */ -static void init P((void)); -#ifdef BROKEN_MEMORY_CCACHE -static void cleanup P((void)); -#endif -static const char *get_tgt P((char *, char *)); -static void open_callback P((void *)); -static void connect_callback P((void *)); -static void connect_timeout P((void *)); -static int send_token P((struct krb5_conn *, int, const gss_buffer_desc *)); -static int recv_token P((struct krb5_conn *, int *, gss_buffer_desc *, int)); -static void recvpkt_callback P((void *, void *, ssize_t)); -static void recvpkt_timeout P((void *)); -static void stream_read_callback P((void *)); -static int gss_server P((struct krb5_conn *)); -static int gss_client P((struct krb5_handle *)); -static const char *gss_error P((OM_uint32, OM_uint32)); - -#ifdef AMANDA_KRB5_ENCRYPT -static int kdecrypt P((struct krb5_stream *, gss_buffer_desc *, - gss_buffer_desc *)); -static int kencrypt P((struct krb5_stream *, gss_buffer_desc *, - gss_buffer_desc *)); -#endif -static struct krb5_conn *conn_get P((const char *)); -static void conn_put P((struct krb5_conn *)); -static void conn_read P((struct krb5_conn *)); -static void conn_read_cancel P((struct krb5_conn *)); -static void conn_read_callback P((void *)); -static int conn_run_frameq P((struct krb5_conn *, struct krb5_stream *)); -static int net_writev P((int, struct iovec *, int)); -static ssize_t net_read P((struct krb5_conn *, void *, size_t, int)); -static int net_read_fillbuf P((struct krb5_conn *, int)); -static char *krb5_checkuser(char *, char *, char *); -static void parse_pkt P((pkt_t *, const void *, size_t)); +static int runkrb5(struct sec_handle *); +char *keytab_name; +char *principal_name; /* * krb5 version of a security handle allocator. Logically sets * up a network "connection". */ static void -krb5_connect(hostname, conf_fn, fn, arg) - const char *hostname; - char *(*conf_fn) P((char *, void *)); - void (*fn) P((void *, security_handle_t *, security_status_t)); - void *arg; +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 port, fd; - const char *err; - char *keytab_name = NULL; - char *principal_name = NULL; + struct sec_handle *rh; + int result; + char *canonname; + assert(fn != NULL); assert(hostname != NULL); - k5printf(("krb5_connect: %s\n", hostname)); + auth_debug(1, "krb5: krb5_connect: %s\n", hostname); - /* - * Make sure we're initted - */ - init(); + krb5_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; + rh = alloc(sizeof(*rh)); + security_handleinit(&rh->sech, &krb5_security_driver); + rh->hostname = NULL; + rh->rs = NULL; + rh->ev_timeout = NULL; + rh->rc = NULL; + + 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; + } + + 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; + + if (rh->rs == NULL) + goto error; + + 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", arg); + keytab_name = conf_fn("krb5keytab", datap); } #endif #ifdef AMANDA_PRINCIPAL principal_name = AMANDA_PRINCIPAL; #else if(conf_fn) { - principal_name = conf_fn("krb5principal", arg); - } -#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->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 (connq.qlength > AMANDA_KRB5_MAXCONN) { - k5printf(("krb5_connect: too many conections (%d), delaying %s\n", - 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(fd, EV_WRITEFD, connect_callback, kh); - kh->ev_timeout = event_register(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(cookie) - 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); - amfree(kh->hostname); - amfree(kh); -} - -/* - * Called when a tcp connection is finished connecting and is ready - * to be authenticated. - */ -static void -connect_callback(cookie) - 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(cookie) - 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(in, out, fn) - int in, out; - void (*fn) P((security_handle_t *, pkt_t *)); -{ - struct sockaddr_in sin; - size_t len; - struct krb5_conn *kc; - struct hostent *he; - - /* - * Make sure we're initted - */ - init(); - - 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(hostname) - const char *hostname; -{ - struct krb5_conn *kc; - - k5printf(("krb5: conn_get: %s\n", hostname)); - - for (kc = connq_first(); kc != NULL; kc = 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); - connq_append(kc); - return (kc); -} - -/* - * Delete a reference to a connection, and close it if it is the last - * reference. - */ -static void -conn_put(kc) - 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); - } - 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(kc) - 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(kc->fd, EV_READFD, conn_read_callback, kc); - kc->ev_read_refcnt = 1; -} - -static void -conn_read_cancel(kc) - 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(inst) - 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 int -krb5_sendpkt(cookie, pkt) - void *cookie; - pkt_t *pkt; -{ - struct krb5_handle *kh = cookie; - gss_buffer_desc tok; - int rval; - unsigned char c, *buf; - - assert(kh != NULL); - assert(pkt != NULL); - - k5printf(("krb5: sendpkt: enter\n")); - - if (pkt->body[0] == '\0') { - c = (unsigned char)pkt->type; - tok.length = 1; - tok.value = &c; - } else { - tok.length = strlen(pkt->body) + 2; - tok.value = alloc(tok.length); - buf = tok.value; - *buf++ = (unsigned char)pkt->type; - strncpy(buf, pkt->body, tok.length - 2); - buf[tok.length - 2] = '\0'; + principal_name = conf_fn("krb5principal", datap); } - - 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)); - if (tok.length > 1) - amfree(tok.value); - return (rval); -} - -/* - * Set up to receive a packet asyncronously, and call back when - * it has been read. - */ -static void -krb5_recvpkt(cookie, fn, arg, timeout) - void *cookie, *arg; - void (*fn) P((void *, pkt_t *, security_status_t)); - 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(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(cookie) - 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(cookie, buf, bufsize) - void *cookie, *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, 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(cookie) - 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(h) - 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 = 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(s) - void *s; -{ - - return (0); -} - -/* - * Return a connected stream. For krb5, this means setup a stream - * with the supplied handle. - */ -static void * -krb5_stream_client(h, id) - 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 = 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(s) - 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(s) - void *s; -{ - - return (0); -} - -/* - * Returns the stream id for this stream. This is just the local - * port. - */ -static int -krb5_stream_id(s) - 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(s, buf, size) - 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(s, fn, arg) - void *s, *arg; - void (*fn) P((void *, void *, int)); -{ - struct krb5_stream *ks = s; - - assert(ks != NULL); /* - * Only one read request can be active per stream. + * We need to open a new connection. + * + * XXX need to eventually limit number of outgoing connections here. */ - ks->fn = fn; - ks->arg = arg; + if(rh->rc->read == -1) { + if (runkrb5(rh) < 0) + goto error; + rh->rc->refcnt++; + } /* - * First see if there's any queued frames for this stream. - * If so, we're done. + * 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. */ - if (conn_run_frameq(ks->kc, ks) > 0) - return; + 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; - 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); - } +error: + amfree(canonname); + (*fn)(arg, &rh->sech, S_ERROR); } /* - * Cancel a previous stream read request. It's ok if we didn't have a read - * scheduled. + + * Setup to handle new incoming connections */ static void -krb5_stream_read_cancel(s) - void *s; +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_stream *ks = s; + sockaddr_union sin; + socklen_t_equiv len; + struct tcp_conn *rc; + char hostname[NI_MAXHOST]; + int result; + char *errmsg = NULL; - assert(ks != NULL); + krb5_init(); - if (ks->ev_read != NULL) { - event_release(ks->ev_read); - ks->ev_read = NULL; - conn_read_cancel(ks->kc); - } -} - -/* - * Callback for krb5_stream_read - */ -static void -stream_read_callback(arg) - void *arg; -{ - struct krb5_stream *ks = arg; + len = sizeof(sin); + if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) { + dbprintf(_("getpeername returned: %s\n"), + strerror(errno)); + return; - assert(ks != 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: stream_read_callback: handle %d\n", ks->handle)); - conn_run_frameq(ks->kc, ks); + 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); } /* - * 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. + * Forks a krb5 to the host listed in rc->hostname + * Returns negative on error, with an errmsg in rc->errmsg. */ static int -conn_run_frameq(kc, ks) - struct krb5_conn *kc; - struct krb5_stream *ks; +runkrb5( + struct sec_handle * rh) { - 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; + struct servent * sp; + int server_socket; + in_port_t my_port, port; + struct tcp_conn * rc = rh->rc; + const char *err; - 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 */ - } + if ((sp = getservbyname(AMANDA_KRB5_SERVICE_NAME, "tcp")) == NULL) + port = htons(AMANDA_KRB5_DEFAULT_PORT); + else + port = sp->s_port; -#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, dectok->length); -#ifdef AMANDA_KRB5_ENCRYPT - gss_release_buffer(&min_stat, dectok); -#endif - } - amfree(enctok->value); - amfree(kf); - return (1); /* stop after one frame */ + if ((err = get_tgt(keytab_name, principal_name)) != NULL) { + security_seterror(&rh->sech, "%s: could not get TGT: %s", + rc->hostname, err); + return -1; } - 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(cookie) - 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); + 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); - 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(server_socket < 0) { + security_seterror(&rh->sech, + "%s", strerror(errno)); + + 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; + rc->read = rc->write = server_socket; - /* - * 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 (gss_client(rh) < 0) { + 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 -#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); + return 0; +} - /* - * 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(kh) - struct krb5_handle *kh; +gss_client( + 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 != GSS_S_COMPLETE) { - security_seterror(&kh->sech, "can't import name %s: %s", + if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { + 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. @@ -1314,14 +477,15 @@ gss_client(kh) * and only if the server has another token to send us. */ + recv_tok.value = NULL; for (recv_tok.length = 0;;) { 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, - GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG, + (OM_uint32)GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG, 0, NULL, /* no channel bindings */ (recv_tok.length == 0 ? GSS_C_NO_BUFFER : &recv_tok), NULL, /* ignore mech type */ @@ -1333,19 +497,18 @@ gss_client(kh) amfree(recv_tok.value); recv_tok.length = 0; } - - if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { - security_seterror(&kh->sech, - "error getting gss context: %s", - gss_error(maj_stat, min_stat)); + if (maj_stat != (OM_uint32)GSS_S_COMPLETE && maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) { + 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; } @@ -1354,20 +517,25 @@ gss_client(kh) /* * If we need to continue, then register for more packets */ - if (maj_stat != GSS_S_CONTINUE_NEEDED) + 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); @@ -1377,42 +545,37 @@ done: * Negotiate a krb5 gss context from the server end. */ static int -gss_server(kc) - struct krb5_conn *kc; +gss_server( + 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)) @@ -1420,56 +583,64 @@ gss_server(kc) } maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID, &gss_name); - if (maj_stat != GSS_S_COMPLETE) { - seteuid(euid); - snprintf(errbuf, sizeof(errbuf), - "can't import name %s: %s", (char *)send_tok.value, + if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { + 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 != GSS_S_COMPLETE) { - snprintf(errbuf, sizeof(errbuf), - "can't acquire creds for host key host/%s: %s", hostname, + if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { + 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 != GSS_S_COMPLETE && - maj_stat != GSS_S_CONTINUE_NEEDED) { - snprintf(errbuf, sizeof(errbuf), - "error accepting context: %s", gss_error(maj_stat, min_stat)); + if (maj_stat != (OM_uint32)GSS_S_COMPLETE && + maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) { + 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); - errbuf[sizeof(errbuf) - 1] = '\0'; - amfree(kc->errmsg); + 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(rc->errmsg); gss_release_buffer(&min_stat, &send_tok); goto out; } @@ -1480,14 +651,14 @@ gss_server(kc) * If we need to get more from the client, then register for * more packets. */ - if (maj_stat != GSS_S_CONTINUE_NEEDED) + if (maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) break; } maj_stat = gss_display_name(&min_stat, gss_name, &send_tok, &doid); - if (maj_stat != GSS_S_COMPLETE) { - snprintf(errbuf, sizeof(errbuf), - "can't display gss name: %s", gss_error(maj_stat, min_stat)); + if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { + 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; } @@ -1495,8 +666,8 @@ gss_server(kc) /* 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; } @@ -1506,9 +677,9 @@ gss_server(kc) /* * 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; } @@ -1516,10 +687,13 @@ gss_server(kc) 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); } @@ -1527,19 +701,18 @@ out: * Setup some things about krb5. This should only be called once. */ static void -init() +krb5_init(void) { static int beenhere = 0; - struct hostent *he; char *p; - int krb5_setenv P((const char *, const char *, int)); + char *myfqhostname=NULL; if (beenhere) return; beenhere = 1; #ifndef BROKEN_MEMORY_CCACHE - krb5_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 @@ -1551,26 +724,34 @@ init() */ atexit(cleanup); { - char ccache[64]; - snprintf(ccache, sizeof(ccache), "FILE:/tmp/amanda_ccache.%ld.%ld", - (long)geteuid(), (long)getpid()); - krb5_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); } @@ -1578,11 +759,11 @@ init() #ifdef BROKEN_MEMORY_CCACHE static void -cleanup() +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 @@ -1595,39 +776,43 @@ cleanup() * Get a ticket granting ticket and stuff it in the cache */ static const char * -get_tgt(keytab_name, principal_name) - char *keytab_name, *principal_name; +get_tgt( + char * keytab_name, + char * principal_name) { krb5_context context; 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); + /*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); } @@ -1635,8 +820,8 @@ get_tgt(keytab_name, principal_name) * 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); } @@ -1646,11 +831,20 @@ get_tgt(keytab_name, principal_name) */ 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, @@ -1658,20 +852,20 @@ get_tgt(keytab_name, principal_name) 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); } - memset(&creds, 0, sizeof(creds)); + memset(&creds, 0, SIZEOF(creds)); creds.times.starttime = 0; creds.times.endtime = now + AMANDA_TKT_LIFETIME; @@ -1685,24 +879,22 @@ get_tgt(keytab_name, principal_name) 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); @@ -1717,10 +909,12 @@ cleanup2: return (error); } +#ifndef KDESTROY_VIA_UNLINK /* * get rid of tickets */ -kdestroy() +static void +kdestroy(void) { krb5_context context; krb5_ccache ccache; @@ -1739,40 +933,15 @@ cleanup: krb5_free_context(context); return; } - -static void -parse_pkt(pkt, buf, bufsize) - pkt_t *pkt; - const void *buf; - size_t bufsize; -{ - const unsigned char *bufp = buf; - - k5printf(("krb5: parse_pkt: parsing buffer of %d bytes\n", bufsize)); - - pkt->type = (pktype_t)*bufp++; - bufsize--; - - if (bufsize == 0) { - pkt->body[0] = '\0'; - } else { - if (bufsize > sizeof(pkt->body) - 1) - bufsize = sizeof(pkt->body) - 1; - memcpy(pkt->body, bufp, bufsize); - pkt->body[sizeof(pkt->body) - 1] = '\0'; - } - - k5printf(("krb5: parse_pkt: %s (%d): \"%s\"\n", pkt_type2str(pkt->type), - pkt->type, pkt->body)); -} - +#endif /* * Formats an error from the gss api */ static const char * -gss_error(major, minor) - OM_uint32 major, minor; +gss_error( + OM_uint32 major, + OM_uint32 minor) { static gss_buffer_desc msg; OM_uint32 min_stat, msg_ctx; @@ -1790,322 +959,126 @@ gss_error(major, minor) 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(kc, handle, tok) - 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 = 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 = htonl(handle); - iov[1].iov_base = (void *)&nethandle; - iov[1].iov_len = sizeof(nethandle); + if (rc->conf_fn && rc->conf_fn("kencrypt", rc->datap)) { + auth_debug(1, _("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) { + 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 -recv_token(kc, handle, gtok, timeout) - struct krb5_conn *kc; - int *handle; - gss_buffer_desc *gtok; - int timeout; -{ - OM_uint32 netint; - - assert(kc->fd >= 0); - assert(gtok != NULL); - - k5printf(("krb5: recv_token: reading from %s\n", kc->hostname)); - - switch (net_read(kc, &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); - - 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); - } - - switch (net_read(kc, &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; - } - if (handle != NULL) - *handle = ntohl(netint); - - gtok->value = alloc(gtok->length); - switch (net_read(kc, 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 (gtok->length); -} - -#ifdef AMANDA_KRB5_ENCRYPT -static int -kencrypt(ks, tok, enctok) - struct krb5_stream *ks; - gss_buffer_desc *tok, *enctok; -{ - int conf_state; - OM_uint32 maj_stat, min_stat; - - 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 != 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); -} static int -kdecrypt(ks, enctok, tok) - struct krb5_stream *ks; - gss_buffer_desc *enctok, *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 != 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); - } - return (0); -} -#endif - -/* - * Writes out the entire iovec - */ -static int -net_writev(fd, iov, iovcnt) - int fd, iovcnt; - struct iovec *iov; -{ - int delta, n, total; - - assert(iov != NULL); - - total = 0; - while (iovcnt > 0) { - /* - * Write the iovec - */ - total += n = writev(fd, iov, iovcnt); - if (n < 0) - return (-1); - if (n == 0) { - errno = EIO; - return (-1); - } - /* - * Iterate through each iov. Figure out what we still need - * to write out. - */ - for (; n > 0; iovcnt--, iov++) { - /* 'delta' is the bytes written from this iovec */ - delta = n < iov->iov_len ? n : iov->iov_len; - /* subtract from the total num bytes written */ - n -= delta; - assert(n >= 0); - /* subtract from this iovec */ - iov->iov_len -= delta; - (char *)iov->iov_base += delta; - /* if this iovec isn't empty, run the writev again */ - if (iov->iov_len > 0) - break; - } - } - return (total); -} + 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; -/* - * Like read(), but waits until the entire buffer has been filled. - */ -static ssize_t -net_read(kc, vbuf, origsize, timeout) - struct krb5_conn *kc; - void *vbuf; - size_t origsize; - int timeout; -{ - char *buf = vbuf, *off; /* ptr arith */ - int nread; - size_t size = origsize; + auth_debug(1, _("krb5: k5_decrypt: decrypting %zu bytes\n"), enctok.length); - while (size > 0) { - if (kc->readbuf.left == 0) { - if (net_read_fillbuf(kc, timeout) < 0) + 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); - if (kc->readbuf.size == 0) - return (0); + } + auth_debug(1, _("krb5: k5_decrypt: give %zu bytes\n"), + dectok.length); + *decbuf = dectok.value; + *decbuflen = dectok.length; + } else { + *decbuf = buf; + *decbuflen = buflen; } - nread = min(kc->readbuf.left, size); - off = kc->readbuf.buf + kc->readbuf.size - kc->readbuf.left; - memcpy(buf, off, nread); - - buf += nread; - size -= nread; - kc->readbuf.left -= nread; - } - return ((ssize_t)origsize); -} - -/* - * net_read likes to do a lot of little reads. Buffer it. - */ -static int -net_read_fillbuf(kc, timeout) - struct krb5_conn *kc; - int timeout; -{ - fd_set readfds; - struct timeval tv; - - FD_ZERO(&readfds); - FD_SET(kc->fd, &readfds); - tv.tv_sec = timeout; - tv.tv_usec = 0; - switch (select(kc->fd + 1, &readfds, NULL, NULL, &tv)) { - case 0: - errno = ETIMEDOUT; - /* FALLTHROUGH */ - case -1: - return (-1); - case 1: - assert(FD_ISSET(kc->fd, &readfds)); - break; - default: - assert(0); - break; + auth_debug(1, _("krb5: k5_decrypt: exit\n")); + } else { + *decbuf = buf; + *decbuflen = buflen; } - kc->readbuf.left = 0; - kc->readbuf.size = read(kc->fd, kc->readbuf.buf, - sizeof(kc->readbuf.buf)); -k5printf(("net_read_fillbuf: read %d characters w/ errno %d\n", kc->readbuf.size, errno)); - if (kc->readbuf.size < 0) - return (-1); - kc->readbuf.left = kc->readbuf.size; return (0); } -/* - * 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 */ static char * -krb5_checkuser(host, name, realm) - char *host, *name, *realm; +krb5_checkuser( char * host, + char * name, + char * realm) { #ifdef AMANDA_PRINCIPAL 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; @@ -2116,7 +1089,7 @@ krb5_checkuser(host, name, realm) #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; } @@ -2129,46 +1102,43 @@ krb5_checkuser(host, name, realm) * the destination user mimicing the .k5login functionality. */ if(strcmp(name, CLIENT_LOGIN) != 0) { - result = vstralloc(name, " does not match ", - CLIENT_LOGIN, NULL); - goto common_exit; + 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); - goto common_exit; + 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') { + amfree(line); + continue; + } - while((line = agets(fp)) != NULL) { -#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); @@ -2188,7 +1158,7 @@ krb5_checkuser(host, name, realm) amfree(line); continue; } else { - k5printf(("found a host match\n")); + auth_debug(1, _("found a host match\n")); } if( (filerealm = strchr(fileuser, '@')) != NULL) { @@ -2203,31 +1173,142 @@ krb5_checkuser(host, name, realm) * 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; } result = NULL; + amfree(line); goto common_exit; } - amfree(line); } - - result = vstralloc("no match in ", ptmp, NULL); + result = vstrallocf(_("no match in %s"), ptmp); common_exit: - if(fp) - afclose(fp); - if(line) - amfree(line); + afclose(fp); return(result); #endif /* AMANDA_PRINCIPAL */ } -#else -void krb5_security_dummy (void) {} -#endif /* KRB5_SECURITY */ +/* + * 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 + */ + +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)); +} +