hashsum.c: Document `--progress' in the `--help' display.
[u/mdw/catacomb] / pixie.c
diff --git a/pixie.c b/pixie.c
index a6b7e05..af6483a 100644 (file)
--- a/pixie.c
+++ b/pixie.c
@@ -1,13 +1,13 @@
 /* -*-c-*-
  *
- * $Id: pixie.c,v 1.3 1999/12/22 22:14:40 mdw Exp $
+ * $Id$
  *
  * Passphrase pixie for Catacomb
  *
  * (c) 1999 Straylight/Edgeware
  */
 
-/*----- Licensing notice --------------------------------------------------* 
+/*----- Licensing notice --------------------------------------------------*
  *
  * This file is part of Catacomb.
  *
  * it under the terms of the GNU Library General Public License as
  * published by the Free Software Foundation; either version 2 of the
  * License, or (at your option) any later version.
- * 
+ *
  * Catacomb is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Library General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Library General Public
  * License along with Catacomb; if not, write to the Free
  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  * MA 02111-1307, USA.
  */
 
-/*----- Revision history --------------------------------------------------* 
- *
- * $Log: pixie.c,v $
- * Revision 1.3  1999/12/22 22:14:40  mdw
- * Only produce initialization message if verbose.
- *
- * Revision 1.2  1999/12/22 22:13:42  mdw
- * Fix bug in passphrase flushing loop.
- *
- * Revision 1.1  1999/12/22 15:58:41  mdw
- * Passphrase pixie support.
- *
- */
-
 /*----- Header files ------------------------------------------------------*/
 
 #include "config.h"
 #include <mLib/sub.h>
 #include <mLib/tv.h>
 
+#include "arena.h"
 #include "lmem.h"
 #include "passphrase.h"
 #include "pixie.h"
 
 /*----- Static variables --------------------------------------------------*/
 
-static unsigned long timeout = 300;
+static unsigned long timeout = 900;
 static sel_state sel;
 static unsigned verbose = 1;
 static const char *command = 0;
 static lmem lm;
 static unsigned flags = 0;
 
-enum {
-  F_SYSLOG = 1
-};
+#define F_SYSLOG 1u
+#define F_FETCH 2u
 
 /*----- Event logging -----------------------------------------------------*/
 
@@ -124,7 +110,7 @@ static void log(const char *p, ...)
     d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
   }
   va_start(ap, p);
-  dstr_vputf(&d, p, ap);
+  dstr_vputf(&d, p, &ap);
   va_end(ap);
 
   if (flags & F_SYSLOG)
@@ -168,7 +154,7 @@ static void p_free(phrase *p)
 {
   if (p->t)
     sel_rmtimer(&p->timer);
-  free(p->tag);
+  xfree(p->tag);
   l_free(&lm, p->p);
   p->next->prev = p->prev;
   p->prev->next = p->next;
@@ -211,7 +197,7 @@ static void *p_alloc(size_t sz)
       return (p);
     if (P_ROOT->next == P_ROOT)
       return (0);
-    if (verbose) { 
+    if (verbose) {
       log("flushing passphrase `%s' to free up needed space",
          P_ROOT->next->tag);
     }
@@ -356,6 +342,7 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz)
     int fd[2];
     pid_t kid;
     int r;
+    int rc;
 
     /* --- Substitute the prompt string into the command --- */
 
@@ -398,7 +385,7 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz)
       if (dup2(fd[1], STDOUT_FILENO) < 0)
        _exit(127);
       close(fd[0]);
-      execl("/bin/sh", "sh", "-c", d.buf, (void *)0);
+      execl("/bin/sh", "sh", "-c", d.buf, (char *)0);
       _exit(127);
     }
 
@@ -412,11 +399,11 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz)
       *q = 0;
     }
     close(fd[0]);
-    waitpid(kid, 0, 0);
+    waitpid(kid, &rc, 0);
     dstr_destroy(&d);
-    if (r < 0)
+    if (r < 0 || rc != 0)
       goto fail_0;
-    return (0);
+    goto ok;
 
     /* --- Tidy up when things go wrong --- */
 
@@ -442,22 +429,47 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz)
     dstr_putf(&d, "%s %s: ", msg, tag);
     rc = pixie_getpass(d.buf, buf, sz);
     dstr_destroy(&d);
-    return (rc);
+    if (rc)
+      return (rc);
+    goto ok;
   }
