From 9d2e2c65c582dd01d2fc7d0024487f60231b2d7a Mon Sep 17 00:00:00 2001 From: mdw Date: Sat, 23 Oct 1999 10:58:48 +0000 Subject: [PATCH] Initial revision --- .cvsignore | 3 + .links | 6 + .skelrc | 7 + Makefile.am | 49 +++ configure.in | 49 +++ pgp-pixie.in | 4 + pixie.1 | 206 +++++++++++++ pixie.c | 991 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup | 8 + 9 files changed, 1323 insertions(+) create mode 100644 .cvsignore create mode 100644 .links create mode 100644 .skelrc create mode 100644 Makefile.am create mode 100644 configure.in create mode 100644 pgp-pixie.in create mode 100644 pixie.1 create mode 100644 pixie.c create mode 100755 setup diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..9073e24 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,3 @@ +Makefile.in +configure +build diff --git a/.links b/.links new file mode 100644 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 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 index 0000000..7e0273c --- /dev/null +++ b/Makefile.am @@ -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 index 0000000..70b4abe --- /dev/null +++ b/configure.in @@ -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 index 0000000..4d59742 --- /dev/null +++ b/pgp-pixie.in @@ -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 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, diff --git a/pixie.c b/pixie.c new file mode 100644 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_MLOCK +# include +#endif + +#include +#include + +#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 = ""; /* 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 index 0000000..27dde72 --- /dev/null +++ b/setup @@ -0,0 +1,8 @@ +#! /bin/sh + +set -e +mklinks +mkaclocal +autoconf +automake +mkdir build -- 2.11.0