--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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 -------------------------------------------------*/