+
+  /* --- Sort out the buffer --- *
+   *
+   * Strip leading spaces.
+   */
+
+ok: {
+    char *p = buf;
+    size_t len;
+    while (isspace((unsigned char)*p))
+      p++;
+    len = strlen(p);
+    memmove(buf, p, len);
+    p[len] = 0;
+  }
+
+  /* --- Done --- */
+
+  return (0);
 }
 
 /* --- @p_get@ --- *
  *
- * Arguments:  @const char *tag@ = pointer to tag string
+ * Arguments:  @const char **q@ = where to store the result
+ *             @const char *tag@ = pointer to tag string
  *             @unsigned mode@ = reading mode (verify?)
  *             @time_t exp@ = expiry time suggestion
  *
- * Returns:    Pointer to passphrase, or zero.
+ * Returns:    Zero if successful, @-1@ on a read failure, or @+1@ if the
+ *             passphrase is missing and there is no fetcher.  (This will
+ *             always happen if there is no fetcher and @mode@ is
+ *             @PMODE_VERIFY@.
  *
  * Use:                Reads a passphrase from somewhere.
  */
 
-static const char *p_get(const char *tag, unsigned mode, time_t exp)
+static int p_get(const char **q, const char *tag, unsigned mode, time_t exp)
 {
 #define LBUFSZ 1024
 
@@ -469,12 +481,26 @@ static const char *p_get(const char *tag, unsigned mode, time_t exp)
   if (verbose > 1)
     log("passphrase `%s' requested", tag);
 
+  /* --- If there is no fetcher, life is simpler --- */
+
+  if (!(flags & F_FETCH)) {
+    if (mode == PMODE_VERIFY)
+      return (+1);
+    if ((p = p_find(tag)) == 0)
+      return (+1);
+    *q = p->p;
+    return (0);
+  }
+
   /* --- Try to find the phrase --- */
 
-  if ((p = p_find(tag)) == 0) {
+  if (mode == PMODE_VERIFY)
+    p_flush(tag);
+  if (mode == PMODE_VERIFY || (p = p_find(tag)) == 0) {
     if ((pp = p_alloc(LBUFSZ)) == 0)
       goto fail;
-    if (p_request("Passphrase", tag, pp, LBUFSZ) < 0)
+    if (p_request(mode == PMODE_READ ? "Passphrase" : "New passphrase",
+                 tag, pp, LBUFSZ) < 0)
       goto fail;
     p = p_add(tag, pp, exp);
     if (!p)
@@ -502,7 +528,8 @@ static const char *p_get(const char *tag, unsigned mode, time_t exp)
     memset(pp, 0, LBUFSZ);
     l_free(&lm, pp);
   }
-  return (p->p);
+  *q = p->p;
+  return (0);
 
   /* --- Tidy up if things went wrong --- */
 
@@ -511,7 +538,7 @@ fail:
     memset(pp, 0, LBUFSZ);
     l_free(&lm, pp);
   }
-  return (0);
+  return (-1);
 
 #undef LBUFSZ
 }
@@ -524,9 +551,12 @@ typedef struct pixserv {
   selbuf b;
   int fd;
   sel_timer timer;
+  unsigned f;
 } pixserv;
 
-#define PIXSERV_TIMEOUT 300
+#define px_stdin 1u
+
+#define PIXSERV_TIMEOUT 30
 
 /* --- @pixserv_expire@ --- *
  *
@@ -544,7 +574,7 @@ static void pixserv_expire(struct timeval *tv, void *p)
   pixserv *px = p;
   if (px->fd != px->b.reader.fd)
     close(px->fd);
-  selbuf_disable(&px->b);
+  selbuf_destroy(&px->b);
   close(px->b.reader.fd);
   DESTROY(px);
 }
@@ -566,7 +596,7 @@ static void pixserv_write(pixserv *px, const char *p, ...)
   va_list ap;
 
   va_start(ap, p);
-  dstr_vputf(&d, p, ap);
+  dstr_vputf(&d, p, &ap);
   write(px->fd, d.buf, d.len);
   va_end(ap);
   dstr_destroy(&d);
@@ -595,7 +625,7 @@ static unsigned long pixserv_timeout(const char *p)
     case 'h': t *= 60;
     case 'm': t *= 60;
     case 's': if (q[1] != 0)
-      default:    t = 0;
+      default:   t = 0;
     case 0:   break;
   }
   return (t);
@@ -604,6 +634,7 @@ static unsigned long pixserv_timeout(const char *p)
 /* --- @pixserv_line@ --- *
  *
  * Arguments:  @char *s@ = pointer to the line read
+ *             @size_t len@ = length of the line
  *             @void *p@ = pointer to server block
  *
  * Returns:    ---
@@ -611,7 +642,7 @@ static unsigned long pixserv_timeout(const char *p)
  * Use:                Handles a line read from the client.
  */
 
