lintian doesn't like orphan packages with uploaders...
[debian/amanda] / amandad-src / amandad.c
index f04d4e3f1508095441b1d0a7ef35ce86beec410c..d864c3fa1bc7ca511ac93374fbccf9004a8d87f6 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
  * Copyright (c) 1991-1999 University of Maryland at College Park
+ * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
  * All Rights Reserved.
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
@@ -39,7 +40,6 @@
 #include "amfeatures.h"
 #include "packet.h"
 #include "version.h"
-#include "queue.h"
 #include "security.h"
 #include "stream.h"
 #include "util.h"
 
 #define        REP_TIMEOUT     (6*60*60)       /* secs for service to reply */
 #define        ACK_TIMEOUT     10              /* XXX should be configurable */
+#define STDERR_PIPE (DATA_FD_COUNT + 1)
 
-#define amandad_debug(i,x) do {                \
+#define amandad_debug(i, ...) do {     \
        if ((i) <= debug_amandad) {     \
-               dbprintf(x);            \
+               dbprintf(__VA_ARGS__);  \
        }                               \
 } while (0)
 
@@ -67,6 +68,39 @@ typedef enum { A_START, A_RECVPKT, A_RECVREP, A_PENDING, A_FINISH, A_CONTINUE,
 struct active_service;
 typedef action_t (*state_t)(struct active_service *, action_t, pkt_t *);
 
+/* string that we scan for in sendbackup's MESG stream */
+static const char info_end_str[] = "sendbackup: info end\n";
+#define INFO_END_LEN (sizeof(info_end_str)-1)
+
+/* 
+ * Here are the services that we allow.
+ * Must be in the same order as services[].
+ */
+typedef enum {
+    SERVICE_NOOP,
+    SERVICE_SENDSIZE,
+    SERVICE_SENDBACKUP,
+    SERVICE_SELFCHECK,
+    SERVICE_AMINDEXD,
+    SERVICE_AMIDXTAPED,
+    SERVICE_AMDUMPD
+} service_t;
+
+static struct services {
+    char *name;
+    int  active;
+    service_t service;
+} services[] = {
+   { "noop", 1, SERVICE_NOOP },
+   { "sendsize", 1, SERVICE_SENDSIZE },
+   { "sendbackup", 1, SERVICE_SENDBACKUP },
+   { "selfcheck", 1, SERVICE_SELFCHECK },
+   { "amindexd", 0, SERVICE_AMINDEXD },
+   { "amidxtaped", 0, SERVICE_AMIDXTAPED },
+   { "amdumpd", 0, SERVICE_AMDUMPD }
+};
+#define        NSERVICES       (int)(sizeof(services) / sizeof(services[0]))
+
 /*
  * This structure describes an active running service.
  *
@@ -76,6 +110,7 @@ typedef action_t (*state_t)(struct active_service *, action_t, pkt_t *);
  * for communications with the amanda server.
  */
 struct active_service {
+    service_t service;                 /* service name */
     char *cmd;                         /* name of command we ran */
     char *arguments;                   /* arguments we sent it */
     security_handle_t *security_handle;        /* remote server */
@@ -84,13 +119,19 @@ struct active_service {
     int send_partial_reply;            /* send PREP packet */
     int reqfd;                         /* pipe to write requests */
     int repfd;                         /* pipe to read replies */
+    int errfd;                         /* pipe to read stderr */
     event_handle_t *ev_repfd;          /* read event handle for repfd */
     event_handle_t *ev_reptimeout;     /* timeout for rep data */
+    event_handle_t *ev_errfd;          /* read event handle for errfd */
     pkt_t rep_pkt;                     /* rep packet we're sending out */
+    char *errbuf;                      /* buffer to read the err into */
     char *repbuf;                      /* buffer to read the rep into */
     size_t bufsize;                    /* length of repbuf */
     size_t repbufsize;                 /* length of repbuf */
     int repretry;                      /* times we'll retry sending the rep */
+    int seen_info_end;                 /* have we seen "sendbackup info end\n"? */
+    char info_end_buf[INFO_END_LEN];   /* last few bytes read, used for scanning for info end */
+
     /*
      * General user streams to the process, and their equivalent
      * network streams.
@@ -104,49 +145,17 @@ struct active_service {
        struct active_service *as;      /* pointer back to our enclosure */
     } data[DATA_FD_COUNT];
     char databuf[NETWORK_BLOCK_BYTES]; /* buffer to relay netfd data in */
-    TAILQ_ENTRY(active_service) tq;    /* queue handle */
 };
 
-/* 
- * Here are the services that we allow.
- */
-static struct services {
-    char *name;
-    int  active;
-} services[] = {
-    { "noop", 1 },
-    { "sendsize", 1 },
-    { "sendbackup", 1 },
-    { "selfcheck", 1 },
-    { "amindexd", 0 },
-    { "amidxtaped", 0 }
-};
-#define        NSERVICES       (int)(sizeof(services) / sizeof(services[0]))
-
 /*
  * Queue of outstanding requests that we are running.
  */
-static struct {
-    TAILQ_HEAD(, active_service) tailq;
-    int qlength;
-} serviceq = {
-    TAILQ_HEAD_INITIALIZER(serviceq.tailq), 0
-};
+GSList *serviceq = NULL;
 
-/*
- * Data for dbmalloc to check for memory leaks
- */
-#ifdef USE_DBMALLOC
-static struct {
-    struct {
-       unsigned long size, hist;
-    } start, end;
-} dbmalloc_info;
-#endif
-
-static int wait_30s = 1;
+static event_handle_t *exit_event;
 static int exit_on_qlength = 1;
 static char *auth = NULL;
+static kencrypt_type amandad_kencrypt = KENCRYPT_NONE;
 
 int main(int argc, char **argv);
 
@@ -162,40 +171,22 @@ static action_t s_sendrep(struct active_service *, action_t, pkt_t *);
 static action_t s_ackwait(struct active_service *, action_t, pkt_t *);
 
 static void repfd_recv(void *);
+static void process_errfd(void *cookie);
+static void errfd_recv(void *);
 static void timeout_repfd(void *);
 static void protocol_recv(void *, pkt_t *, security_status_t);
 static void process_readnetfd(void *);
 static void process_writenetfd(void *, void *, ssize_t);
 static struct active_service *service_new(security_handle_t *,
-    const char *, const char *);
+    const char *, service_t, const char *);
 static void service_delete(struct active_service *);
 static int writebuf(struct active_service *, const void *, size_t);
 static ssize_t do_sendpkt(security_handle_t *handle, pkt_t *pkt);
-
-static void child_signal(int signal);
+static char *amandad_get_security_conf (char *, void *);
 
 static const char *state2str(state_t);
 static const char *action2str(action_t);
 
