Initial revision
authormdw <mdw>
Sat, 23 Oct 1999 10:58:48 +0000 (10:58 +0000)
committermdw <mdw>
Sat, 23 Oct 1999 10:58:48 +0000 (10:58 +0000)
.cvsignore [new file with mode: 0644]
.links [new file with mode: 0644]
.skelrc [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
configure.in [new file with mode: 0644]
pgp-pixie.in [new file with mode: 0644]
pixie.1 [new file with mode: 0644]
pixie.c [new file with mode: 0644]
setup [new file with mode: 0755]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..9073e24
--- /dev/null
@@ -0,0 +1,3 @@
+Makefile.in
+configure
+build
diff --git a/.links b/.links
new file mode 100644 (file)
index 0000000..720c5f4
--- /dev/null
+++ b/.links
@@ -0,0 +1,6 @@
+COPYING
+install-sh
+mkinstalldirs
+missing
+mdwopt.c
+mdwopt.h
diff --git a/.skelrc b/.skelrc
new file mode 100644 (file)
index 0000000..e79a052
--- /dev/null
+++ b/.skelrc
@@ -0,0 +1,7 @@
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+      (append
+       '((program . "PGP pixie")
+        (author . "Mark Wooding"))
+       skel-alist))
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..7e0273c
--- /dev/null
@@ -0,0 +1,49 @@
+## -*-makefile-*-
+##
+## $Id: Makefile.am,v 1.1 1999/10/23 10:58:49 mdw Exp $
+##
+## Makefile for PGP pixie
+##
+## (c) 1999 Mark Wooding
+##
+
+##----- Licensing notice ----------------------------------------------------
+##
+## PGP pixie is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## PGP pixie 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 General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with PGP pixie; if not, write to the Free Software Foundation,
+## Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+##----- Revision history ----------------------------------------------------
+##
+## $Log: Makefile.am,v $
+## Revision 1.1  1999/10/23 10:58:49  mdw
+## Initial revision
+##
+
+AUTOMAKE_OPTIONS = foreign
+
+bin_PROGRAMS = pixie
+bin_SCRIPTS = pgp-pixie
+man_MANS = pixie.1
+pixie_SOURCES = pixie.c mdwopt.c mdwopt.h
+
+EXTRA_DIST = pixie.1
+
+install-exec-hook:
+       pixie="$(bindir)/`echo pixie|sed $(transform)`"; \
+       chown root $$pixie && chmod 4755 $$pixie || {\
+         echo ">>>"; echo ">>> You might want to make $$pixie"; \
+         echo ">>> setuid-root";\
+         echo ">>>"; }
+
+##----- That's all, folks ---------------------------------------------------
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..70b4abe
--- /dev/null
@@ -0,0 +1,49 @@
+dnl -*-fundamental-*-
+dnl
+dnl $Id: configure.in,v 1.1 1999/10/23 10:58:49 mdw Exp $
+dnl
+dnl Configuration script for PGP pixie
+dnl
+dnl (c) 1999 Mark Wooding
+dnl
+
+dnl ----- Licensing notice --------------------------------------------------
+dnl
+dnl PGP pixie is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl 
+dnl PGP pixie is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU General Public License for more details.
+dnl 
+dnl You should have received a copy of the GNU General Public License
+dnl along with PGP pixie; if not, write to the Free Software Foundation,
+dnl Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+dnl ----- Revision history --------------------------------------------------
+dnl
+dnl $Log: configure.in,v $
+dnl Revision 1.1  1999/10/23 10:58:49  mdw
+dnl Initial revision
+dnl
+
+AC_INIT(pixie.c)
+AM_INIT_AUTOMAKE(pixie, 1.0.0)
+AC_PROG_CC
+mdw_GCC_FLAGS
+AC_PATH_PROG(PATH_XGETLINE, xgetline)
+if test -n "$PATH_XGETLINE"; then
+  AC_DEFINE_UNQUOTED(PATH_XGETLINE, "$PATH_XGETLINE")
+fi
+AC_CHECK_FUNCS(mlock)
+mdw_prefix=$prefix mdw_exec_prefix=$exec_prefix
+test "$prefix" = "NONE" && prefix=$ac_default_prefix
+test "$exec_prefix" = "NONE" && exec_prefix=$prefix
+PIXIE=`eval echo $bindir`/`echo pixie|sed ${program_transform_name}`
+AC_SUBST(PIXIE)
+AC_OUTPUT(Makefile pgp-pixie)
+
+dnl ----- That's all, folks -------------------------------------------------
diff --git a/pgp-pixie.in b/pgp-pixie.in
new file mode 100644 (file)
index 0000000..4d59742
--- /dev/null
@@ -0,0 +1,4 @@
+#! /bin/sh
+
+pixie=@PIXIE@
+exec $pixie -d "${PGPPATH-${HOME}/.pgp}/.wrapper" "$@"
diff --git a/pixie.1 b/pixie.1
new file mode 100644 (file)
index 0000000..6a10ee0
--- /dev/null
+++ b/pixie.1
@@ -0,0 +1,206 @@
+.\" -*-nroff-*-
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.
+.TH pixie 1 "14 October 1999"
+.SH "NAME"
+pixie \- new, improved PGP passphrase pixie
+.SH "SYNOPSIS"
+.B pixie
+.RB [ \-xqv ]
+.RB [ \-t
+.IR timeout ]
+.RB [ \-d
+.IR dir ]
+.RI [ socket ]
+.PP
+.B pgp-pixie
+.SH "DESCRIPTION"
+The passphrase pixie sets up a Unix-domain socket and gives your PGP
+passphrase to anyone who connects to it.  It tries quite hard to make
+sure that only your own processes are allowed to do this, and to be
+secure in other ways.  This is mostly useful with Ian Jackson's
+.B auto-pgp
+tools.
+.SS "Command line options"
+The
+.B pixie
+program understands the following command line options:
+.TP
+.B "\-h, \-\-help"
+Prints a relatively comprehensive help message, and exit successfully.
+.TP
+.B "\-V, \-\-version"
+Print the pixie's version number and exit successfully.
+.TP
+.B "\-u, \-\-usage"
+Print a terse usage summary and exit successfully.
+.TP
+.B "\-x, \-\-x11"
+Use the
+.BR xgetline (1)
+program to request a passphrase when necessary, rather than requesting
+the passphrase from the terminal.
+.RB ( xgetline
+is part of the
+.B xtoys
+distribution.  It requires the GTK library.)  Use the
+.B +x
+or
+.B \-\-no\-x11
+option to prevent use of
+.BR xgetline .
+.TP
+.B "\-q, \-\-quiet"
+Produce fewer messages.  This can be used multiple times to make the
+pixie very quiet.
+.TP
+.B "\-v, \-\-verbose"
+Produce more messages.  This can be used multiple times to make the
+pixie more verbose.
+.TP
+.BI "\-t, \-\-timeout=" timeout
+Sets a timeout for the user's passphrase.  The timeout is, by default,
+in seconds, although a suffix
+.RB ` m ',
+.RB ` h '
+or
+.RB ` d '
+can be added to specify minutes, hours or days respectively.  A timeout
+of zero means that the pixie will never time out a passphrase.  The
+default is to time out a passphrase after 5 minutes.
+.TP
+.BI "\-d, \-\-dir=" directory
+Create, secure and set the named directory as being current before
+creating a socket.  If the directory does not exist, it is created with
+mode 700 (readable and writable only by owner); if it does exist, it is
+checked to ensure that it's owned by the calling user, and it's not
+readable or writable by anyone other than the caller.
+.PP
+If neither of
+.B \-x
+nor
+.B +x
+are specified on the command line, the pixie decides for itself how to
+ask for a passphrase.  If standard input is a terminal, it uses the
+terminal; otherwise it tries
+.BR xgetline .
+.PP
+A socket name is required if no
+.B \-d
+option is given; otherwise it's optional and defaults to
+.BR pass-socket .
+.PP
+The
+.B pgp-pixie
+shell script provides some default arguments to the main
+.B pixie
+program which put the socket in
+.BR $PGPPATH/.wrapper/pass-socket ,
+where the other
+.B auto-pgp
+tools expect it to be.  It passes any command line options straight on
+to the main
+.B pixie
+program.
+.SS "Pixie initialization"
+The pixie initializes itself as follows:
+.hP 1.
+If the operating system supports locking pages into memory, the pixie
+requests a page of memory and then attempts to lock it.  If successful,
+the passphrase will be stored in this area of memory to prevent its
+being swapped out to disk.
+.hP 2.
+The pixie sets its effective uid the same as its real uid, dropping any
+setuid privileges the program might have had.
+.hP 3.
+If the attempt to lock the page failed, or the operating system doesn't
+support locking pages, a buffer of normal memory is allocated for the
+passphrase.
+.hP 4.
+The pixie parses its command line options.
+.hP 5.
+If a directory was specified with the
+.B \-d
+option, the pixie attempts to set it as the current directory.  If the
+attempt fails because the directory doesn't exist, it is created and the
+attempt to change directory is made again.  The directory's status is
+then examined to ensure that it conforms with the security requirements
+described above.
+.hP 6.
+A Unix domain socket is created, with the process's umask (see
+.BR umask (2))
+artifically set to 077.  If socket cannot be created, for whatever
+reason, the pixie reports an error and exits.
+.hP 7.
+If the verbosity level is sufficiently high, and a locked memory page
+could not be allocated, the pixie emits a warning.
+.SS "Runtime behaviour"
+After initialization, the pixie enters a loop, waiting for various
+things to happen.
+.PP
+If a client connects to the pixie's socket, the pixie will write its
+passphrase to the connected socket and then close it.  If the pixie
+currently has no passphrase, it asks for one and starts a timer (if one
+was requested by the user).
+.PP
+When the passphrase timer expires, all memory of the passphrase is
+expunged, and the timer is removed.  Any future connections requesting a
+passphrase will require the user to type one in again.
+.PP
+Some signals have a special meaning for the pixie:
+.TP
+.BR SIGINT " and " SIGTERM
+Cause an orderly shutdown of the pixie.  The Unix-domain socket is
+removed.
+.TP
+.BR SIGHUP " and " SIGQUIT
+Causes the passphrase to be forgotten immediately, as if timed out.
+This can be handy if you've typed the passphrase wrongly.
+.RB ( SIGQUIT
+was chosen because it can be easily typed at the terminal, usually using
+.BR C-\e .)
+.SH "IMPORTANT SECURITY NOTE"
+Don't use this software on a machine with a hostile admin.  You will
+lose.  Any machine with hostile administration must be automatically
+assumed hostile.  Never type a passphrase into a hostile machine.  Don't
+sent a passphrase over a hostile or potentially hostile network.  Don't
+do anything else stupid.
+.SH "OTHER CAVEATS"
+The
+.B \-d
+option doesn't do a thorough audit of a directory, in the way that, say
+.BR chkpath (1)
+does.  It's your responsibility to make sure that the full path is
+relatively safe.
+.PP
+The
+.BR xgetline (1)
+program is not as careful about locking memory as the pixie is.
+Hopefully the fact that it's a short-lived process means that this isn't
+a major issue; however, it remains possible that the passphrase typed
+into
+.B xgetline
+could be swapped to disk.
+.PP
+It's possible, though unlikely, that there's a security hole in the part
+of the
+.B pixie
+program which can run with setuid privileges.  In this case, remove
+setuid privileges immediately \- the program runs quite happily without,
+except that it might not be able to lock pages into memory.
+.SH "ACKNOWLEDGEMENTS"
+The original passphrase pixie was written by Ian Jackson as part of his
+.B auto-pgp
+package.  This pixie incorporates some improvements over the original
+which were noted in the
+.B auto-pgp
+documentation.
+.SH "AUTHOR"
+Mark Wooding, <mdw@nsict.org>
diff --git a/pixie.c b/pixie.c
new file mode 100644 (file)
index 0000000..97f5de2
--- /dev/null
+++ b/pixie.c
@@ -0,0 +1,991 @@
+/* -*-c-*-
+ *
+ * $Id: pixie.c,v 1.1 1999/10/23 10:58:49 mdw Exp $
+ *
+ * New, improved PGP pixie for auto-pgp
+ *
+ * (c) 1999 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * PGP pixie is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * PGP pixie 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with PGP pixie; 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.1  1999/10/23 10:58:49  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#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 <termios.h>
+
+#ifdef HAVE_MLOCK
+#  include <sys/mman.h>
+#endif
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "mdwopt.h"
+
+/*----- Magic constants ---------------------------------------------------*/
+
+#define PIXIE_BUFSZ 1024               /* Passphrase buffer size */
+#define PIXIE_TIMEOUT 300              /* Default timeout (in seconds) */
+
+#define PIXIE_SOCKET "pass-socket"
+
+/*----- Static variables --------------------------------------------------*/
+
+static char *pass;
+static size_t passlen = 0;
+static int sigfd_out;
+static unsigned flags;
+
+enum {
+  f_pass = 1u,
+  f_xgetline = 2u,
+  f_getpass = 4u,
+  f_goodbuf = 8u,
+  f_bogus = 128u
+};
+
+/*----- Library code ------------------------------------------------------*/
+
+const char *pn__name = "<UNNAMED>";    /* Program name */
+#define QUIS pn__name
+
+/* --- @quis@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Pointer to the program name.
+ *
+ * Use:                Returns the program name.
+ */
+
+const char *quis(void) { return (QUIS); }
+
+/* --- @ego@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to program name
+ *
+ * Returns:    ---
+ *
+ * Use:                Tells mLib what the program's name is.
+ */
+
+#ifndef PATHSEP
+#  if defined(__riscos)
+#    define PATHSEP '.'
+#  elif defined(__unix) || defined(unix)
+#    define PATHSEP '/'
+#  else
+#    define PATHSEP '\\'
+#  endif
+#endif
+
+void ego(const char *p)
+{
+  const char *q = p;
+  while (*q) {
+    if (*q++ == PATHSEP)
+      p = q;
+  }
+  if (*p == '-')
+    p++;
+  pn__name = p;
+}
+
+#undef PATHSEP
+
+/* --- @pquis@ --- *
+ *
+ * Arguments:  @FILE *fp@ = output stream to write on
+ *             @const char *p@ = pointer to string to write
+ *
+ * Returns:    Zero if everything worked, EOF if not.
+ *
+ * Use:                Writes the string @p@ to the output stream @fp@.  Occurrences
+ *             of the character `$' in @p@ are replaced by the program name
+ *             as reported by @quis@.  A `$$' is replaced by a single `$'
+ *             sign.
+ */
+
+int pquis(FILE *fp, const char *p)
+{
+  size_t sz;
+
+  while (*p) {
+    sz = strcspn(p, "$");
+    if (sz) {
+      if (fwrite(p, 1, sz, fp) < sz)
+       return (EOF);
+      p += sz;
+    }
+    if (*p == '$') {
+      p++;
+      if (*p == '$') {
+       if (fputc('$', fp) == EOF)
+         return (EOF);
+       p++;
+      } else {
+       if (fputs(pn__name, fp) == EOF)
+         return (EOF);
+      }
+    }
+  }
+  return (0);
+}
+
+/* --- @die@ --- *
+ *
+ * Arguments:  @int status@ = exit status to return
+ *             @const char *f@ = a @printf@-style format string
+ *             @...@ = other arguments
+ *
+ * Returns:    Never.
+ *
+ * Use:                Reports an error and exits.  Like @moan@ above, only more
+ *             permanent.
+ */
+
+void die(int status, const char *f, ...)
+{
+  va_list ap;
+  va_start(ap, f);
+  fprintf(stderr, "%s: ", QUIS);
+  vfprintf(stderr, f, ap);
+  va_end(ap);
+  putc('\n', stderr);
+  exit(status);
+}
+
+/* --- @fdflags@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to fiddle with
+ *             @unsigned fbic, fxor@ = file flags to set and clear
+ *             @unsigned fdbic, fdxor@ = descriptor flags to set and clear
+ *
+ * Returns:    Zero if successful, @-1@ if not.
+ *
+ * Use:                Sets file descriptor flags in what is, I hope, an obvious
+ *             way.
+ */
+
+int fdflags(int fd, unsigned fbic, unsigned fxor,
+           unsigned fdbic, unsigned fdxor)
+{
+  int f;
+
+  if ((f = fcntl(fd, F_GETFL)) == -1 ||
+      fcntl(fd, F_SETFL, (f & ~fbic) ^ fxor) == -1 ||
+      (f = fcntl(fd, F_GETFD)) == -1 ||
+      fcntl(fd, F_SETFD, (f & ~fdbic) ^ fdxor) == -1)
+    return (-1);
+  return (0);
+}
+
+/* --- Timeval manipulation macros --- */
+
+#define MILLION 1000000
+
+#define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec)
+
+#define TV_ADDL(dst, a, sec, usec) do {                                        \
+  (dst)->tv_sec = (a)->tv_sec + (sec);                                 \
+  (dst)->tv_usec = (a)->tv_usec + (usec);                              \
+  if ((dst)->tv_usec >= MILLION) {                                     \
+    (dst)->tv_usec -= MILLION;                                         \
+    (dst)->tv_sec++;                                                   \
+  }                                                                    \
+} while (0)
+
+#define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec)
+
+#define TV_SUBL(dst, a, sec, usec) do {                                        \
+  (dst)->tv_sec = (a)->tv_sec - (sec);                                 \
+  if ((a)->tv_usec >= (usec))                                          \
+    (dst)->tv_usec = (a)->tv_usec - (usec);                            \
+  else {                                                               \
+    (dst)->tv_usec = (a)->tv_usec + MILLION - (usec);                  \
+    (dst)->tv_sec--;                                                   \
+  }                                                                    \
+} while (0)
+
+#define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ?                 \
+                           (a)->tv_usec op (b)->tv_usec :              \
+                           (a)->tv_sec op (b)->tv_sec)
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @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, ...)
+{
+  char b[32];
+  va_list ap;
+  time_t t = time(0);
+  struct tm *tm = localtime(&t);
+
+  strftime(b, sizeof(b), "%Y-%m-%d %H:%M:%S", tm);
+  fprintf(stderr, "%s: %s ", QUIS, b);
+  va_start(ap, p);
+  vfprintf(stderr, p, ap);
+  va_end(ap);
+  fputc('\n', stderr);
+}
+
+/* --- @sigwrite@ --- *
+ *
+ * Arguments:  @int sig@ = signal number
+ *
+ * Returns:    ---
+ *
+ * Use:                Handles signals.  It writes the signal number to a pipe and
+ *             exits.  It's possible for signals to be lost if the pipe is
+ *             full.  This isn't likely enough to be worth caring about.
+ *             The implementation in mLib's `sig.c' does the job right but
+ *             it's rather more effort.
+ */
+
+static void sigwrite(int sig)
+{
+  int e = errno;
+  char c = sig;
+  write(sigfd_out, &c, 1);
+  errno = e;
+}
+
+/* --- @readpass@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to read from
+ *
+ * Returns:    0 if OK, -1 if not.
+ *
+ * Use:                Reads a line from a file descriptor.  It continues reading
+ *             buffers until it gets a newline character.  If the buffer
+ *             becomes full, a newline is inserted and no more data is
+ *             read.  This might cause confusion.
+ */
+
+static int readpass(int fd)
+{
+  int r;
+  char *p = pass;
+  char *q;
+  size_t sz = PIXIE_BUFSZ;
+
+  for (;;) {
+    r = read(fd, p, sz);
+    if (r < 0)
+      return (-1);
+    if (r == 0) {
+      q = p + r;
+      break;
+    }
+    if ((q = memchr(p, '\n', r)) != 0) {
+      q++;
+      break;
+    }
+    sz -= r;
+    p += r;
+    if (!sz) {
+      q = p;
+      p[-1] = '\n';
+      break;
+    }
+  }
+
+  passlen = q - pass;
+  return (0);
+}
+
+/* --- @get_pass@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    0 if OK, -1 if it failed.
+ *
+ * Use:                Reads a passphrase from somewhere.  The data from the
+ *             passphrase goes straight into the @pass@ buffer without
+ *             touching any other memory.  Of course, if @xgetline@ is used,
+ *             it might end up in unprotected memory there.  That's a shame,
+ *             but @xgetline@ is a much shorter-lived process than this one
+ *             so it shouldn't matter as much.
+ */
+
+static int get_pass(void)
+{
+#ifdef PATH_XGETLINE
+  if (flags & f_xgetline) {
+    int fd[2];
+    pid_t kid;
+    int r;
+
+    /* --- Do everything by hand --- *
+     *
+     * I could, I suppose, use @popen@.  However, (a) that involves a shell
+     * which is extra overhead and makes passing arguments with spaces a
+     * little trickier; and (b) it uses @stdio@ buffers, which might get
+     * swapped to disk.
+     */
+
+    if (pipe(fd))
+      return (-1);
+    kid = fork();
+    if (kid < 0)
+      return (-1);
+    if (kid == 0) {
+      dup2(fd[1], STDOUT_FILENO);
+      close(fd[0]);
+      close(fd[1]);
+      execlp(PATH_XGETLINE, "xgetline",
+            "-i", "-tPGP pixie", "-pPGP passphrase:", (char *)0);
+      _exit(127);
+    }
+    close(fd[1]);
+    r = readpass(fd[0]);
+    close(fd[0]);
+    waitpid(kid, 0, 0);
+    return (r);
+  } else
+#endif
+  {
+    struct termios o, n;
+    int fd;
+    int r;
+    char prompt[] = "PGP passphrase: ";
+    char nl = '\n';
+
+    /* --- Do this by hand --- *
+     *
+     * I could use @getpass@, but that puts the passphrase in its own memory
+     * rather than mine, so I'd have to scrub it out manually.  This is
+     * probably just as good if you don't mind fiddling with @termios@.
+     * Also, the GNU version uses @stdio@ streams to read from the terminal,
+     * which might be considered a Bad Thing.
+     */
+
+    if ((fd = open("/dev/tty", O_RDWR)) < 0)
+      return (-1);
+    if (tcgetattr(fd, &o))
+      return (-1);
+    n = o;
+    n.c_lflag &= ~(ECHO | ISIG);
+    if (tcsetattr(fd, TCSAFLUSH, &n))
+      return (-1);
+    write(fd, prompt, sizeof(prompt) - 1);
+    r = readpass(fd);
+    tcsetattr(fd, TCSAFLUSH, &o);
+    write(fd, &nl, 1);
+    close(fd);
+    return (r);
+  }
+}
+
+/* --- @help@, @version@ @usage@ --- *
+ *
+ * Arguments:  @FILE *fp@ = stream to write on
+ *
+ * Returns:    ---
+ *
+ * Use:                Emit helpful messages.
+ */
+
+static void usage(FILE *fp)
+{
+  pquis(fp, "Usage: $ [-xqv] [-t timeout] [-d dir] [socket]\n");
+}
+
+static void version(FILE *fp)
+{
+  pquis(fp, "$ version " VERSION "\n");
+}
+
+static void help(FILE *fp)
+{
+  version(fp);
+  fputc('\n', fp);
+  usage(fp);
+  pquis(fp, "\n\
+The passphrase pixie remembers a PGP passphrase and passes it on to\n\
+clients which connect to a Unix-domain socket.\n\
+\n\
+The pixie will forget a passphrase after a certain amount of time.  The\n\
+duration of the pixie's memory is configurable using the `-t' option, and\n\
+the default is 5 minutes.  By giving a timeout of zero, the pixie can be\n\
+endowed with a perfect memory.\n\
+\n\
+The pixie attempts to lock its passphrase buffer into physical memory.  If\n\
+this doesn't work (e.g., your operating system doesn't support this\n\
+feature, or you have insufficient privilege) a warning is emitted.\n\
+\n\
+Options available are:\n\
+\n\
+-h, --help             Show this help text.\n\
+-V, --version          Show the pixie's version number.\n\
+-u, --usage            Show a uselessly terse usage message.\n\
+\n\
+"
+#ifdef PATH_XGETLINE
+"\
+-x, --x11              Run `xgetline' to read a passphrase.\n\
++x, --no-x11           Don't run `xgetline' to read a passphrase.\n\
+"
+#endif
+"\
+-d, --directory=DIR    Make secure directory DIR and change to it.\n\
+-q, --quiet            Don't emit so many messages.\n\
+-v, --verbose          Emit more messages.\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 *dir = 0;
+  int fd;
+  int sigfd_in;
+  unsigned verbose = 1;
+  char *sock = 0;
+  unsigned long timeout = PIXIE_TIMEOUT;
+  char *emsg = 0;
+  int elock = 0;
+
+  ego(argv[0]);
+
+  /* --- Try making a secure locked passphrase buffer --- *
+   *
+   * Drop privileges before emitting diagnostic messages.
+   */
+
+#ifdef HAVE_MLOCK
+
+  /* --- Memory-map a page from somewhere --- */
+
+#  ifdef MAP_ANON
+
+  pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE,
+             MAP_PRIVATE | MAP_ANON, -1, 0);
+
+#  else
+
+  if ((fd = open("/dev/zero", O_RDWR)) < 0) {
+    emsg = "couldn't open `/dev/zero': %s";
+    elock = errno;
+  } else {
+    pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE,
+               MAP_PRIVATE, fd, 0);
+    close(fd);
+  }
+
+#  endif
+
+  /* --- Lock the page in memory --- *
+   *
+   * Why does @mmap@ return such a stupid result if it fails?
+   */
+
+  if (pass == 0 || pass == MAP_FAILED) {
+    emsg = "couldn't map a passphrase buffer: %s";
+    elock = errno;
+    pass = 0;
+  } else if (mlock(pass, PIXIE_BUFSZ)) {
+    emsg = "couldn't lock passphrase buffer: %s";
+    elock = errno;
+    munmap(pass, PIXIE_BUFSZ);
+    pass = 0;
+  } else
+    flags |= f_goodbuf;
+
+#endif
+
+  /* --- Make a standard passphrase buffer --- */
+
+  setuid(getuid());
+
+#ifdef HAVE_MLOCK
+  if (!pass)
+#endif
+  {
+    if ((pass = malloc(PIXIE_BUFSZ)) == 0)
+      die(1, "not enough memory for passphrase buffer");
+  }
+
+  /* --- Parse options --- */
+
+  for (;;) {
+    static struct option opts[] = {
+
+      /* --- GNUey help options --- */
+
+      { "help",                0,              0,      'h' },
+      { "usage",       0,              0,      'u' },
+      { "version",     0,              0,      'V' },
+
+      /* --- Other options --- */
+
+      { "timeout",     OPTF_ARGREQ,    0,      't' },
+#ifdef PATH_XGETLINE
+      { "xgetline",    OPTF_NEGATE,    0,      'x' },
+      { "x11",         OPTF_NEGATE,    0,      'x' },
+#endif
+      { "directory",   OPTF_ARGREQ,    0,      'd' },
+      { "quiet",       0,              0,      'q' },
+      { "verbose",     0,              0,      'v' },
+
+      /* --- Magic end marker --- */
+
+      { 0,             0,              0,      0 }
+    };
+
+#ifdef PATH_XGETLINE
+#  define XOPTS "x+"
+#else
+#  define XOPTS
+#endif
+
+    int i = mdwopt(argc, argv, "huV" XOPTS "t:d:qv",
+                  opts, 0, 0, OPTF_NEGATION);
+
+#undef XOPTS
+
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help(stdout);
+       exit(0);
+      case 'V':
+       version(stdout);
+       exit(0);
+      case 'u':
+       usage(stdout);
+       exit(0);
+      case 't': {
+       char *p;
+       timeout = strtoul(optarg, &p, 0);
+       switch (*p) {
+         case 'd': timeout *= 24;
+         case 'h': timeout *= 60;
+         case 'm': timeout *= 60;
+         case 's': case 0: break;
+         default:
+           die(1, "unrecognized suffix character `%c'", *p);
+           break;
+       }
+      } break;
+#ifdef PATH_XGETLINE
+      case 'x':
+       flags |= f_xgetline;
+       flags &= ~f_getpass;
+       break;
+      case 'x' | OPTF_NEGATED:
+       flags |= f_getpass;
+       flags &= ~f_xgetline;
+       break;
+#endif
+      case 'd':
+       dir = optarg;
+       break;
+      case 'q':
+       if (verbose > 0)
+         verbose--;
+       break;
+      case 'v':
+       verbose++;
+       break;
+      default:
+       flags |= f_bogus;
+       break;
+    }
+  }
+
+  if (optind < argc)
+    sock = argv[optind++];
+
+  if (optind < argc)
+    flags |= f_bogus;
+
+  if (flags & f_bogus) {
+    usage(stderr);
+    exit(1);
+  }
+
+  /* --- Sort out how to request the passphrase --- */
+
+#ifdef PATH_XGETLINE
+  if ((flags & (f_xgetline | f_getpass)) == 0) {
+    if (isatty(STDIN_FILENO))
+      flags |= f_getpass;
+    else
+      flags |= f_xgetline;
+  }
+#endif
+
+  /* --- Make the socket directory --- *
+   *
+   * Be very paranoid about the directory.  Very paranoid indeed.
+   */
+
+  if (dir) {
+    struct stat st;
+
+    if (chdir(dir)) {
+      if (errno != ENOENT) {
+       die(1, "couldn't change directory to `%s': %s",
+           dir, strerror(errno));
+      }
+      if (mkdir(dir, 0700))
+       die(1, "couldn't create directory `%s': %s", dir, strerror(errno));
+      if (chdir(dir)) {
+       die(1, "couldn't change directory to `%s': %s",
+           dir, strerror(errno));
+      }
+      if (verbose > 1)
+       log("created directory `%s'", dir);
+    }
+
+    if (stat(".", &st))
+      die(1, "couldn't stat directory `%s': %s", dir, strerror(errno));
+    if ((st.st_mode & 07777) != 0700) {
+      die(1, "directory `%s' has mode %04o; should be 0700",
+         dir, st.st_mode & 07777);
+    }
+    if (st.st_uid != getuid()) {
+      struct passwd *pw = getpwuid(st.st_uid);
+      char b[16];
+      char *p;
+
+      if (pw)
+       p = pw->pw_name;
+      else {
+       sprintf(b, "uid `%i'", st.st_uid);
+       p = b;
+      }
+      die(1, "directory `%s' owned by %s; should be you", dir, p);
+    }
+
+    if (verbose > 2)
+      log("directory `%s' checked out OK", dir);
+  }
+
+  /* --- A little argument checking --- */
+
+  if (!sock) {
+    if (dir)
+      sock = PIXIE_SOCKET;
+    else
+      die(1, "no socket filename given");
+  }
+
+  /* --- Create and bind the socket --- */
+
+  {
+    size_t len = strlen(sock) + 1;
+    size_t sz = offsetof(struct sockaddr_un, sun_path) + len;
+    struct sockaddr_un *sun = malloc(sz);
+    unsigned u = umask(077);
+
+    /* --- Create the file descriptor --- */
+
+    if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+      die(1, "couldn't create socket: %s", strerror(errno));
+    if (fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
+      die(1, "couldn't configure socket: %s", strerror(errno));
+
+    /* --- Set up the address --- */
+
+    memset(sun, 0, sz);
+    sun->sun_family = AF_UNIX;
+    strcpy(sun->sun_path, sock);
+
+    /* --- Bind to the address --- */
+
+    if (bind(fd, (struct sockaddr *)sun, sz))
+      die(1, "couldn't bind to socket `%s': %s", sock, strerror(errno));
+    free(sun);
+    if (listen(fd, 5))
+      die(1, "couldn't listen on socket: %s", strerror(errno));
+    umask(u);
+  }
+
+  /* --- Set signals up --- *
+   *
+   * I'm using Dan Bernstein's self-pipe trick to catch signals in the main
+   * code.  See http://pobox.com/~djb/docs/selfpipe.html
+   */
+
+  {
+    static int sig[] = { SIGINT, SIGTERM, SIGHUP, SIGQUIT, 0 };
+    struct sigaction sa;
+    int i;
+    int pfd[2];
+
+    /* --- Create the signal pipe --- */
+
+    if (pipe(pfd))
+      die(1, "couldn't create pipe: %s", strerror(errno));
+    
+    if (fdflags(pfd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) ||
+       fdflags(pfd[1], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
+      die(1, "couldn't configure pipe attributes: %s", strerror(errno));
+
+    sigfd_in = pfd[0];
+    sigfd_out = pfd[1];
+
+    /* --- Set up the signal handlers --- */
+
+    sa.sa_handler = sigwrite;
+    sa.sa_flags = 0;
+#ifdef SA_RESTART
+    sa.sa_flags |= SA_RESTART;
+#endif
+    sigemptyset(&sa.sa_mask);
+
+    for (i = 0; sig[i]; i++) {
+      struct sigaction osa;
+      if (sigaction(sig[i], 0, &osa) == 0 &&
+         osa.sa_handler != SIG_IGN)
+       sigaction(sig[i], &sa, 0);
+    }
+  }
+
+  /* --- Now listen, and wait --- */
+
+  {
+    int maxfd;
+    fd_set fds;
+    struct timeval tv, now, when, *tvp;
+
+    if (fd > sigfd_in)
+      maxfd = fd + 1;
+    else
+      maxfd = sigfd_in + 1;
+
+    if (flags & f_goodbuf) {
+      if (verbose > 1)
+       log("passphrase buffer created and locked OK");
+    } else {
+      if (emsg && verbose > 1)
+       log(emsg, strerror(elock));
+      else if (verbose)
+       log("couldn't create locked passphrase buffer");
+    }
+
+    if (verbose > 1)
+      log("passphrase pixie initialized OK");
+
+    for (;;) {
+
+      /* --- Set up the file descriptors --- */
+
+      FD_ZERO(&fds);
+      FD_SET(fd, &fds);
+      FD_SET(sigfd_in, &fds);
+
+      /* --- Set up the timeout --- */
+
+      if (!timeout || !(flags & f_pass))
+        tvp = 0;
+      else {
+       gettimeofday(&now, 0);
+       TV_SUB(&tv, &when, &now);
+       tvp = &tv;
+      }
+
+      /* --- Wait for something interesting to happen --- */
+
+      if (select(maxfd, &fds, 0, 0, tvp) < 0) {
+       if (errno == EINTR)
+         continue;
+       die(1, "error from select: %s", strerror(errno));
+      }
+
+      /* --- Act on a signal --- */
+
+      if (FD_ISSET(sigfd_in, &fds)) {
+       char buf[256];
+       int r;
+       sigset_t ss;
+
+       /* --- Go through each signal in turn --- *
+        *
+        * Don't try to respond to duplicates.
+        */
+
+       sigemptyset(&ss);
+       while ((r = read(sigfd_in, buf, sizeof(buf))) > 0) {
+         char *p = buf;
+
+         /* --- A buffer of signals has arrived; grind through it --- */
+
+         for (p = buf; r; r--, p++) {
+
+           /* --- If this signal has been seen, skip on to the next --- */
+
+           if (sigismember(&ss, *p))
+             continue;
+           sigaddset(&ss, *p);
+
+           switch (*p) {
+             const char *s;
+
+             /* --- Various interesting signals --- */
+
+             case SIGINT:
+               s = "SIGINT";
+               goto closedown;
+             case SIGTERM:
+               s = "SIGTERM";
+               goto closedown;
+             case SIGHUP:
+               s = "SIGHUP";
+               goto clear;
+             case SIGQUIT:
+               s = "SIGQUIT";
+               goto clear;
+               
+             /* --- Shut down the program if requested --- */
+
+             closedown:
+               if (verbose > 1)
+                 log("closing down on %s", s);
+               goto done;
+
+             /* --- Clear the passphrase if requested --- */
+
+             clear:
+               if (flags & f_pass) {
+                 memset(pass, 0, PIXIE_BUFSZ);
+                 passlen = 0;
+                 flags &= ~f_pass;
+                 if (verbose)
+                   log("caught %s: passphrase cleared", s);
+               } else if (verbose > 1)
+                 log("caught %s: passphrase not set", s);
+               break;
+
+             /* --- Other signals which aren't so interesting --- */
+
+             default:
+               if (verbose > 2)
+                 log("caught unexpected signal %i: ignoring it", *p);
+               break;
+           }
+         }
+       }
+      }
+
+      /* --- Act on a passphrase timeout --- */
+
+      if (timeout && (flags & f_pass)) {
+       gettimeofday(&now, 0);
+       if (TV_CMP(&now, >, &when)) {
+         memset(pass, 0, PIXIE_BUFSZ);
+         passlen = 0;
+         flags &= ~f_pass;
+         if (verbose > 1)
+           log("passphrase timed out");
+       }
+      }
+
+      /* --- Act on a new connection --- */
+
+      if (FD_ISSET(fd, &fds)) {
+       int nfd;
+
+       {
+         struct sockaddr_un sun;
+         int sunsz = sizeof(sun);
+
+         if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) {
+           if (verbose > 1)
+             log("accept failed: %s", strerror(errno));
+           goto fail_0;
+         }
+       }
+
+       if (!(flags & f_pass)) {
+         if (get_pass()) {
+           if (verbose)
+             log("couldn't get passphrase: %s", strerror(errno));
+           goto fail_1;
+         }
+         flags |= f_pass;
+         if (timeout) {
+           gettimeofday(&when, 0);
+           when.tv_sec += timeout;
+         }
+       }
+       write(nfd, pass, passlen);
+       if (verbose)
+         log("responded to passphrase request");
+      fail_1:
+       close(nfd);
+      fail_0:
+       ;
+      }
+    }
+  }
+
+done:
+  memset(pass, 0, PIXIE_BUFSZ);
+  passlen = 0;
+  unlink(sock);
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/setup b/setup
new file mode 100755 (executable)
index 0000000..27dde72
--- /dev/null
+++ b/setup
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+set -e
+mklinks
+mkaclocal
+autoconf
+automake
+mkdir build