-static void pixserv_line(char *s, void *p)
+static void pixserv_line(char *s, size_t len, void *p)
 {
   pixserv *px = p;
   char *q, *qq;
@@ -619,18 +650,19 @@ static void pixserv_line(char *s, void *p)
 
   /* --- Handle an end-of-file --- */
 
-  sel_rmtimer(&px->timer);
+  if (!(px->f & px_stdin))
+    sel_rmtimer(&px->timer);
   if (!s) {
     if (px->fd != px->b.reader.fd)
       close(px->fd);
-    selbuf_disable(&px->b);
+    selbuf_destroy(&px->b);
     close(px->b.reader.fd);
     return;
   }
 
   /* --- Fiddle the timeout --- */
 
-  {
+  if (!(px->f & px_stdin)) {
     struct timeval tv;
     gettimeofday(&tv, 0);
     tv.tv_sec += PIXSERV_TIMEOUT;
@@ -654,6 +686,7 @@ INFO LIST\n\
 INFO PASS tag [expire]\n\
 INFO VERIFY tag [expire]\n\
 INFO FLUSH [tag]\n\
+INFO SET tag [expire] -- phrase\n\
 INFO QUIT\n\
 OK\n\
 ");
@@ -683,15 +716,26 @@ OK\n\
           (mode = PMODE_VERIFY, strcmp(q, "verify") == 0)) {
     unsigned long t;
     const char *p;
+    int rc;
 
     if ((q = str_getword(&s)) == 0)
       pixserv_write(px, "FAIL missing tag\n");
     else if ((t = pixserv_timeout(s)) == 0)
       pixserv_write(px, "FAIL bad timeout\n");
-    else if ((p = p_get(q, mode, t > timeout ? timeout : t)) == 0)
-      pixserv_write(px, "FAIL error reading passphrase\n");
-    else
-      pixserv_write(px, "OK %s\n", p);
+    else {
+      rc = p_get(&p, q, mode, t > timeout ? timeout : t);
+      switch (rc) {
+       case 0:
+         pixserv_write(px, "OK %s\n", p);
+         break;
+       case -1:
+         pixserv_write(px, "FAIL error reading passphrase\n");
+         break;
+       case +1:
+         pixserv_write(px, "MISSING\n");
+         break;
+      }
+    }
   }
 
   /* --- Flush existing passphrases --- */
@@ -702,11 +746,39 @@ OK\n\
     pixserv_write(px, "OK\n");
   }
 
+  /* --- Set a passphrase --- */
+
+  else if (strcmp(q, "set") == 0) {
+    char *tag;
+    unsigned long t;
+    if ((tag = str_getword(&s)) == 0)
+      pixserv_write(px, "FAIL missing tag\n");
+    else if ((q = str_getword(&s)) == 0)
+      pixserv_write(px, "FAIL no passphrase\n");
+    else {
+      if (strcmp(q, "--") != 0) {
+       t = pixserv_timeout(q);
+       q = str_getword(&s);
+      } else
+       t = pixserv_timeout(0);
+      if (!q)
+       pixserv_write(px, "FAIL no passphrase\n");
+      else if (strcmp(q, "--") != 0)
+       pixserv_write(px, "FAIL rubbish found before passphrase\n");
+      else {
+       p_flush(tag);
+       p_add(tag, s, t);
+       pixserv_write(px, "OK\n");
+      }
+    }
+  }
+
   /* --- Shut the server down --- */
 
   else if (strcmp(q, "quit") == 0) {
     if (verbose)
-      log("client requested shutdown");
+      log("%s client requested shutdown",
+         px->f & px_stdin ? "local" : "remote");
     pixserv_write(px, "OK\n");
     exit(0);
   }
@@ -722,12 +794,12 @@ OK\n\
  * Arguments:  @int fd@ = file descriptor to read from
  *             @int ofd@ = file descriptor to write to
  *
- * Returns:    ---
+ * Returns:    Pointer to the new connection.
  *
  * Use:                Creates a new Pixie server instance for a new connection.
  */
 
-static void pixserv_create(int fd, int ofd)
+static pixserv *pixserv_create(int fd, int ofd)
 {
   pixserv *px = CREATE(pixserv);
   struct timeval tv;
@@ -736,9 +808,13 @@ static void pixserv_create(int fd, int ofd)
     fdflags(ofd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
   px->fd = ofd;
   selbuf_init(&px->b, &sel, fd, pixserv_line, px);
+  px->b.b.a = arena_secure;
+  selbuf_setsize(&px->b, 1024);
   gettimeofday(&tv, 0);
   tv.tv_sec += PIXSERV_TIMEOUT;
   sel_addtimer(&sel, &px->timer, &tv, pixserv_expire, px);
+  px->f = 0;
+  return (px);
 }
 
 /* --- @pixserv_accept@ --- *
@@ -756,12 +832,13 @@ static void pixserv_accept(int fd, unsigned mode, void *p)
 {
   int nfd;
   struct sockaddr_un sun;
-  int sunsz = sizeof(sun);
+  size_t sunsz = sizeof(sun);
 
   if (mode != SEL_READ)
     return;
   if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) {
-    if (verbose)
+    if (verbose && errno != EAGAIN && errno != EWOULDBLOCK &&
+       errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
       log("new connection failed: %s", strerror(errno));
     return;
   }
@@ -862,11 +939,11 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz)
   /* --- Set up the parent directory --- */
 
   {
-    dstr d = DSTR_INIT;
     char *p = sun->sun_path;
     char *q = strrchr(p, '/');
 
     if (q) {
+      dstr d = DSTR_INIT;
       struct stat st;
 
       DPUTM(&d, p, q - p);
@@ -875,8 +952,11 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz)
       mkdir(d.buf, 0700);
       if (stat(d.buf, &st))
        die(1, "couldn't stat `%s': %s", d.buf, strerror(errno));
+      if (!S_ISDIR(st.st_mode))
+       die(1, "object `%s' isn't a directory", d.buf);
       if (st.st_mode & 0077)
        die(1, "parent directory `%s' has group or world access", d.buf);
+      dstr_destroy(&d);
     }
   }
 
@@ -898,8 +978,13 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz)
        die(1, "too many retries; giving up");
       n--;
       if (connect(fd, (struct sockaddr *)sun, sz)) {
+       struct stat st;
        if (errno != ECONNREFUSED)
          die(1, "couldn't bind to address: %s", strerror(e));
+       if (stat(sun->sun_path, &st))
+         die(1, "couldn't stat `%s': %s", sun->sun_path, strerror(errno));
+       if (!S_ISSOCK(st.st_mode))
+         die(1, "object `%s' isn't a socket", sun->sun_path);
        if (verbose)
          log("stale socket found; removing it");
        unlink(sun->sun_path);
@@ -934,28 +1019,53 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz)
 
 /* --- Variables --- */
 
