lintian doesn't like orphan packages with uploaders...
[debian/amanda] / common-src / krb5-security.c
index e1e78a0ca0138748c28a1930ebda8e5d0f823550..c3075fa9bc2456d0aafd8afec557e4138f42d337 100644 (file)
  */
 
 /*
- * $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 <gssapi/gssapi_generic.h>
 #else
 #include <krb5.h>
 
 #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.
  * 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));
+}
+