-/*
- * Harvests defunct processes...
- */
-
-static void
-child_signal(
-    int                signal)
-{
-    pid_t      rp;
-
-    (void)signal;      /* Quite compiler warning */
-    /*
-     * Reap and child status and promptly ignore since we don't care...
-     */
-    do {
-       rp = waitpid(-1, NULL, WNOHANG);
-    } while (rp > 0);
-}
-
 int
 main(
     int                argc,
@@ -206,17 +197,35 @@ main(
     int in, out;
     const security_driver_t *secdrv;
     int no_exit = 0;
-    struct sigaction act, oact;
     char *pgm = "amandad";             /* in case argv[0] is not set */
 #if defined(USE_REUSEADDR)
     const int on = 1;
     int r;
 #endif
-    char *conffile;
+
+    /*
+     * Configure program for internationalization:
+     *   1) Only set the message locale for now.
+     *   2) Set textdomain for all amanda related programs to "amanda"
+     *      We don't want to be forced to support dozens of message catalogs.
+     */  
+    setlocale(LC_MESSAGES, "C");
+    textdomain("amanda"); 
 
     safe_fd(-1, 0);
     safe_cd();
 
+    /*
+     * Nexenta needs the SUN_PERSONALITY env variable to be unset, otherwise
+     * the Sun version of tar in /usr/sun/sbin/tar is called instead.
+     *
+     * On other operating systems this will have no effect.
+     */
+
+#ifdef HAVE_UNSETENV
+    unsetenv("SUN_PERSONALITY");
+#endif
+
     /*
      * When called via inetd, it is not uncommon to forget to put the
      * argv[0] value on the config line.  On some systems (e.g. Solaris)
@@ -232,48 +241,33 @@ main(
     dbopen(DBG_SUBDIR_AMANDAD);
 
     if(argv == NULL) {
-       error("argv == NULL\n");
+       error(_("argv == NULL\n"));
        /*NOTREACHED*/
     }
 
-    /* Don't die when child closes pipe */
-    signal(SIGPIPE, SIG_IGN);
-
-    /* Tell me when a child exits or dies... */
-    act.sa_handler = child_signal;
-    sigemptyset(&act.sa_mask);
-    act.sa_flags = 0;
-    if(sigaction(SIGCHLD, &act, &oact) != 0) {
-       error("error setting SIGCHLD handler: %s", strerror(errno));
-       /*NOTREACHED*/
-    }
-
-    conffile = vstralloc(CONFIG_DIR, "/", "amanda-client.conf", NULL);
-    if (read_clientconf(conffile) > 0) {
-       error("error reading conffile: %s", conffile);
-       /*NOTREACHED*/
+    if (argc > 1 && argv && argv[1] && g_str_equal(argv[1], "--version")) {
+       printf("amandad-%s\n", VERSION);
+       return (0);
     }
-    amfree(conffile);
 
-#ifdef USE_DBMALLOC
-    dbmalloc_info.start.size = malloc_inuse(&dbmalloc_info.start.hist);
-#endif
+    /* Don't die when child closes pipe */
+    signal(SIGPIPE, SIG_IGN);
 
-    erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
+    /* Parse the configuration; we'll handle errors later */
+    config_init(CONFIG_INIT_CLIENT, NULL);
 
-#ifdef FORCE_USERID
-    /* we'd rather not run as root */
     if (geteuid() == 0) {
-       if(client_uid == (uid_t) -1) {
-           error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
-           /*NOTREACHED*/
-       }
-       initgroups(CLIENT_LOGIN, client_gid);
-       setgid(client_gid);
-       setegid(client_gid);
-       seteuid(client_uid);
+       check_running_as(RUNNING_AS_ROOT);
+       initgroups(CLIENT_LOGIN, get_client_gid());
+       setgid(get_client_gid());
+       setegid(get_client_gid());
+       seteuid(get_client_uid());
+    } else {
+       check_running_as(RUNNING_AS_CLIENT_LOGIN);
     }
-#endif /* FORCE_USERID */
+
+    add_amanda_log_handler(amanda_log_stderr);
+    add_amanda_log_handler(amanda_log_syslog);
 
     /*
      * ad-hoc argument parsing
@@ -288,26 +282,25 @@ main(
     in = 0; out = 1;           /* default to stdin/stdout */
     have_services = 0;
     for (i = 1; i < argc; i++) {
-       /*
-        * accept -krb4 as an alias for -auth=krb4 (for compatibility)
-        */
-       if (strcmp(argv[i], "-krb4") == 0) {
-           argv[i] = "-auth=krb4";
-           /* FALLTHROUGH */
-           auth = "krb4";
-       }
-
        /*
         * Get a driver for a security type specified after -auth=
         */
-       else if (strncmp(argv[i], "-auth=", strlen("-auth=")) == 0) {
+       if (strncmp(argv[i], "-auth=", strlen("-auth=")) == 0) {
            argv[i] += strlen("-auth=");
            secdrv = security_getdriver(argv[i]);
            auth = argv[i];
            if (secdrv == NULL) {
-               error("no driver for security type '%s'\n", argv[i]);
+               error(_("no driver for security type '%s'\n"), argv[i]);
                 /*NOTREACHED*/
            }
+           if (strcmp(auth, "local") == 0 ||
+               strcmp(auth, "rsh") == 0 ||
+               strcmp(auth, "ssh") == 0) {
+               int i;
+               for (i=0; i < NSERVICES; i++) {
+                   services[i].active = 1;
+               }
+           }
            continue;
        }
 
@@ -338,16 +331,15 @@ main(
            in = out = socket(AF_INET, SOCK_DGRAM, 0);
 #endif
            if (in < 0) {
-               error("can't create dgram socket: %s\n", strerror(errno));
+               error(_("can't create dgram socket: %s\n"), strerror(errno));
                /*NOTREACHED*/
            }
 #ifdef USE_REUSEADDR
            r = setsockopt(in, SOL_SOCKET, SO_REUSEADDR,
-               (void *)&on, (socklen_t)sizeof(on));
+               (void *)&on, (socklen_t_equiv)sizeof(on));
            if (r < 0) {
-               dbprintf(("%s: amandad: setsockopt(SO_REUSEADDR) failed: %s\n",
-                         debug_prefix_time(NULL),
-                         strerror(errno)));
+               dbprintf(_("amandad: setsockopt(SO_REUSEADDR) failed: %s\n"),
+                         strerror(errno));
            }
 #endif
 
@@ -360,8 +352,8 @@ main(
            sin.sin_addr.s_addr = INADDR_ANY;
            sin.sin_port = (in_port_t)htons((in_port_t)atoi(argv[i]));
 #endif
-           if (bind(in, (struct sockaddr *)&sin, (socklen_t)sizeof(sin)) < 0) {
-               error("can't bind to port %d: %s\n", atoi(argv[i]),
+           if (bind(in, (struct sockaddr *)&sin, (socklen_t_equiv)sizeof(sin)) < 0) {
+               error(_("can't bind to port %d: %s\n"), atoi(argv[i]),
                    strerror(errno));
                /*NOTREACHED*/
            }
@@ -376,7 +368,7 @@ main(
            struct sockaddr_in sin;
 #endif
            int sock;
-           socklen_t n;
+           socklen_t_equiv n;
 
            argv[i] += strlen("-tcp=");
 #ifdef WORKING_IPV6
@@ -385,16 +377,15 @@ main(
            sock = socket(AF_INET, SOCK_STREAM, 0);
 #endif
            if (sock < 0) {
-               error("can't create tcp socket: %s\n", strerror(errno));
+               error(_("can't create tcp socket: %s\n"), strerror(errno));
                /*NOTREACHED*/
            }
 #ifdef USE_REUSEADDR
            r = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
-               (void *)&on, (socklen_t)sizeof(on));
+               (void *)&on, (socklen_t_equiv)sizeof(on));
            if (r < 0) {
-               dbprintf(("%s: amandad: setsockopt(SO_REUSEADDR) failed: %s\n",
-                         debug_prefix_time(NULL),
-                         strerror(errno)));
+               dbprintf(_("amandad: setsockopt(SO_REUSEADDR) failed: %s\n"),
+                         strerror(errno));
            }
 #endif
 #ifdef WORKING_IPV6
@@ -406,13 +397,13 @@ main(
            sin.sin_addr.s_addr = INADDR_ANY;
            sin.sin_port = (in_port_t)htons((in_port_t)atoi(argv[i]));
 #endif
-           if (bind(sock, (struct sockaddr *)&sin, (socklen_t)sizeof(sin)) < 0) {
-               error("can't bind to port %d: %s\n", atoi(argv[i]),
+           if (bind(sock, (struct sockaddr *)&sin, (socklen_t_equiv)sizeof(sin)) < 0) {
+               error(_("can't bind to port %d: %s\n"), atoi(argv[i]),
                    strerror(errno));
                /*NOTREACHED*/
            }
            listen(sock, 10);
-           n = (socklen_t)sizeof(sin);
+           n = (socklen_t_equiv)sizeof(sin);
            in = out = accept(sock, (struct sockaddr *)&sin, &n);
        }
        /*
@@ -437,8 +428,7 @@ main(
                    if (strcmp(services[j].name, argv[i]) == 0)
                        break;
                if (j == (int)NSERVICES) {
-                   dbprintf(("%s: %s: invalid service\n",
-                             debug_prefix_time(NULL), argv[i]));
+                   dbprintf(_("%s: invalid service\n"), argv[i]);
                    exit(1);
                }
                services[j].active = 1;
@@ -447,50 +437,74 @@ main(
     }
 
     /*
-     * If no security type specified, use BSD
+     * If no security type specified, use BSDTCP
      */
     if (secdrv == NULL) {
-       secdrv = security_getdriver("BSD");
-       auth = "bsd";
+       secdrv = security_getdriver("BSDTCP");
+       auth = "bsdtcp";
        if (secdrv == NULL) {
-           error("no driver for default security type 'BSD'\n");
+           error(_("no driver for default security type 'BSDTCP'\n"));
            /*NOTREACHED*/
        }
     }
 
     if(strcasecmp(auth, "rsh") == 0 ||
        strcasecmp(auth, "ssh") == 0 ||
+       strcasecmp(auth, "local") == 0 ||
        strcasecmp(auth, "bsdtcp") == 0) {
-       wait_30s = 0;
        exit_on_qlength = 1;
     }
 
+#ifndef SINGLE_USERID
+    if (geteuid() == 0) {
+       if (strcasecmp(auth, "krb5") != 0) {
+           struct passwd *pwd;
+           /* lookup our local user name */
+           if ((pwd = getpwnam(CLIENT_LOGIN)) == NULL) {
+               error(_("getpwnam(%s) failed."), CLIENT_LOGIN);
+           }
+
+           if (pwd->pw_uid != 0) {
+               error(_("'amandad' must be run as user '%s' when using '%s' authentication"),
+                     CLIENT_LOGIN, auth);
+           }
+       }
+    } else {
+       if (strcasecmp(auth, "krb5") == 0) {
+           error(_("'amandad' must be run as user 'root' when using 'krb5' authentication"));
+       }
+    }
+#endif
+
     /* initialize */
 
     startclock();
 
-    dbprintf(("%s: version %s\n", get_pname(), version()));
+    dbprintf(_("version %s\n"), VERSION);
     for (i = 0; version_info[i] != NULL; i++) {
-       dbprintf(("%s: %s", debug_prefix_time(NULL), version_info[i]));
+       dbprintf("    %s", version_info[i]);
     }
 
     if (! (argc >= 1 && argv != NULL && argv[0] != NULL)) {
-       dbprintf(("%s: WARNING: argv[0] not defined: check inetd.conf\n",
-                 debug_prefix_time(NULL)));
+       dbprintf(_("WARNING: argv[0] not defined: check inetd.conf\n"));
+    }
+
+    /* krb5 require the euid to be 0 */
+    if (strcasecmp(auth, "krb5") == 0) {
+       seteuid((uid_t)0);
     }
 
     /*
      * Schedule to call protocol_accept() when new security handles
      * are created on stdin.
      */
-    security_accept(secdrv, in, out, protocol_accept);
+    security_accept(secdrv, amandad_get_security_conf, in, out, protocol_accept, NULL);
 
     /*
      * Schedule an event that will try to exit every 30 seconds if there
      * are no requests outstanding.
      */
-    if(wait_30s)
-       (void)event_register((event_id_t)30, EV_TIME, exit_check, &no_exit);
+    exit_event = event_register((event_id_t)30, EV_TIME, exit_check, &no_exit);
 
     /*
      * Call event_loop() with an arg of 0, telling it to block until all
@@ -520,7 +534,7 @@ exit_check(
     /*
      * If things are still running, then don't exit.
      */
-    if (serviceq.qlength > 0)
+    if (g_slist_length(serviceq) > 0)
        return;
 
     /*
@@ -529,15 +543,7 @@ exit_check(
     if (no_exit)
        return;
 
-#ifdef USE_DBMALLOC
-    dbmalloc_info.end.size = malloc_inuse(&dbmalloc_info.end.hist);
-
-    if (dbmalloc_info.start.size != dbmalloc_info.end.size) {
-       malloc_list(dbfd(), dbmalloc_info.start.hist,
-           dbmalloc_info.end.hist);
-    }
-#endif
-
+    g_debug("timeout exit");
     dbclose();
     exit(0);
 }
@@ -552,26 +558,63 @@ protocol_accept(
     pkt_t *            pkt)
 {
     pkt_t pkt_out;
+    GSList *iter;
     struct active_service *as;
     char *pktbody, *tok, *service, *arguments;
     char *service_path = NULL;
+    GSList *errlist = NULL;
     int i;
+    char *peer_name;
 
     pkt_out.body = NULL;
 
     /*
      * If handle is NULL, then the connection is closed.
      */
-    if(handle == NULL) {
+    if (handle == NULL) {
+       if (exit_on_qlength && exit_event) {
+           /* remove the timeout, we will exit once the service terminate */
+           event_release(exit_event);
+           exit_event = NULL;
+       }
+       return;
+    }
+
+    /*
+     * If we have errors (not warnings) from the config file, let the remote system
+     * know immediately.  Unfortunately, we only get one ERROR line, so if there
+     * are multiple errors, we just show the first.
+     */
+    if (config_errors(&errlist) >= CFGERR_ERRORS) {
+       GSList *iter = errlist;
+       char *errmsg;
+       gboolean multiple_errors = FALSE;
+
+       if (iter) {
+           errmsg = (char *)iter->data;
+           if (iter->next)
+               multiple_errors = TRUE;
+       } else {
+           errmsg = "(no error message)";
+       }
+
+       pkt_init(&pkt_out, P_NAK, "ERROR %s%s", errmsg,
+           multiple_errors? _(" (additional errors not displayed)"):"");
+       do_sendpkt(handle, &pkt_out);
+       amfree(pkt_out.body);
+       security_close(handle);
        return;
     }
 
+    peer_name = security_get_authenticated_peer_name(handle);
+    g_debug("authenticated peer name is '%s'", peer_name);
+    amfree(peer_name);
+
     /*
      * If pkt is NULL, then there was a problem with the new connection.
      */
     if (pkt == NULL) {
-       dbprintf(("%s: accept error: %s\n",
-           debug_prefix_time(NULL), security_geterror(handle)));
+       dbprintf(_("accept error: %s\n"), security_geterror(handle));
        pkt_init(&pkt_out, P_NAK, "ERROR %s\n", security_geterror(handle));
        do_sendpkt(handle, &pkt_out);
        amfree(pkt_out.body);
@@ -579,15 +622,15 @@ protocol_accept(
        return;
     }
 
-    dbprintf(("%s: accept recv %s pkt:\n<<<<<\n%s>>>>>\n",
-       debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
+    dbprintf(_("accept recv %s pkt:\n<<<<<\n%s>>>>>\n"),
+       pkt_type2str(pkt->type), pkt->body);
 
     /*
      * If this is not a REQ packet, just forget about it.
      */
     if (pkt->type != P_REQ) {
-       dbprintf(("%s: received unexpected %s packet:\n<<<<<\n%s>>>>>\n\n",
-           debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
+       dbprintf(_("received unexpected %s packet:\n<<<<<\n%s>>>>>\n\n"),
+           pkt_type2str(pkt->type), pkt->body);
        security_close(handle);
        return;
     }
@@ -623,29 +666,27 @@ protocol_accept(
        if (services[i].active == 1 && strcmp(services[i].name, service) == 0)
            break;
     if (i == (int)NSERVICES) {
-       dbprintf(("%s: %s: invalid service\n",
-           debug_prefix_time(NULL), service));
-       pkt_init(&pkt_out, P_NAK, "ERROR %s: invalid service, add '%s' as argument to amandad\n", service, service);
+       dbprintf(_("%s: invalid service\n"), service);
+       pkt_init(&pkt_out, P_NAK, _("ERROR %s: invalid service, add '%s' as argument to amandad\n"), service, service);
        goto send_pkt_out;
     }
 
-    service_path = vstralloc(libexecdir, "/", service, versionsuffix(), NULL);
+    service_path = vstralloc(amlibexecdir, "/", service, NULL);
     if (access(service_path, X_OK) < 0) {
-       dbprintf(("%s: can't execute %s: %s\n",
-           debug_prefix_time(NULL), service_path, strerror(errno)));
+       dbprintf(_("can't execute %s: %s\n"), service_path, strerror(errno));
            pkt_init(&pkt_out, P_NAK,
-                    "ERROR execute access to \"%s\" denied\n",
+                    _("ERROR execute access to \"%s\" denied\n"),
                     service_path);
        goto send_pkt_out;
     }
 
     /* see if its already running */
-    for (as = TAILQ_FIRST(&serviceq.tailq); as != NULL;
-       as = TAILQ_NEXT(as, tq)) {
+    for (iter = serviceq; iter != NULL; iter = g_slist_next(iter)) {
+       as = (struct active_service *)iter->data;
            if (strcmp(as->cmd, service_path) == 0 &&
                strcmp(as->arguments, arguments) == 0) {
-                   dbprintf(("%s: %s %s: already running, acking req\n",
-                       debug_prefix_time(NULL), service, arguments));
+                   dbprintf(_("%s %s: already running, acking req\n"),
+                       service, arguments);
                    pkt_init_empty(&pkt_out, P_ACK);
                    goto send_pkt_out_no_delete;
            }
@@ -655,14 +696,12 @@ protocol_accept(
      * create a new service instance, and send the arguments down
      * the request pipe.
      */
-    dbprintf(("%s: creating new service: %s\n%s\n",
-       debug_prefix_time(NULL), service, arguments));
-    as = service_new(handle, service_path, arguments);
+    dbprintf(_("creating new service: %s\n%s\n"), service, arguments);
+    as = service_new(handle, service_path, services[i].service, arguments);
     if (writebuf(as, arguments, strlen(arguments)) < 0) {
        const char *errmsg = strerror(errno);
-       dbprintf(("%s: error sending arguments to %s: %s\n",
-           debug_prefix_time(NULL), service, errmsg));
-       pkt_init(&pkt_out, P_NAK, "ERROR error writing arguments to %s: %s\n",
+       dbprintf(_("error sending arguments to %s: %s\n"), service, errmsg);
+       pkt_init(&pkt_out, P_NAK, _("ERROR error writing arguments to %s: %s\n"),
            service, errmsg);
        goto send_pkt_out;
     }
@@ -682,9 +721,9 @@ protocol_accept(
     return;
 
 badreq:
-    pkt_init(&pkt_out, P_NAK, "ERROR invalid REQ\n");
-    dbprintf(("%s: received invalid %s packet:\n<<<<<\n%s>>>>>\n\n",
-       debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
+    pkt_init(&pkt_out, P_NAK, _("ERROR invalid REQ\n"));
+    dbprintf(_("received invalid %s packet:\n<<<<<\n%s>>>>>\n\n"),
+       pkt_type2str(pkt->type), pkt->body);
 
 send_pkt_out:
     if(as)
@@ -713,26 +752,22 @@ state_machine(
     state_t curstate;
     pkt_t nak;
 
-    amandad_debug(1, ("%s: state_machine: %p entering\n",
-                     debug_prefix_time(NULL), as));
+    amandad_debug(1, _("state_machine: %p entering\n"), as);
     for (;;) {
        curstate = as->state;
-       amandad_debug(1, ("%s: state_machine: %p curstate=%s action=%s\n",
-                         debug_prefix_time(NULL), as,
-                         state2str(curstate), action2str(action)));
+       amandad_debug(1, _("state_machine: %p curstate=%s action=%s\n"), as,
+                         state2str(curstate), action2str(action));
        retaction = (*curstate)(as, action, pkt);
-       amandad_debug(1, ("%s: state_machine: %p curstate=%s returned %s (nextstate=%s)\n",
-                         debug_prefix_time(NULL),
+       amandad_debug(1, _("state_machine: %p curstate=%s returned %s (nextstate=%s)\n"),
                          as, state2str(curstate), action2str(retaction),
-                         state2str(as->state)));
+                         state2str(as->state));
 
        switch (retaction) {
        /*
         * State has queued up and is now blocking on input.
         */
        case A_PENDING:
-           amandad_debug(1, ("%s: state_machine: %p leaving (A_PENDING)\n",
-                             debug_prefix_time(NULL), as));
+           amandad_debug(1, _("state_machine: %p leaving (A_PENDING)\n"), as);
            return;
 
        /*
@@ -746,25 +781,23 @@ state_machine(
         * Send a nak, and return.
         */
        case A_SENDNAK:
-           dbprintf(("%s: received unexpected %s packet\n",
-               debug_prefix_time(NULL), pkt_type2str(pkt->type)));
-           dbprintf(("<<<<<\n%s----\n\n", pkt->body));
-           pkt_init(&nak, P_NAK, "ERROR unexpected packet type %s\n",
+           dbprintf(_("received unexpected %s packet\n"),
+               pkt_type2str(pkt->type));
+           dbprintf(_("<<<<<\n%s----\n\n"), pkt->body);
+           pkt_init(&nak, P_NAK, _("ERROR unexpected packet type %s\n"),
                pkt_type2str(pkt->type));
            do_sendpkt(as->security_handle, &nak);
            amfree(nak.body);
            security_recvpkt(as->security_handle, protocol_recv, as, -1);
-           amandad_debug(1, ("%s: state_machine: %p leaving (A_SENDNAK)\n",
-                             debug_prefix_time(NULL), as));
+           amandad_debug(1, _("state_machine: %p leaving (A_SENDNAK)\n"), as);
            return;
 
        /*
         * Service is done.  Remove it and finish.
         */
        case A_FINISH:
+           amandad_debug(1, _("state_machine: %p leaving (A_FINISH)\n"), as);
            service_delete(as);
-           amandad_debug(1, ("%s: state_machine: %p leaving (A_FINISH)\n",
-                             debug_prefix_time(NULL), as));
            return;
 
        default:
@@ -792,8 +825,8 @@ s_sendack(
 
     pkt_init_empty(&ack, P_ACK);
     if (do_sendpkt(as->security_handle, &ack) < 0) {
-       dbprintf(("%s: error sending ACK: %s\n",
-           debug_prefix_time(NULL), security_geterror(as->security_handle)));
+       dbprintf(_("error sending ACK: %s\n"),
+           security_geterror(as->security_handle));
        amfree(ack.body);
        return (A_FINISH);
     }
@@ -811,6 +844,8 @@ s_sendack(
     as->ev_repfd = event_register((event_id_t)as->repfd, EV_READFD, repfd_recv, as);
     as->ev_reptimeout = event_register(REP_TIMEOUT, EV_TIME,
        timeout_repfd, as);
+    as->errbuf = NULL;
+    as->ev_errfd = event_register((event_id_t)as->errfd, EV_READFD, errfd_recv, as);
     security_recvpkt(as->security_handle, protocol_recv, as, -1);
     return (A_PENDING);
 }
@@ -826,8 +861,13 @@ s_repwait(
     action_t                   action,
     pkt_t *                    pkt)
 {
-    ssize_t n;
-    char *repbuf_temp;
+    ssize_t   n;
+    char     *repbuf_temp;
+    char     *what;
+    char     *msg;
+    int       code = 0;
+    int       pid;
+    amwait_t  retstat;
 
     /*
      * We normally shouldn't receive any packets while waiting
@@ -840,8 +880,7 @@ s_repwait(
         * and go back and wait for more data.
         */
        if (pkt->type == P_REQ) {
-           dbprintf(("%s: received dup P_REQ packet, ACKing it\n",
-               debug_prefix_time(NULL)));
+           dbprintf(_("received dup P_REQ packet, ACKing it\n"));
            amfree(as->rep_pkt.body);
            pkt_init_empty(&as->rep_pkt, P_ACK);
            do_sendpkt(as->security_handle, &as->rep_pkt);
@@ -854,9 +893,8 @@ s_repwait(
 
     if (action == A_TIMEOUT) {
        amfree(as->rep_pkt.body);
-       pkt_init(&as->rep_pkt, P_NAK, "ERROR timeout on reply pipe\n");
-       dbprintf(("%s: %s timed out waiting for REP data\n",
-           debug_prefix_time(NULL), as->cmd));
+       pkt_init(&as->rep_pkt, P_NAK, _("ERROR timeout on reply pipe\n"));
+       dbprintf(_("%s timed out waiting for REP data\n"), as->cmd);
        do_sendpkt(as->security_handle, &as->rep_pkt);
        return (A_FINISH);
     }
@@ -873,14 +911,65 @@ s_repwait(
     } while ((n < 0) && ((errno == EINTR) || (errno == EAGAIN)));
     if (n < 0) {
        const char *errstr = strerror(errno);
-       dbprintf(("%s: read error on reply pipe: %s\n",
-                 debug_prefix_time(NULL), errstr));
+       dbprintf(_("read error on reply pipe: %s\n"), errstr);
        amfree(as->rep_pkt.body);
-       pkt_init(&as->rep_pkt, P_NAK, "ERROR read error on reply pipe: %s\n",
+       pkt_init(&as->rep_pkt, P_NAK, _("ERROR read error on reply pipe: %s\n"),
                 errstr);
        do_sendpkt(as->security_handle, &as->rep_pkt);
        return (A_FINISH);
     }
+
+    /* If end of service, wait for process status */
+    if (n == 0) {
+       pid = waitpid(as->pid, &retstat, WNOHANG);
+       if (as->service  == SERVICE_NOOP ||
+           as->service  == SERVICE_SENDSIZE ||
+           as->service  == SERVICE_SELFCHECK) {
+           int t = 0;
+           while (t<5 && pid == 0) {
+               sleep(1);
+               t++;
+               pid = waitpid(as->pid, &retstat, WNOHANG);
+           }
+       }
+
+       process_errfd(as);
+
+       if (pid == 0)
+           pid = waitpid(as->pid, &retstat, WNOHANG);
+
+       if (pid > 0) {
+           what = NULL;
+           if (! WIFEXITED(retstat)) {
+               what = _("signal");
+               code = WTERMSIG(retstat);
+           } else if (WEXITSTATUS(retstat) != 0) {
+               what = _("code");
+               code = WEXITSTATUS(retstat);
+           }
+           if (what) {
+               dbprintf(_("service %s failed: pid %u exited with %s %d\n"),
+                        (as->cmd)?as->cmd:_("??UNKONWN??"),
+                        (unsigned)as->pid,
+                        what, code);
+               msg = vstrallocf(
+                    _("ERROR service %s failed: pid %u exited with %s %d\n"),
+                    (as->cmd)?as->cmd:_("??UNKONWN??"), (unsigned)as->pid,
+                    what, code);
+               if (as->repbufsize + strlen(msg) >= (as->bufsize - 1)) {
+                       as->bufsize *= 2;
+                       repbuf_temp = alloc(as->bufsize);
+                       memcpy(repbuf_temp, as->repbuf, as->repbufsize + 1);
+                       amfree(as->repbuf);
+                       as->repbuf = repbuf_temp;
+               }
+               strcpy(as->repbuf + as->repbufsize, msg);
+               as->repbufsize += strlen(msg);
+                amfree(msg);
+           }
+       }
+    }
+
     /*
      * If we got some data, go back and wait for more, or EOF.  Nul terminate
      * the buffer first.
@@ -953,7 +1042,12 @@ s_processrep(
      * We need to map these to security streams and pass them back
      * to the amanda server.  If the handle is -1, then we don't map.
      */
-    repbuf = stralloc(as->repbuf);
+    if (strncmp_const(as->repbuf,"KENCRYPT\n") == 0) {
+        amandad_kencrypt = KENCRYPT_WILL_DO;
+       repbuf = stralloc(as->repbuf + 9);
+    } else {
+       repbuf = stralloc(as->repbuf);
+    }
     amfree(as->rep_pkt.body);
     pkt_init_empty(&as->rep_pkt, P_REP);
     tok = strtok(repbuf, " ");
@@ -1046,18 +1140,15 @@ s_ackwait(
            as->state = s_sendrep;
            return (A_CONTINUE);
        }
-       dbprintf(("%s: timeout waiting for ACK for our REP\n",
-           debug_prefix_time(NULL)));
+       dbprintf(_("timeout waiting for ACK for our REP\n"));
        return (A_FINISH);
     }
-    amandad_debug(1, ("%s: received ACK, now opening streams\n",
-                     debug_prefix_time(NULL)));
+    amandad_debug(1, _("received ACK, now opening streams\n"));
 
     assert(action == A_RECVPKT);
 
     if (pkt->type == P_REQ) {
-       dbprintf(("%s: received dup P_REQ packet, resending REP\n",
-                 debug_prefix_time(NULL)));
+       dbprintf(_("received dup P_REQ packet, resending REP\n"));
        as->state = s_sendrep;
        return (A_CONTINUE);
     }
@@ -1065,23 +1156,36 @@ s_ackwait(
     if (pkt->type != P_ACK)
        return (A_SENDNAK);
 
+    if (amandad_kencrypt == KENCRYPT_WILL_DO) {
+       amandad_kencrypt = KENCRYPT_YES;
+    }
+
     /*
      * Got the ack, now open the pipes
      */
     for (dh = &as->data[0]; dh < &as->data[DATA_FD_COUNT]; dh++) {
        if (dh->netfd == NULL)
            continue;
+       dbprintf("opening security stream for fd %d\n", (int)(dh - as->data) + DATA_FD_OFFSET);
        if (security_stream_accept(dh->netfd) < 0) {
-           dbprintf(("%s: stream %td accept failed: %s\n",
-               debug_prefix_time(NULL),
-               dh - &as->data[0], security_geterror(as->security_handle)));
+           dbprintf(_("stream %td accept failed: %s\n"),
+               dh - &as->data[0], security_geterror(as->security_handle));
            security_stream_close(dh->netfd);
            dh->netfd = NULL;
            continue;
        }
-       /* setup an event for reads from it */
-       dh->ev_read = event_register((event_id_t)dh->fd_read, EV_READFD,
-                                    process_readnetfd, dh);
+
+       /* setup an event for reads from it.  As a special case, don't start
+        * listening on as->data[0] until we read some data on another fd, if
+        * the service is sendbackup.  This ensures that we send a MESG or 
+        * INDEX token before any DATA tokens, as dumper assumes. This is a
+        * hack, if that wasn't already obvious! */
+       if (dh != &as->data[0] || as->service != SERVICE_SENDBACKUP) {
+           dh->ev_read = event_register((event_id_t)dh->fd_read, EV_READFD,
+                                        process_readnetfd, dh);
+       } else {
+           amandad_debug(1, "Skipping registration of sendbackup's data FD\n");
+       }
 
        security_stream_read(dh->netfd, process_writenetfd, dh);
 
@@ -1109,8 +1213,7 @@ s_ackwait(
      * If no pipes are open, then we're done.  Otherwise, just start running.
      * The event handlers on all of the pipes will take it from here.
      */
-    amandad_debug(1, ("%s: at end of s_ackwait, npipes is %d\n",
-                     debug_prefix_time(NULL), npipes));
+    amandad_debug(1, _("at end of s_ackwait, npipes is %d\n"), npipes);
     if (npipes == 0)
        return (A_FINISH);
     else {
@@ -1135,6 +1238,101 @@ repfd_recv(
     state_machine(as, A_RECVREP, NULL);
 }
 
+static void
+process_errfd(
+    void *cookie)
+{
+    struct active_service *as = cookie;
+
+    /* Process errfd before sending the REP packet */
+    if (as->ev_errfd) {
+       SELECT_ARG_TYPE readset;
+       struct timeval  tv;
+       int             nfound;
+
+       memset(&tv, 0, SIZEOF(tv));
+       FD_ZERO(&readset);
+       FD_SET(as->errfd, &readset);
+       nfound = select(as->errfd+1, &readset, NULL, NULL, &tv);
+       if (nfound && FD_ISSET(as->errfd, &readset)) {
+           errfd_recv(as);
+       }
+    }
+}
+
+/*
+ * Called when a errfd has received data
+ */
+static void
+errfd_recv(
+    void *     cookie)
+{
+    struct active_service *as = cookie;
+    char  buf[32769];
+    int   n;
+    char *r;
+
+    assert(as != NULL);
+    assert(as->ev_errfd != NULL);
+
+    n = read(as->errfd, &buf, 32768);
+    /* merge buffer */
+    if (n > 0) {
+       /* Terminate it with '\0' */
+       buf[n+1] = '\0';
+
+       if (as->errbuf) {
+           as->errbuf = vstrextend(&as->errbuf, buf, NULL);
+       } else {
+           as->errbuf = stralloc(buf);
+       }
+    } else if (n == 0) {
+       event_release(as->ev_errfd);
+       as->ev_errfd = NULL;
+    } else { /* n < 0 */
+       event_release(as->ev_errfd);
+       as->ev_errfd = NULL;
+       g_snprintf(buf, 32768,
+                  "error reading stderr or service: %s\n", strerror(errno));
+    }
+
+    /* for each line terminate by '\n' */
+    while (as->errbuf != NULL  && (r = strchr(as->errbuf, '\n')) != NULL) {
+       char *s;
+
+       *r = '\0';
+       s = vstrallocf("ERROR service %s: %s\n",
+                      services[as->service].name, as->errbuf);
+
+       /* Add to repbuf, error message will be in the REP packet if it
+        * is not already sent
+        */
+       n = strlen(s);
+       if (as->bufsize == 0) {
+           as->bufsize = NETWORK_BLOCK_BYTES;
+           as->repbuf = alloc(as->bufsize);
+       }
+       while (as->bufsize < as->repbufsize + n) {
+           char *repbuf_temp;
+           as->bufsize *= 2;
+           repbuf_temp = alloc(as->bufsize);
+           memcpy(repbuf_temp, as->repbuf, as->repbufsize + 1);
+           amfree(as->repbuf);
+           as->repbuf = repbuf_temp;
+       }
+       memcpy(as->repbuf + as->repbufsize, s, n);
+       as->repbufsize += n;
+
+       dbprintf("%s", s);
+
+       /* remove first line from buffer */
+       r++;
+       s = stralloc(r);
+       amfree(as->errbuf);
+       as->errbuf = s;
+    }
+}
+
 /*
  * Called when a repfd has timed out
  */
@@ -1165,17 +1363,17 @@ protocol_recv(
 
     switch (status) {
     case S_OK:
-       dbprintf(("%s: received %s pkt:\n<<<<<\n%s>>>>>\n",
-           debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
+       dbprintf(_("received %s pkt:\n<<<<<\n%s>>>>>\n"),
+           pkt_type2str(pkt->type), pkt->body);
        state_machine(as, A_RECVPKT, pkt);
        break;
     case S_TIMEOUT:
-       dbprintf(("%s: timeout\n", debug_prefix_time(NULL)));
+       dbprintf(_("timeout\n"));
        state_machine(as, A_TIMEOUT, NULL);
        break;
     case S_ERROR:
-       dbprintf(("%s: receive error: %s\n",
-           debug_prefix_time(NULL), security_geterror(as->security_handle)));
+       dbprintf(_("receive error: %s\n"),
+           security_geterror(as->security_handle));
        break;
     }
 }
@@ -1203,7 +1401,7 @@ process_readnetfd(
      * Process has died.
      */
     if (n < 0) {
-       pkt_init(&nak, P_NAK, "A ERROR data descriptor %d broken: %s\n",
+       pkt_init(&nak, P_NAK, _("A ERROR data descriptor %d broken: %s\n"),
            dh->fd_read, strerror(errno));
        goto sendnak;
     }
@@ -1225,9 +1423,38 @@ process_readnetfd(
        service_delete(as);
        return;
     }
+
+    /* Handle the special case of recognizing "sendbackup info end"
+     * from sendbackup's MESG fd */
+    if (as->service == SERVICE_SENDBACKUP && !as->seen_info_end && dh == &as->data[1]) {
+       /* make a buffer containing the combined data from info_end_buf
+        * and what we've read this time, and search it for info_end_strj
+        * This includes a NULL byte for strstr's sanity. */
+       char *combined_buf = malloc(INFO_END_LEN + n + 1);
+       memcpy(combined_buf, as->info_end_buf, INFO_END_LEN);
+       memcpy(combined_buf+INFO_END_LEN, as->databuf, n);
+       combined_buf[INFO_END_LEN+n] = '\0';
+
+       as->seen_info_end = (strstr(combined_buf, info_end_str) != NULL);
+
+       /* fill info_end_buf from the tail end of combined_buf */
+       memcpy(as->info_end_buf, combined_buf + n, INFO_END_LEN);
+       amfree(combined_buf);
+
+       /* if we did see info_end_str, start reading the data fd (fd 0) */
+       if (as->seen_info_end) {
+           struct datafd_handle *dh = &as->data[0];
+           amandad_debug(1, "Opening datafd to sendbackup (delayed until sendbackup sent header info)\n");
+           dh->ev_read = event_register((event_id_t)dh->fd_read, EV_READFD,
+                                        process_readnetfd, dh);
+       } else {
+           amandad_debug(1, "sendbackup header info still not complete\n");
+       }
+    }
+
     if (security_stream_write(dh->netfd, as->databuf, (size_t)n) < 0) {
        /* stream has croaked */
-       pkt_init(&nak, P_NAK, "ERROR write error on stream %d: %s\n",
+       pkt_init(&nak, P_NAK, _("ERROR write error on stream %d: %s\n"),
            security_stream_id(dh->netfd),
            security_stream_geterror(dh->netfd));
        goto sendnak;
@@ -1256,13 +1483,10 @@ process_writenetfd(
     dh = cookie;
 
     if (dh->fd_write <= 0) {
-       dbprintf(("%s: process_writenetfd: dh->fd_write <= 0\n",
-           debug_prefix_time(NULL)));
+       dbprintf(_("process_writenetfd: dh->fd_write <= 0\n"));
     } else if (size > 0) {
-       fullwrite(dh->fd_write, buf, (size_t)size);
-       security_stream_read(dh->netfd, process_writenetfd, dh);
-    }
-    else {
+       full_write(dh->fd_write, buf, (size_t)size);
+    } else {
        aclose(dh->fd_write);
     }
 }
@@ -1281,6 +1505,9 @@ allocstream(
 {
     struct datafd_handle *dh;
 
+    /* note that handle is in the range DATA_FD_OFFSET to DATA_FD_COUNT, but
+     * it is NOT a file descriptor! */
+
     /* if the handle is -1, then we don't bother */
     if (handle < 0)
        return (-1);
@@ -1299,8 +1526,8 @@ allocstream(
     /* allocate a stream from the security layer and return */
     dh->netfd = security_stream_server(as->security_handle);
     if (dh->netfd == NULL) {
-       dbprintf(("%s: couldn't open stream to server: %s\n",
-           debug_prefix_time(NULL), security_geterror(as->security_handle)));
+       dbprintf(_("couldn't open stream to server: %s\n"),
+           security_geterror(as->security_handle));
        return (-1);
     }
 
@@ -1318,47 +1545,65 @@ static struct active_service *
 service_new(
     security_handle_t *        security_handle,
     const char *       cmd,
+    service_t          service,
     const char *       arguments)
 {
     int i;
-    int data_read[DATA_FD_COUNT + 1][2];
-    int data_write[DATA_FD_COUNT + 1][2];
+    int data_read[DATA_FD_COUNT + 2][2];
+    int data_write[DATA_FD_COUNT + 2][2];
     struct active_service *as;
     pid_t pid;
     int newfd;
+    char *peer_name;
+    char *amanda_remote_host_env[2];
 
     assert(security_handle != NULL);
     assert(cmd != NULL);
     assert(arguments != NULL);
 
     /* a plethora of pipes */
+    /* data_read[0]                : stdin
+     * data_write[0]               : stdout
+     * data_read[1], data_write[1] : first  stream
+     * data_read[2], data_write[2] : second stream
+     * data_read[3], data_write[3] : third stream
+     * data_write[4]               : stderr
+     */
     for (i = 0; i < DATA_FD_COUNT + 1; i++) {
        if (pipe(data_read[i]) < 0) {
-           error("pipe: %s\n", strerror(errno));
+           error(_("pipe: %s\n"), strerror(errno));
            /*NOTREACHED*/
        }
        if (pipe(data_write[i]) < 0) {
-           error("pipe: %s\n", strerror(errno));
+           error(_("pipe: %s\n"), strerror(errno));
            /*NOTREACHED*/
        }
     }
+    if (pipe(data_write[STDERR_PIPE]) < 0) {
+       error(_("pipe: %s\n"), strerror(errno));
+       /*NOTREACHED*/
+    }
 
     switch(pid = fork()) {
     case -1:
-       error("could not fork service %s: %s\n", cmd, strerror(errno));
+       error(_("could not fork service %s: %s\n"), cmd, strerror(errno));
        /*NOTREACHED*/
     default:
        /*
         * The parent.  Close the far ends of our pipes and return.
         */
-       as = alloc(SIZEOF(*as));
+       as = g_new0(struct active_service, 1);
        as->cmd = stralloc(cmd);
        as->arguments = stralloc(arguments);
        as->security_handle = security_handle;
        as->state = NULL;
+       as->service = service;
        as->pid = pid;
        as->send_partial_reply = 0;
-       if(strcmp(cmd+(strlen(cmd)-8), "sendsize") == 0) {
+       as->seen_info_end = FALSE;
+       /* fill in info_end_buf with non-null characters */
+       memset(as->info_end_buf, '-', sizeof(as->info_end_buf));
+       if(service == SERVICE_SENDSIZE) {
            g_option_t *g_options;
            char *option_str, *p;
 
@@ -1390,6 +1635,14 @@ service_new(
        as->repretry = 0;
        as->rep_pkt.body = NULL;
 
+       /*
+        * read from the stderr pipe
+        */
+       as->errfd = data_write[STDERR_PIPE][0];
+       aclose(data_write[STDERR_PIPE][1]);
+       as->ev_errfd = NULL;
+       as->errbuf = NULL;
+
        /*
         * read from the rest of the general-use pipes
         * (netfds are opened as the client requests them)
@@ -1407,8 +1660,7 @@ service_new(
 
        /* add it to the service queue */
        /* increment the active service count */
-       TAILQ_INSERT_TAIL(&serviceq.tailq, as, tq);
-       serviceq.qlength++;
+       serviceq = g_slist_append(serviceq, (gpointer)as);
 
        return (as);
     case 0:
@@ -1416,16 +1668,22 @@ service_new(
         * The child.  Put our pipes in their advertised locations
         * and start up.
         */
-#ifdef FORCE_USERID
-       seteuid((uid_t)0);
-       setuid(client_uid);
-#endif
+
+       /* set up the AMANDA_AUTHENTICATED_PEER env var so child services
+        * can use it to authenticate */
+       peer_name = security_get_authenticated_peer_name(security_handle);
+       amanda_remote_host_env[0] = NULL;
+       amanda_remote_host_env[1] = NULL;
+       if (*peer_name) {
+           amanda_remote_host_env[0] =
+               g_strdup_printf("AMANDA_AUTHENTICATED_PEER=%s", peer_name);
+       }
 
        /*
         * The data stream is stdin in the new process
         */
         if (dup2(data_read[0][0], 0) < 0) {
-           error("dup %d to %d failed: %s\n", data_read[0][0], 0,
+           error(_("dup %d to %d failed: %s\n"), data_read[0][0], 0,
                strerror(errno));
            /*NOTREACHED*/
        }
@@ -1436,7 +1694,7 @@ service_new(
         * The reply stream is stdout
         */
         if (dup2(data_write[0][1], 1) < 0) {
-           error("dup %d to %d failed: %s\n", data_write[0][1], 1,
+           error(_("dup %d to %d failed: %s\n"), data_write[0][1], 1,
                strerror(errno));
        }
         aclose(data_write[0][0]);
@@ -1456,17 +1714,32 @@ service_new(
                  data_read[i + 1][1] <= DATA_FD_OFFSET + DATA_FD_COUNT*2 - 1) {
                newfd = dup(data_read[i + 1][1]);
                if(newfd == -1)
-                   error("Can't dup out off DATA_FD range");
+                   error(_("Can't dup out off DATA_FD range"));
                data_read[i + 1][1] = newfd;
            }
            while(data_write[i + 1][0] >= DATA_FD_OFFSET &&
                  data_write[i + 1][0] <= DATA_FD_OFFSET + DATA_FD_COUNT*2 - 1) {
                newfd = dup(data_write[i + 1][0]);
                if(newfd == -1)
-                   error("Can't dup out off DATA_FD range");
+                   error(_("Can't dup out off DATA_FD range"));
                data_write[i + 1][0] = newfd;
            }
        }
+       while(data_write[4][0] >= DATA_FD_OFFSET &&
+             data_write[4][0] <= DATA_FD_OFFSET + DATA_FD_COUNT*2 - 1) {
+           newfd = dup(data_write[4][0]);
+           if (newfd == -1)
+               error(_("Can't dup out off DATA_FD range"));
+           data_write[4][0] = newfd;
+       }
+       while(data_write[4][1] >= DATA_FD_OFFSET &&
+             data_write[4][1] <= DATA_FD_OFFSET + DATA_FD_COUNT*2 - 1) {
+           newfd = dup(data_write[4][1]);
+           if (newfd == -1)
+               error(_("Can't dup out off DATA_FD range"));
+           data_write[4][1] = newfd;
+       }
+
        for (i = 0; i < DATA_FD_COUNT*2; i++)
            close(DATA_FD_OFFSET + i);
 
@@ -1476,24 +1749,27 @@ service_new(
         */
        for (i = 0; i < DATA_FD_COUNT; i++) {
            if (dup2(data_read[i + 1][1], i*2 + DATA_FD_OFFSET) < 0) {
-               error("dup %d to %d failed: %s\n", data_read[i + 1][1],
+               error(_("dup %d to %d failed: %s\n"), data_read[i + 1][1],
                    i + DATA_FD_OFFSET, strerror(errno));
            }
            aclose(data_read[i + 1][1]);
 
            if (dup2(data_write[i + 1][0], i*2 + 1 + DATA_FD_OFFSET) < 0) {
-               error("dup %d to %d failed: %s\n", data_write[i + 1][0],
+               error(_("dup %d to %d failed: %s\n"), data_write[i + 1][0],
                    i + DATA_FD_OFFSET, strerror(errno));
            }
            aclose(data_write[i + 1][0]);
        }
 
        /* close all unneeded fd */
+       close(STDERR_FILENO);
+       dup2(data_write[STDERR_PIPE][1], 2);
+        aclose(data_write[STDERR_PIPE][0]);
+        aclose(data_write[STDERR_PIPE][1]);
        safe_fd(DATA_FD_OFFSET, DATA_FD_COUNT*2);
-       close(2);
 
-       execle(cmd, cmd, "amandad", auth, (char *)NULL, safe_env());
-       error("could not exec service %s: %s\n", cmd, strerror(errno));
+       execle(cmd, cmd, "amandad", auth, (char *)NULL, safe_env_full(amanda_remote_host_env));
+       error(_("could not exec service %s: %s\n"), cmd, strerror(errno));
        /*NOTREACHED*/
     }
     return NULL;
@@ -1507,11 +1783,12 @@ service_delete(
     struct active_service *    as)
 {
     int i;
+    int   count;
+    pid_t pid;
     struct datafd_handle *dh;
 
-    amandad_debug(1, ("%s: closing service: %s\n",
-                     debug_prefix_time(NULL),
-                     (as->cmd)?as->cmd:"??UNKONWN??"));
+    amandad_debug(1, _("closing service: %s\n"),
+                     (as->cmd)?as->cmd:_("??UNKONWN??"));
 
     assert(as != NULL);
 
@@ -1525,11 +1802,17 @@ service_delete(
        aclose(as->reqfd);
     if (as->repfd != -1)
        aclose(as->repfd);
+    if (as->errfd != -1) {
+       process_errfd(as);
+       aclose(as->errfd);
+    }
 
     if (as->ev_repfd != NULL)
        event_release(as->ev_repfd);
     if (as->ev_reptimeout != NULL)
        event_release(as->ev_reptimeout);
+    if (as->ev_errfd != NULL)
+       event_release(as->ev_errfd);
 
     for (i = 0; i < DATA_FD_COUNT; i++) {
        dh = &as->data[i];
@@ -1549,13 +1832,25 @@ service_delete(
     if (as->security_handle != NULL)
        security_close(as->security_handle);
 
+    /* try to kill the process; if this fails, then it's already dead and
+     * likely some of the other zombie cleanup ate its brains, so we don't
+     * bother to waitpid for it */
     assert(as->pid > 0);
-    kill(as->pid, SIGTERM);
-    waitpid(as->pid, NULL, WNOHANG);
+    pid = waitpid(as->pid, NULL, WNOHANG);
+    if (pid != as->pid && kill(as->pid, SIGTERM) == 0) {
+       pid = waitpid(as->pid, NULL, WNOHANG);
+       count = 5;
+       while (pid != as->pid && count > 0) {
+           count--;
+           sleep(1);
+           pid = waitpid(as->pid, NULL, WNOHANG);
+       }
+       if (pid != as->pid) {
+           g_debug("Process %d failed to exit", (int)as->pid);
+       }
+    }
 
-    TAILQ_REMOVE(&serviceq.tailq, as, tq);
-    assert(serviceq.qlength > 0);
-    serviceq.qlength--;
+    serviceq = g_slist_remove(serviceq, (gpointer)as);
 
     amfree(as->cmd);
     amfree(as->arguments);
@@ -1563,7 +1858,7 @@ service_delete(
     amfree(as->rep_pkt.body);
     amfree(as);
 
-    if(exit_on_qlength == 0 && serviceq.qlength == 0) {
+    if(exit_on_qlength == 0 && g_slist_length(serviceq) == 0) {
        dbclose();
        exit(0);
     }
@@ -1580,7 +1875,7 @@ writebuf(
     size_t                     size)
 {
     pid_t pid;
-    ssize_t    writesize;
+    size_t    writesize;
 
     switch (pid=fork()) {
     case -1:
@@ -1592,8 +1887,8 @@ writebuf(
 
     case 0:                            /* this is the child */
        close(as->repfd);
-       writesize = fullwrite(as->reqfd, bufp, size);
-       exit(writesize != (ssize_t)size);
+       writesize = full_write(as->reqfd, bufp, size);
+       exit(writesize != size);
        /* NOTREACHED */
     }
     return -1;
@@ -1604,8 +1899,8 @@ do_sendpkt(
     security_handle_t *        handle,
     pkt_t *            pkt)
 {
-    dbprintf(("%s: sending %s pkt:\n<<<<<\n%s>>>>>\n",
-       debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
+    dbprintf(_("sending %s pkt:\n<<<<<\n%s>>>>>\n"),
+       pkt_type2str(pkt->type), pkt->body);
     if (handle)
        return security_sendpkt(handle, pkt);
     else
@@ -1636,7 +1931,7 @@ state2str(
     for (i = 0; i < (int)(sizeof(states) / sizeof(states[0])); i++)
        if (state == states[i].state)
            return (states[i].str);
-    return ("INVALID STATE");
+    return (_("INVALID STATE"));
 }
 
 /*
@@ -1666,5 +1961,25 @@ action2str(
     for (i = 0; i < (int)(sizeof(actions) / sizeof(actions[0])); i++)
        if (action == actions[i].action)
            return (actions[i].str);
-    return ("UNKNOWN ACTION");
+    return (_("UNKNOWN ACTION"));
+}
+
+static char *
+amandad_get_security_conf(
+    char *      string,
+    void *      arg)
+{
+    (void)arg;      /* Quiet unused parameter warning */
+
+    if (!string || !*string)
+       return(NULL);
+
+    if (strcmp(string, "kencrypt")==0) {
+       if (amandad_kencrypt == KENCRYPT_YES)
+           return ("yes");
+       else
+           return (NULL);
+    }
+    return(NULL);
 }
+