-static selbuf c_server, c_client;;
+static selbuf c_server, c_client;
+static unsigned c_flags = 0;
+
+#define cf_uclose 1u
+#define cf_sclose 2u
+#define cf_cooked 4u
 
 /* --- Line handler functions --- */
 
-static void c_uline(char *s, void *p)
+static void c_uline(char *s, size_t len, void *p)
 {
-  size_t sz;
-  if (!s)
-    exit(0);
-  sz = strlen(s);
-  s[sz++] = '\n';
-  write(c_server.reader.fd, s, sz);
+  if (!s) {
+    selbuf_destroy(&c_client);
+    shutdown(c_server.reader.fd, 1);
+    c_flags |= cf_uclose;
+  } else {
+    s[len++] = '\n';
+    write(c_server.reader.fd, s, len);
+  }
 }
 
-static void c_sline(char *s, void *p)
+static void c_sline(char *s, size_t len, void *p)
 {
   if (!s) {
-    if (verbose > 1)
+    selbuf_destroy(&c_server);
+    if (!(c_flags & cf_uclose)) {
       moan("server closed the connection");
+      selbuf_destroy(&c_client);
+    }
     exit(0);
   }
-  puts(s);
+  if (!(c_flags & cf_cooked))
+    puts(s);
+  else {
+    char *q = str_getword(&s);
+    if (strcmp(q, "FAIL") == 0)
+      die(1, "%s", s);
+    else if (strcmp(q, "INFO") == 0 ||
+            strcmp(q, "ITEM") == 0)
+      puts(s);
+    else if (strcmp(q, "OK") == 0) {
+      if (s && *s) puts(s);
+    } else if (strcmp(q, "MISSING") == 0)
+      ;
+    else
+      moan("unexpected output: %s %s", q, s);
+  }
 }
 
 /* --- @pix_client@ --- *
@@ -973,6 +1083,10 @@ static void pix_client(struct sockaddr_un *sun, size_t sz, char *argv[])
 {
   int fd;
 
+  /* --- Dispose of locked memory --- */
+
+  l_destroy(&lm);
+
   /* --- Open the socket --- */
 
   if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
