Rearrange the file tree.
[u/mdw/catacomb] / pixie.c
diff --git a/pixie.c b/pixie.c
deleted file mode 100644 (file)
index af6483a..0000000
--- a/pixie.c
+++ /dev/null
@@ -1,1462 +0,0 @@
-/* -*-c-*-
- *
- * $Id$
- *
- * Passphrase pixie for Catacomb
- *
- * (c) 1999 Straylight/Edgeware
- */
-
-/*----- Licensing notice --------------------------------------------------*
- *
- * This file is part of Catacomb.
- *
- * Catacomb is free software; you can redistribute it and/or modify
- * 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.
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include "config.h"
-
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <pwd.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <termios.h>
-#include <syslog.h>
-
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include <mLib/alloc.h>
-#include <mLib/dstr.h>
-#include <mLib/fdflags.h>
-#include <mLib/mdwopt.h>
-#include <mLib/quis.h>
-#include <mLib/report.h>
-#include <mLib/sel.h>
-#include <mLib/selbuf.h>
-#include <mLib/sig.h>
-#include <mLib/str.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 = 900;
-static sel_state sel;
-static unsigned verbose = 1;
-static const char *command = 0;
-static lmem lm;
-static unsigned flags = 0;
-
-#define F_SYSLOG 1u
-#define F_FETCH 2u
-
-/*----- Event logging -----------------------------------------------------*/
-
-/* --- @log@ --- *
- *
- * Arguments:  @const char *p@ = @printf@-style format string
- *             @...@ = extra arguments to fill in
- *
- * Returns:    ---
- *
- * Use:                Writes out a timestamped log message.
- */
-
-static void log(const char *p, ...)
-{
-  dstr d = DSTR_INIT;
-  va_list ap;
-
-  if (!(flags & F_SYSLOG)) {
-    time_t t = time(0);
-    struct tm *tm = localtime(&t);
-    DENSURE(&d, 64);
-    d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
-  }
-  va_start(ap, p);
-  dstr_vputf(&d, p, &ap);
-  va_end(ap);
-
-  if (flags & F_SYSLOG)
-    syslog(LOG_NOTICE, "%s", d.buf);
-  else {
-    DPUTC(&d, '\n');
-    dstr_write(&d, stderr);
-  }
-  DDESTROY(&d);
-}
-
-/*----- Passphrase management ---------------------------------------------*/
-
-/* --- Data structures --- */
-
-typedef struct phrase {
-  struct phrase *next;
-  struct phrase *prev;
-  char *tag;
-  char *p;
-  unsigned long t;
-  sel_timer timer;
-  unsigned f;
-} phrase;
-
-/* --- Variables --- */
-
-#define P_ROOT ((phrase *)&p_root)
-static struct { phrase *next; phrase *prev; } p_root = { P_ROOT, P_ROOT };
-
-/* --- @p_free@ --- *
- *
- * Arguments:  @phrase *p@ = pointer to phrase block
- *
- * Returns:    ---
- *
- * Use:                Frees a phrase block.
- */
-
-static void p_free(phrase *p)
-{
-  if (p->t)
-    sel_rmtimer(&p->timer);
-  xfree(p->tag);
-  l_free(&lm, p->p);
-  p->next->prev = p->prev;
-  p->prev->next = p->next;
-  DESTROY(p);
-}
-
-/* --- @p_timer@ --- *
- *
- * Arguments:  @struct timeval *tv@ = current time
- *             @void *p@ = pointer to phrase
- *
- * Returns:    ---
- *
- * Use:                Expires a passphrase.
- */
-
-static void p_timer(struct timeval *tv, void *p)
-{
-  phrase *pp = p;
-  if (verbose)
-    log("expiring passphrase `%s'", pp->tag);
-  p_free(pp);
-}
-
-/* --- @p_alloc@ --- *
- *
- * Arguments:  @size_t sz@ = amount of memory required
- *
- * Returns:    Pointer to allocated memory, or null.
- *
- * Use:                Allocates some locked memory, flushing old passphrases if
- *             there's not enough space.
- */
-
-static void *p_alloc(size_t sz)
-{
-  for (;;) {
-    char *p;
-    if ((p = l_alloc(&lm, sz)) != 0)
-      return (p);
-    if (P_ROOT->next == P_ROOT)
-      return (0);
-    if (verbose) {
-      log("flushing passphrase `%s' to free up needed space",
-         P_ROOT->next->tag);
-    }
-    p_free(P_ROOT->next);
-  }
-}
-
-/* --- @p_find@ --- *
- *
- * Arguments:  @const char *tag@ = pointer to tag to find
- *
- * Returns:    Pointer to passphrase block, or null.
- *
- * Use:                Finds a passphrase with a given tag.
- */
-
-static phrase *p_find(const char *tag)
-{
-  phrase *p;
-
-  for (p = P_ROOT->next; p != P_ROOT; p = p->next) {
-    if (strcmp(p->tag, tag) == 0) {
-      if (p->t) {
-       struct timeval tv;
-       sel_rmtimer(&p->timer);
-       gettimeofday(&tv, 0);
-       tv.tv_sec += p->t;
-       sel_addtimer(&sel, &p->timer, &tv, p_timer, p);
-      }
-      p->next->prev = p->prev;
-      p->prev->next = p->next;
-      p->next = P_ROOT;
-      p->prev = P_ROOT->prev;
-      P_ROOT->prev->next = p;
-      P_ROOT->prev = p;
-      return (p);
-    }
-  }
-  return (0);
-}
-
-/* --- @p_add@ --- *
- *
- * Arguments:  @const char *tag@ = pointer to tag string
- *             @const char *p@ = pointer to passphrase
- *             @unsigned long t@ = expiry timeout
- *
- * Returns:    Pointer to newly-added passphrase.
- *
- * Use:                Adds a new passphrase.  The tag must not already exist.
- */
-
-static phrase *p_add(const char *tag, const char *p, unsigned long t)
-{
-  size_t sz = strlen(p) + 1;
-  char *l = p_alloc(sz);
-  phrase *pp;
-
-  /* --- Make sure the locked memory was allocated --- */
-
-  if (!l)
-    return (0);
-
-  /* --- Fill in some other bits of the block --- */
-
-  pp = CREATE(phrase);
-  memcpy(l, p, sz);
-  pp->p = l;
-  pp->tag = xstrdup(tag);
-  pp->f = 0;
-
-  /* --- Set the timer --- */
-
-  pp->t = t;
-  if (t) {
-    struct timeval tv;
-    gettimeofday(&tv, 0);
-    tv.tv_sec += t;
-    sel_addtimer(&sel, &pp->timer, &tv, p_timer, pp);
-  }
-
-  /* --- Link the block into the chain --- */
-
-  pp->next = P_ROOT;
-  pp->prev = P_ROOT->prev;
-  P_ROOT->prev->next = pp;
-  P_ROOT->prev = pp;
-  return (pp);
-}
-
-/* --- @p_flush@ --- *
- *
- * Arguments:  @const char *tag@ = pointer to tag string, or zero for all
- *
- * Returns:    ---
- *
- * Use:                Immediately flushes either a single phrase or all of them.
- */
-
-static void p_flush(const char *tag)
-{
-  phrase *p;
-
-  if (!tag && verbose > 1)
-    log("flushing all passphrases");
-  p = P_ROOT->next;
-  while (p != P_ROOT) {
-    phrase *pp = p->next;
-    if (!tag)
-      p_free(p);
-    else if (strcmp(p->tag, tag) == 0) {
-      if (verbose > 1)
-       log("flushing passphrase `%s'", tag);
-      p_free(p);
-      break;
-    }
-    p = pp;
-  }
-}
-
-/*----- Reading passphrases -----------------------------------------------*/
-
-/* --- @p_request@ --- *
- *
- * Arguments:  @const char *msg@ = message string
- *             @const char *tag@ = pointer to tag string
- *             @char *buf@ = pointer to (locked) buffer
- *             @size_t sz@ = size of buffer
- *
- * Returns:    Zero if all went well, nonzero otherwise.
- *
- * Use:                Requests a passphrase from the user.
- */
-
-static int p_request(const char *msg, const char *tag, char *buf, size_t sz)
-{
-  /* --- If there's a passphrase-fetching command, run it --- */
-
-  if (command) {
-    dstr d = DSTR_INIT;
-    const char *p;
-    int fd[2];
-    pid_t kid;
-    int r;
-    int rc;
-
-    /* --- Substitute the prompt string into the command --- */
-
-    p = command;
-    for (;;) {
-      const char *q = strchr(p, '%');
-      if (!q || !q[1]) {
-       DPUTS(&d, p);
-       break;
-      }
-      DPUTM(&d, p, q - p);
-      p = q + 1;
-      switch (*p) {
-       case 'm':
-         DPUTS(&d, msg);
-         break;
-       case 't':
-         DPUTS(&d, tag);
-         break;
-       default:
-         DPUTC(&d, '%');
-         DPUTC(&d, *p);
-         break;
-      }
-      p++;
-    }
-    DPUTZ(&d);
-
-    /* --- Create a pipe and start a child process --- */
-
-    if (pipe(fd))
-      goto fail_1;
-    if ((kid = fork()) < 0)
-      goto fail_2;
-
-    /* --- Child process --- */
-
-    fflush(0);
-    if (kid == 0) {
-      if (dup2(fd[1], STDOUT_FILENO) < 0)
-       _exit(127);
-      close(fd[0]);
-      execl("/bin/sh", "sh", "-c", d.buf, (char *)0);
-      _exit(127);
-    }
-
-    /* --- Read the data back into my buffer --- */
-
-    close(fd[1]);
-    if ((r = read(fd[0], buf, sz - 1)) >= 0) {
-      char *q = memchr(buf, '\n', r);
-      if (!q)
-       q = buf + r;
-      *q = 0;
-    }
-    close(fd[0]);
-    waitpid(kid, &rc, 0);
-    dstr_destroy(&d);
-    if (r < 0 || rc != 0)
-      goto fail_0;
-    goto ok;
-
-    /* --- Tidy up when things go wrong --- */
-
-  fail_2:
-    close(fd[0]);
-    close(fd[1]);
-  fail_1:
-    dstr_destroy(&d);
-  fail_0:
-    return (-1);
-  }
-
-  /* --- Read a passphrase from the terminal --- *
-   *
-   * Use the standard Catacomb passphrase-reading function, so it'll read the
-   * passphrase from a file descriptor or something if the appropriate
-   * environment variable is set.
-   */
-
-  {
-    dstr d = DSTR_INIT;
-    int rc;
-    dstr_putf(&d, "%s %s: ", msg, tag);
-    rc = pixie_getpass(d.buf, buf, sz);
-    dstr_destroy(&d);
-    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 **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:    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 int p_get(const char **q, const char *tag, unsigned mode, time_t exp)
-{
-#define LBUFSZ 1024
-
-  phrase *p;
-  char *pp = 0;
-
-  /* --- Write a log message --- */
-
-  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 (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(mode == PMODE_READ ? "Passphrase" : "New passphrase",
-                 tag, pp, LBUFSZ) < 0)
-      goto fail;
-    p = p_add(tag, pp, exp);
-    if (!p)
-      goto fail;
-  }
-
-  /* --- If verification is requested, verify the passphrase --- */
-
-  if (mode == PMODE_VERIFY) {
-    if (!pp && (pp = p_alloc(LBUFSZ)) == 0)
-      goto fail;
-    if (p_request("Verify passphrase", tag, pp, LBUFSZ) < 0)
-      goto fail;
-    if (strcmp(pp, p->p) != 0) {
-      if (verbose)
-       log("passphrases for `%s' don't match", tag);
-      p_free(p);
-      goto fail;
-    }
-  }
-
-  /* --- Tidy up and return the passphrase --- */
-
-  if (pp) {
-    memset(pp, 0, LBUFSZ);
-    l_free(&lm, pp);
-  }
-  *q = p->p;
-  return (0);
-
-  /* --- Tidy up if things went wrong --- */
-
-fail:
-  if (pp) {
-    memset(pp, 0, LBUFSZ);
-    l_free(&lm, pp);
-  }
-  return (-1);
-
-#undef LBUFSZ
-}
-
-/*----- Server command parsing --------------------------------------------*/
-
-/* --- Data structures --- */
-
-typedef struct pixserv {
-  selbuf b;
-  int fd;
-  sel_timer timer;
-  unsigned f;
-} pixserv;
-
-#define px_stdin 1u
-
-#define PIXSERV_TIMEOUT 30
-
-/* --- @pixserv_expire@ --- *
- *
- * Arguments:  @struct timeval *tv@ = pointer to current time
- *             @void *p@ = pointer to server block
- *
- * Returns:    ---
- *
- * Use:                Expires a pixie connection if the remote end decides he's not
- *             interested any more.
- */
-
-static void pixserv_expire(struct timeval *tv, void *p)
-{
-  pixserv *px = p;
-  if (px->fd != px->b.reader.fd)
-    close(px->fd);
-  selbuf_destroy(&px->b);
-  close(px->b.reader.fd);
-  DESTROY(px);
-}
-
-/* --- @pixserv_write@ --- *
- *
- * Arguments:  @pixserv *px@ = pointer to server block
- *             @const char *p@ = pointer to skeleton string
- *             @...@ = other arguments to fill in
- *
- * Returns:    ---
- *
- * Use:                Formats a string and emits it to the output file.
- */
-
-static void pixserv_write(pixserv *px, const char *p, ...)
-{
-  dstr d = DSTR_INIT;
-  va_list ap;
-
-  va_start(ap, p);
-  dstr_vputf(&d, p, &ap);
-  write(px->fd, d.buf, d.len);
-  va_end(ap);
-  dstr_destroy(&d);
-}
-
-/* --- @pixserv_timeout@ --- *
- *
- * Arguments:  @const char *p@ = pointer to timeout string
- *
- * Returns:    Timeout in seconds.
- *
- * Use:                Translates a string to a timeout value in seconds.
- */
-
-static unsigned long pixserv_timeout(const char *p)
-{
-  unsigned long t;
-  char *q;
-
-  if (!p)
-    return (timeout);
-
-  t = strtoul(p, &q, 0);
-  switch (*q) {
-    case 'd': t *= 24;
-    case 'h': t *= 60;
-    case 'm': t *= 60;
-    case 's': if (q[1] != 0)
-      default:   t = 0;
-    case 0:   break;
-  }
-  return (t);
-}
-
-/* --- @pixserv_line@ --- *
- *
- * Arguments:  @char *s@ = pointer to the line read
- *             @size_t len@ = length of the line
- *             @void *p@ = pointer to server block
- *
- * Returns:    ---
- *
- * Use:                Handles a line read from the client.
- */
-
-static void pixserv_line(char *s, size_t len, void *p)
-{
-  pixserv *px = p;
-  char *q, *qq;
-  unsigned mode;
-
-  /* --- Handle an end-of-file --- */
-
-  if (!(px->f & px_stdin))
-    sel_rmtimer(&px->timer);
-  if (!s) {
-    if (px->fd != px->b.reader.fd)
-      close(px->fd);
-    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;
-    sel_addtimer(&sel, &px->timer, &tv, pixserv_expire, px);
-  }
-
-  /* --- Scan out the first word --- */
-
-  if ((q = str_getword(&s)) == 0)
-    return;
-  for (qq = q; *qq; qq++)
-    *qq = tolower((unsigned char)*qq);
-
-  /* --- Handle a help request --- */
-
-  if (strcmp(q, "help") == 0) {
-    pixserv_write(px, "\
-INFO Commands supported:\n\
-INFO HELP\n\
-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\
-");
-  }
-
-  /* --- List the passphrases --- */
-
-  else if (strcmp(q, "list") == 0) {
-    phrase *p;
-
-    for (p = P_ROOT->next; p != P_ROOT; p = p->next) {
-      if (!p->t)
-       pixserv_write(px, "ITEM %s no-expire\n", p->tag);
-      else {
-       struct timeval tv;
-       gettimeofday(&tv, 0);
-       TV_SUB(&tv, &p->timer.tv, &tv);
-       pixserv_write(px, "ITEM %s %i\n", p->tag, tv.tv_sec);
-      }
-    }
-    pixserv_write(px, "OK\n");
-  }
-
-  /* --- Request a passphrase --- */
-
-  else if ((mode = PMODE_READ, strcmp(q, "pass") == 0) ||
-          (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 {
-      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 --- */
-
-  else if (strcmp(q, "flush") == 0) {
-    q = str_getword(&s);
-    p_flush(q);
-    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("%s client requested shutdown",
-         px->f & px_stdin ? "local" : "remote");
-    pixserv_write(px, "OK\n");
-    exit(0);
-  }
-
-  /* --- Report an error for other commands --- */
-
-  else
-    pixserv_write(px, "FAIL unknown command `%s'\n", q);
-}
-
-/* --- @pixserv_create@ --- *
- *
- * Arguments:  @int fd@ = file descriptor to read from
- *             @int ofd@ = file descriptor to write to
- *
- * Returns:    Pointer to the new connection.
- *
- * Use:                Creates a new Pixie server instance for a new connection.
- */
-
-static pixserv *pixserv_create(int fd, int ofd)
-{
-  pixserv *px = CREATE(pixserv);
-  struct timeval tv;
-  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-  if (ofd != fd)
-    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@ --- *
- *
- * Arguments:  @int fd@ = file descriptor
- *             @unsigned mode@ = what's happened
- *             @void *p@ = an uninteresting argument
- *
- * Returns:    ---
- *
- * Use:                Accepts a new connection.
- */
-
-static void pixserv_accept(int fd, unsigned mode, void *p)
-{
-  int nfd;
-  struct sockaddr_un sun;
-  size_t sunsz = sizeof(sun);
-
-  if (mode != SEL_READ)
-    return;
-  if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) {
-    if (verbose && errno != EAGAIN && errno != EWOULDBLOCK &&
-       errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
-      log("new connection failed: %s", strerror(errno));
-    return;
-  }
-  pixserv_create(nfd, nfd);
-}
-
-/*----- Setting up the server ---------------------------------------------*/
-
-/* --- @unlinksocket@ --- *
- *
- * Arguments:  ---
- *
- * Returns:    ---
- *
- * Use:                Tidies up the socket when it's finished with.
- */
-
-static char *sockpath;
-
-static void unlinksocket(void)
-{
-  unlink(sockpath);
-  l_purge(&lm);
-}
-
-/* --- @pix_sigdie@ --- *
- *
- * Arguments:  @int sig@ = signal number
- *             @void *p@ = uninteresting argument
- *
- * Returns:    ---
- *
- * Use:                Shuts down the program after a fatal signal.
- */
-
-static void pix_sigdie(int sig, void *p)
-{
-  if (verbose) {
-    char *p;
-    char buf[20];
-
-    switch (sig) {
-      case SIGTERM:    p = "SIGTERM"; break;
-      case SIGINT:     p = "SIGINT"; break;
-      default:
-       sprintf(buf, "signal %i", sig);
-       p = buf;
-       break;
-    }
-    log("shutting down on %s", p);
-  }
-  exit(0);
-}
-
-/* --- @pix_sigflush@ --- *
- *
- * Arguments:  @int sig@ = signal number
- *             @void *p@ = uninteresting argument
- *
- * Returns:    ---
- *
- * Use:                Flushes the passphrase cache on receipt of a signal.
- */
-
-static void pix_sigflush(int sig, void *p)
-{
-  if (verbose) {
-    char *p;
-    char buf[20];
-
-    switch (sig) {
-      case SIGHUP:     p = "SIGHUP"; break;
-      case SIGQUIT:    p = "SIGQUIT"; break;
-      default:
-       sprintf(buf, "signal %i", sig);
-       p = buf;
-       break;
-    }
-    log("received %s; flushing passphrases", p);
-  }
-  p_flush(0);
-}
-
-/* --- @pix_setup@ --- *
- *
- * Arguments:  @struct sockaddr_un *sun@ = pointer to address to use
- *             @size_t sz@ = size of socket address
- *
- * Returns:    ---
- *
- * Use:                Sets up the pixie's Unix-domain socket.
- */
-
-static void pix_setup(struct sockaddr_un *sun, size_t sz)
-{
-  int fd;
-
-  /* --- Set up the parent directory --- */
-
-  {
-    char *p = sun->sun_path;
-    char *q = strrchr(p, '/');
-
-    if (q) {
-      dstr d = DSTR_INIT;
-      struct stat st;
-
-      DPUTM(&d, p, q - p);
-      DPUTZ(&d);
-
-      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);
-    }
-  }
-
-  /* --- Initialize the socket --- */
-
-  {
-    int n = 5;
-    int e;
-
-    umask(0077);
-  again:
-    if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
-      die(1, "couldn't create socket: %s", strerror(errno));
-    if (bind(fd, (struct sockaddr *)sun, sz) < 0) {
-      e = errno;
-      if (errno != EADDRINUSE)
-       die(1, "couldn't bind to address: %s", strerror(e));
-      if (!n)
-       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);
-       close(fd);
-      } else {
-       if (verbose)
-         log("server already running; shutting it down");
-       write(fd, "QUIT\n", 5);
-       sleep(1);
-       close(fd);
-      }
-      goto again;
-    }
-    chmod(sun->sun_path, 0600);
-    fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-    if (listen(fd, 5))
-      die(1, "couldn't listen on socket: %s", strerror(errno));
-  }
-
-  /* --- Set up the rest of the server --- */
-
-  {
-    static sel_file serv;
-    sockpath = sun->sun_path;
-    atexit(unlinksocket);
-    sel_initfile(&sel, &serv, fd, SEL_READ, pixserv_accept, 0);
-    sel_addfile(&serv);
-  }
-}
-
-/*----- Client support code -----------------------------------------------*/
-
-/* --- Variables --- */
-
-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, size_t len, void *p)
-{
-  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, size_t len, void *p)
-{
-  if (!s) {
-    selbuf_destroy(&c_server);
-    if (!(c_flags & cf_uclose)) {
-      moan("server closed the connection");
-      selbuf_destroy(&c_client);
-    }
-    exit(0);
-  }
-  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@ --- *
- *
- * Arguments:  @struct sockaddr_un *sun@ = pointer to socket address
- *             @size_t sz@ = size of socket address
- *             @char *argv[]@ = pointer to arguments to send
- *
- * Returns:    ---
- *
- * Use:                Performs client-side actions for the passphrase pixie.
- */
-
-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)
-    die(1, "couldn't create socket: %s", strerror(errno));
-  if (connect(fd, (struct sockaddr *)sun, sz))
-    die(1, "couldn't connect to server: %s", strerror(errno));
-  selbuf_init(&c_server, &sel, fd, c_sline, 0);
-
-  /* --- If there are any arguments, turn them into a string --- */
-
-  if (!*argv)
-    selbuf_init(&c_client, &sel, STDIN_FILENO, c_uline, 0);
-  else {
-    dstr d = DSTR_INIT;
-    DPUTS(&d, *argv++);
-    while (*argv) {
-      DPUTC(&d, ' ');
-      DPUTS(&d, *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 (;;) {
-    if (sel_select(&sel))
-      die(EXIT_FAILURE, "select error: %s", strerror(errno));
-  }
-}
-
-/*----- Main code ---------------------------------------------------------*/
-
-/* --- @help@, @version@, @usage@ --- *
- *
- * Arguments:  @FILE *fp@ = stream to write on
- *
- * Returns:    ---
- *
- * Use:                Emit helpful messages.
- */
-
-static void usage(FILE *fp)
-{
-  pquis(fp, "\
-Usage:\n\
-       $ [-qvfidl] [-c COMMAND] [-t TIMEOUT] [-s SOCKET]\n\
-       $ [-s SOCKET] -C [COMMAND ARGS...]\n\
-       $ [-s SOCKET] -P[P] TAG\n\
-");
-}
-
-static void version(FILE *fp)
-{
-  pquis(fp, "$, Catacomb version " VERSION "\n");
-}
-
-static void help(FILE *fp)
-{
-  version(fp);
-  fputc('\n', fp);
-  usage(fp);
-  pquis(fp, "\n\
-The Catacomb passphrase pixie collects and caches passphrases used to\n\
-protect important keys.  Options provided:\n\
-\n\
--h, --help             Show this help text.\n\
--V, --version          Show the program's version number.\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\
-prompt message and the passphrase tag respectively.  The TIMEOUT is an\n\
-integer, optionally followed by `d', `h', `m' or `s' to specify units of\n\
-days, hours, minutes or seconds respectively.\n\
-\n\
-In client mode, if a command is specified on the command line, it is sent\n\
-to the running server; otherwise the program reads requests from stdin.\n\
-Responses from the pixie are written to stdout.  Send a HELP request for\n\
-a quick summary of the pixie communication protocol.\n\
-");
-}
-
-/* --- @main@ --- *
- *
- * Arguments:  @int argc@ = number of arguments
- *             @char *argv[]@ = vector of argument values
- *
- * Returns:    Zero if OK.
- *
- * Use:                Main program.  Listens on a socket and responds with a PGP
- *             passphrase when asked.
- */
-
-int main(int argc, char *argv[])
-{
-  char *path = 0;
-  struct sockaddr_un *sun;
-  size_t sz;
-  unsigned f = 0;
-
-#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 --- */
-
-  ego(argv[0]);
-  sub_init();
-
-  /* --- Set up the locked memory area --- */
-
-  l_init(&lm, 16384);
-  setuid(getuid());
-
-  /* --- Parse command line arguments --- */
-
-  for (;;) {
-    static struct option opts[] = {
-
-      /* --- Standard GNUy help options --- */
-
-      { "help",                0,              0,      'h' },
-      { "version",     0,              0,      'V' },
-      { "usage",       0,              0,      'u' },
-
-      /* --- Other options --- */
-
-      { "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' },
-      { "daemon",      0,              0,      'd' },
-      { "log",         0,              0,      'l' },
-      { "syslog",      0,              0,      'l' },
-
-      /* --- Magic terminator --- */
-
-      { 0,             0,              0,      0 }
-    };
-
-    int i = mdwopt(argc, argv, "hVuqvCPs:c:ft:idl", opts, 0, 0, 0);
-    if (i < 0)
-      break;
-
-    switch (i) {
-
-      /* --- GNUy help options --- */
-
-      case 'h':
-       help(stdout);
-       exit(0);
-      case 'V':
-       version(stdout);
-       exit(0);
-      case 'u':
-       usage(stdout);
-       exit(0);
-
-      /* --- Other interesting things --- */
-
-      case 'q':
-       if (verbose)
-         verbose--;
-       break;
-      case 'v':
-       verbose++;
-       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;
-       break;
-      case 't':
-       if ((timeout = pixserv_timeout(optarg)) == 0)
-         die(1, "bad timeout `%s'", optarg);
-       break;
-      case 'c':
-       command = optarg;
-       flags |= F_FETCH;
-       break;
-      case 'f':
-       flags |= F_FETCH;
-       break;
-      case 'i':
-       f |= f_stdin;
-       break;
-      case 'd':
-       f |= f_daemon;
-       break;
-      case 'l':
-       f |= f_syslog;
-       break;
-
-      /* --- Something else --- */
-
-      default:
-       f |= f_bogus;
-       break;
-    }
-  }
-
-  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);
-
-  /* --- Initialize selectory --- */
-
-  sel_init(&sel);
-  signal(SIGPIPE, SIG_IGN);
-
-  /* --- Be a client if a client's wanted --- */
-
-  if (f & f_client)
-    pix_client(sun, sz, argv + optind);
-
-  /* --- Open the syslog if requested --- */
-
-  if (f & f_syslog) {
-    flags |= F_SYSLOG;
-    openlog(QUIS, 0, LOG_DAEMON);
-  }
-
-  /* --- Check on the locked memory area --- */
-
-  {
-    dstr d = DSTR_INIT;
-    int rc = l_report(&lm, &d);
-    if (rc < 0)
-      die(EXIT_FAILURE, d.buf);
-    else if (rc && verbose) {
-      log(d.buf);
-      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);
-    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);
-  }
-
-  /* --- Set up the server --- */
-
-  pix_setup(sun, sz);
-  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) {
-    pid_t kid;
-
-    if (((f & f_stdin) &&
-        (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))) ||
-       (!command && (flags & F_FETCH)))
-      die(1, "can't become a daemon if terminal required");
-
-    if ((kid = fork()) < 0)
-      die(1, "fork failed: %s", strerror(errno));
-    if (kid)
-      _exit(0);
-#ifdef TIOCNOTTY
-    {
-      int fd;
-      if ((fd = open("/dev/tty", O_RDONLY)) >= 0) {
-       ioctl(fd, TIOCNOTTY);
-       close(fd);
-      }
-    }
-#endif
-    chdir("/");
-    setsid();
-
-    if (fork() != 0)
-      _exit(0);
-  }
-
-  if (verbose)
-    log("initialized ok");
-
-  {
-    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);
-}
-
-/*----- That's all, folks -------------------------------------------------*/