@@ -995,13 +1109,16 @@ static void pix_client(struct sockaddr_un *sun, size_t sz, char *argv[])
     DPUTC(&d, '\n');
     write(fd, d.buf, d.len);
     shutdown(fd, 1);
+    c_flags |= cf_uclose | cf_cooked;
     dstr_destroy(&d);
-  } 
+  }
 
   /* --- And repeat --- */
 
-  for (;;)
-    sel_select(&sel);
+  for (;;) {
+    if (sel_select(&sel))
+      die(EXIT_FAILURE, "select error: %s", strerror(errno));
+  }
 }
 
 /*----- Main code ---------------------------------------------------------*/
@@ -1019,8 +1136,9 @@ static void usage(FILE *fp)
 {
   pquis(fp, "\
 Usage:\n\
-       $ [-qvidl] [-c command] [-t timeout] [-s socket]\n\
-       $ [-s socket] -C [command args...]\n\
+       $ [-qvfidl] [-c COMMAND] [-t TIMEOUT] [-s SOCKET]\n\
+       $ [-s SOCKET] -C [COMMAND ARGS...]\n\
+       $ [-s SOCKET] -P[P] TAG\n\
 ");
 }
 
@@ -1043,13 +1161,18 @@ protect important keys.  Options provided:\n\
 -u, --usage            Show a (very) terse usage summary.\n\
 \n\
 -C, --client           Connect to a running pixie as a client.\n\
+-P, --passphrase       Request passphrase TAG and print to stdout.\n\
+-PP, --verify-passphrase\n\
+                       Verify passphrase TAG and print to stdout.\n\
 \n\
 -q, --quiet            Emit fewer log messages.\n\
 -v, --version          Emit more log messages.\n\
 -s, --socket=FILE      Name the pixie's socket.\n\
 -c, --command=COMMAND  Shell command to read a passphrase.\n\
+-f, --fetch            Fetch passphrases from the terminal.\n\
 -t, --timeout=TIMEOUT  Length of time to retain a passphrase in memory.\n\
 -i, --interactive      Allow commands to be typed interactively.\n\
+-d, --daemon           Fork into the background after initialization.\n\
 -l, --syslog           Emit log messages to the system log.\n\
 \n\
 The COMMAND may contain `%m' and `%t' markers which are replaced by a\n\
@@ -1082,13 +1205,13 @@ int main(int argc, char *argv[])
   size_t sz;
   unsigned f = 0;
 
-  enum {
-    f_bogus = 1,
-    f_client = 2,
-    f_stdin = 4,
-    f_daemon = 8,
-    f_syslog = 16
-  };
+#define f_bogus 1u
+#define f_client 2u
+#define f_stdin 4u
+#define f_daemon 8u
+#define f_syslog 16u
+#define f_fetch 32u
+#define f_verify 64u
 
   /* --- Initialize libraries --- */
 
@@ -1097,7 +1220,7 @@ int main(int argc, char *argv[])
 
   /* --- Set up the locked memory area --- */
 
-  l_init(&lm, 4096);
+  l_init(&lm, 16384);
   setuid(getuid());
 
   /* --- Parse command line arguments --- */
@@ -1116,8 +1239,11 @@ int main(int argc, char *argv[])
       { "quiet",       0,              0,      'q' },
       { "verbose",     0,              0,      'v' },
       { "client",      0,              0,      'C' },
+      { "passphrase",  0,              0,      'P' },
+      { "verify-passphrase",   0,      0,      '+' },
       { "socket",      OPTF_ARGREQ,    0,      's' },
       { "command",     OPTF_ARGREQ,    0,      'c' },
+      { "fetch",       0,              0,      'f' },
       { "timeout",     OPTF_ARGREQ,    0,      't' },
       { "interactive", 0,              0,      'i' },
       { "stdin",       0,              0,      'i' },
@@ -1127,10 +1253,10 @@ int main(int argc, char *argv[])
 
       /* --- Magic terminator --- */
 
-      { 0,             0,              0,      0 }      
+      { 0,             0,              0,      0 }
     };
 
-    int i = mdwopt(argc, argv, "hVuqvCs:c:t:idl", opts, 0, 0, 0);
+    int i = mdwopt(argc, argv, "hVuqvCPs:c:ft:idl", opts, 0, 0, 0);
     if (i < 0)
       break;
 
@@ -1159,6 +1285,17 @@ int main(int argc, char *argv[])
        break;
       case 'C':
        f |= f_client;
+       f &= ~f_fetch;
+       break;
+      case 'P':
+       if (!(f & f_fetch))
+         f |= f_fetch;
+       else
+         f |= f_verify;
+       break;
+      case '+':
+       f |= f_fetch | f_verify;
+       f &= ~f_client;
        break;
       case 's':
        path = optarg;
@@ -1169,6 +1306,10 @@ int main(int argc, char *argv[])
        break;
       case 'c':
        command = optarg;
+       flags |= F_FETCH;
+       break;
+      case 'f':
+       flags |= F_FETCH;
        break;
       case 'i':
        f |= f_stdin;
@@ -1188,11 +1329,26 @@ int main(int argc, char *argv[])
     }
   }
 
-  if (f & f_bogus || (optind < argc && !(f & f_client))) {
+  if (f & f_bogus ||
+      (optind < argc && !(f & (f_client|f_fetch))) ||
+      ((f & f_fetch) && optind != argc - 1)) {
     usage(stderr);
     exit(1);
   }
 
+  /* --- Handle request for a passphrase --- */
+
+  if (f & f_fetch) {
+    char *buf = l_alloc(&lm, 1024);
+    passphrase_connect(path);
+    if (passphrase_read(argv[optind],
+                       (f & f_verify) ? PMODE_VERIFY : PMODE_READ,
+                       buf, 1024))
+      die(1, "failed to read passphrase: %s", strerror(errno));
+    puts(buf);
+    return (0);
+  }
+
   /* --- Set up the socket address --- */
 
   sun = pixie_address(path, &sz);
@@ -1226,14 +1382,18 @@ int main(int argc, char *argv[])
       log("couldn't lock passphrase buffer");
     }
     dstr_destroy(&d);
+    arena_setsecure(&lm.a);
   }
 
   /* --- Set signal behaviours --- */
 
   {
     static sig sigint, sigterm, sigquit, sighup;
+    struct sigaction sa;
     sig_init(&sel);
-    sig_add(&sigint, SIGINT, pix_sigdie, 0);
+    sigaction(SIGINT, 0, &sa);
+    if (sa.sa_handler != SIG_IGN)
+      sig_add(&sigint, SIGINT, pix_sigdie, 0);
     sig_add(&sigterm, SIGTERM, pix_sigdie, 0);
     sig_add(&sigquit, SIGQUIT, pix_sigflush, 0);
     sig_add(&sighup, SIGHUP, pix_sigflush, 0);
@@ -1242,17 +1402,20 @@ int main(int argc, char *argv[])
   /* --- Set up the server --- */
 
   pix_setup(sun, sz);
-  if (f & f_stdin)
-    pixserv_create(STDIN_FILENO, STDOUT_FILENO);
+  if (f & f_stdin) {
+    pixserv *px = pixserv_create(STDIN_FILENO, STDOUT_FILENO);
+    sel_rmtimer(&px->timer);
+    px->f |= px_stdin;
+  }
 
   /* --- Fork into the background if requested --- */
 
-  if (f & f_daemon) {    
+  if (f & f_daemon) {
     pid_t kid;
 
     if (((f & f_stdin) &&
         (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))) ||
-       !command)
+       (!command && (flags & F_FETCH)))
       die(1, "can't become a daemon if terminal required");
 
     if ((kid = fork()) < 0)
@@ -1268,16 +1431,31 @@ int main(int argc, char *argv[])
       }
     }
 #endif
+    chdir("/");
     setsid();
 
-    if (fork() > 0)
+    if (fork() != 0)
       _exit(0);
   }
 
   if (verbose)
     log("initialized ok");
-  for (;;)
-    sel_select(&sel);
+
+  {
+    int selerr = 0;
+    for (;;) {
+      if (!sel_select(&sel))
+       selerr = 0;
+      else if (errno != EINTR && errno != EAGAIN) {
+       log("error from select: %s", strerror(errno));
+       selerr++;
+       if (selerr > 8) {
+         log("too many consecutive select errors: bailing out");
+         exit(EXIT_FAILURE);
+       }
+      }
+    }
+  }
   return (0);
 }