chiark / gitweb /
Initial revision
[pixie] / pixie.c
1 /* -*-c-*-
2  *
3  * $Id: pixie.c,v 1.1 1999/10/23 10:58:49 mdw Exp $
4  *
5  * New, improved PGP pixie for auto-pgp
6  *
7  * (c) 1999 Mark Wooding
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * PGP pixie is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  * 
17  * PGP pixie is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  * 
22  * You should have received a copy of the GNU General Public License
23  * along with PGP pixie; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 /*----- Revision history --------------------------------------------------* 
28  *
29  * $Log: pixie.c,v $
30  * Revision 1.1  1999/10/23 10:58:49  mdw
31  * Initial revision
32  *
33  */
34
35 /*----- Header files ------------------------------------------------------*/
36
37 #include <errno.h>
38 #include <signal.h>
39 #include <stdarg.h>
40 #include <stddef.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <time.h>
45
46 #include <sys/types.h>
47 #include <sys/time.h>
48 #include <unistd.h>
49 #include <sys/stat.h>
50 #include <sys/wait.h>
51 #include <pwd.h>
52 #include <fcntl.h>
53 #include <termios.h>
54
55 #ifdef HAVE_MLOCK
56 #  include <sys/mman.h>
57 #endif
58
59 #include <sys/socket.h>
60 #include <sys/un.h>
61
62 #include "mdwopt.h"
63
64 /*----- Magic constants ---------------------------------------------------*/
65
66 #define PIXIE_BUFSZ 1024                /* Passphrase buffer size */
67 #define PIXIE_TIMEOUT 300               /* Default timeout (in seconds) */
68
69 #define PIXIE_SOCKET "pass-socket"
70
71 /*----- Static variables --------------------------------------------------*/
72
73 static char *pass;
74 static size_t passlen = 0;
75 static int sigfd_out;
76 static unsigned flags;
77
78 enum {
79   f_pass = 1u,
80   f_xgetline = 2u,
81   f_getpass = 4u,
82   f_goodbuf = 8u,
83   f_bogus = 128u
84 };
85
86 /*----- Library code ------------------------------------------------------*/
87
88 const char *pn__name = "<UNNAMED>";     /* Program name */
89 #define QUIS pn__name
90
91 /* --- @quis@ --- *
92  *
93  * Arguments:   ---
94  *
95  * Returns:     Pointer to the program name.
96  *
97  * Use:         Returns the program name.
98  */
99
100 const char *quis(void) { return (QUIS); }
101
102 /* --- @ego@ --- *
103  *
104  * Arguments:   @const char *p@ = pointer to program name
105  *
106  * Returns:     ---
107  *
108  * Use:         Tells mLib what the program's name is.
109  */
110
111 #ifndef PATHSEP
112 #  if defined(__riscos)
113 #    define PATHSEP '.'
114 #  elif defined(__unix) || defined(unix)
115 #    define PATHSEP '/'
116 #  else
117 #    define PATHSEP '\\'
118 #  endif
119 #endif
120
121 void ego(const char *p)
122 {
123   const char *q = p;
124   while (*q) {
125     if (*q++ == PATHSEP)
126       p = q;
127   }
128   if (*p == '-')
129     p++;
130   pn__name = p;
131 }
132
133 #undef PATHSEP
134
135 /* --- @pquis@ --- *
136  *
137  * Arguments:   @FILE *fp@ = output stream to write on
138  *              @const char *p@ = pointer to string to write
139  *
140  * Returns:     Zero if everything worked, EOF if not.
141  *
142  * Use:         Writes the string @p@ to the output stream @fp@.  Occurrences
143  *              of the character `$' in @p@ are replaced by the program name
144  *              as reported by @quis@.  A `$$' is replaced by a single `$'
145  *              sign.
146  */
147
148 int pquis(FILE *fp, const char *p)
149 {
150   size_t sz;
151
152   while (*p) {
153     sz = strcspn(p, "$");
154     if (sz) {
155       if (fwrite(p, 1, sz, fp) < sz)
156         return (EOF);
157       p += sz;
158     }
159     if (*p == '$') {
160       p++;
161       if (*p == '$') {
162         if (fputc('$', fp) == EOF)
163           return (EOF);
164         p++;
165       } else {
166         if (fputs(pn__name, fp) == EOF)
167           return (EOF);
168       }
169     }
170   }
171   return (0);
172 }
173
174 /* --- @die@ --- *
175  *
176  * Arguments:   @int status@ = exit status to return
177  *              @const char *f@ = a @printf@-style format string
178  *              @...@ = other arguments
179  *
180  * Returns:     Never.
181  *
182  * Use:         Reports an error and exits.  Like @moan@ above, only more
183  *              permanent.
184  */
185
186 void die(int status, const char *f, ...)
187 {
188   va_list ap;
189   va_start(ap, f);
190   fprintf(stderr, "%s: ", QUIS);
191   vfprintf(stderr, f, ap);
192   va_end(ap);
193   putc('\n', stderr);
194   exit(status);
195 }
196
197 /* --- @fdflags@ --- *
198  *
199  * Arguments:   @int fd@ = file descriptor to fiddle with
200  *              @unsigned fbic, fxor@ = file flags to set and clear
201  *              @unsigned fdbic, fdxor@ = descriptor flags to set and clear
202  *
203  * Returns:     Zero if successful, @-1@ if not.
204  *
205  * Use:         Sets file descriptor flags in what is, I hope, an obvious
206  *              way.
207  */
208
209 int fdflags(int fd, unsigned fbic, unsigned fxor,
210             unsigned fdbic, unsigned fdxor)
211 {
212   int f;
213
214   if ((f = fcntl(fd, F_GETFL)) == -1 ||
215       fcntl(fd, F_SETFL, (f & ~fbic) ^ fxor) == -1 ||
216       (f = fcntl(fd, F_GETFD)) == -1 ||
217       fcntl(fd, F_SETFD, (f & ~fdbic) ^ fdxor) == -1)
218     return (-1);
219   return (0);
220 }
221
222 /* --- Timeval manipulation macros --- */
223
224 #define MILLION 1000000
225
226 #define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec)
227
228 #define TV_ADDL(dst, a, sec, usec) do {                                 \
229   (dst)->tv_sec = (a)->tv_sec + (sec);                                  \
230   (dst)->tv_usec = (a)->tv_usec + (usec);                               \
231   if ((dst)->tv_usec >= MILLION) {                                      \
232     (dst)->tv_usec -= MILLION;                                          \
233     (dst)->tv_sec++;                                                    \
234   }                                                                     \
235 } while (0)
236
237 #define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec)
238
239 #define TV_SUBL(dst, a, sec, usec) do {                                 \
240   (dst)->tv_sec = (a)->tv_sec - (sec);                                  \
241   if ((a)->tv_usec >= (usec))                                           \
242     (dst)->tv_usec = (a)->tv_usec - (usec);                             \
243   else {                                                                \
244     (dst)->tv_usec = (a)->tv_usec + MILLION - (usec);                   \
245     (dst)->tv_sec--;                                                    \
246   }                                                                     \
247 } while (0)
248
249 #define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ?                  \
250                             (a)->tv_usec op (b)->tv_usec :              \
251                             (a)->tv_sec op (b)->tv_sec)
252
253 /*----- Main code ---------------------------------------------------------*/
254
255 /* --- @log@ --- *
256  *
257  * Arguments:   @const char *p@ = @printf@-style format string
258  *              @...@ = extra arguments to fill in
259  *
260  * Returns:     ---
261  *
262  * Use:         Writes out a timestamped log message.
263  */
264
265 static void log(const char *p, ...)
266 {
267   char b[32];
268   va_list ap;
269   time_t t = time(0);
270   struct tm *tm = localtime(&t);
271
272   strftime(b, sizeof(b), "%Y-%m-%d %H:%M:%S", tm);
273   fprintf(stderr, "%s: %s ", QUIS, b);
274   va_start(ap, p);
275   vfprintf(stderr, p, ap);
276   va_end(ap);
277   fputc('\n', stderr);
278 }
279
280 /* --- @sigwrite@ --- *
281  *
282  * Arguments:   @int sig@ = signal number
283  *
284  * Returns:     ---
285  *
286  * Use:         Handles signals.  It writes the signal number to a pipe and
287  *              exits.  It's possible for signals to be lost if the pipe is
288  *              full.  This isn't likely enough to be worth caring about.
289  *              The implementation in mLib's `sig.c' does the job right but
290  *              it's rather more effort.
291  */
292
293 static void sigwrite(int sig)
294 {
295   int e = errno;
296   char c = sig;
297   write(sigfd_out, &c, 1);
298   errno = e;
299 }
300
301 /* --- @readpass@ --- *
302  *
303  * Arguments:   @int fd@ = file descriptor to read from
304  *
305  * Returns:     0 if OK, -1 if not.
306  *
307  * Use:         Reads a line from a file descriptor.  It continues reading
308  *              buffers until it gets a newline character.  If the buffer
309  *              becomes full, a newline is inserted and no more data is
310  *              read.  This might cause confusion.
311  */
312
313 static int readpass(int fd)
314 {
315   int r;
316   char *p = pass;
317   char *q;
318   size_t sz = PIXIE_BUFSZ;
319
320   for (;;) {
321     r = read(fd, p, sz);
322     if (r < 0)
323       return (-1);
324     if (r == 0) {
325       q = p + r;
326       break;
327     }
328     if ((q = memchr(p, '\n', r)) != 0) {
329       q++;
330       break;
331     }
332     sz -= r;
333     p += r;
334     if (!sz) {
335       q = p;
336       p[-1] = '\n';
337       break;
338     }
339   }
340
341   passlen = q - pass;
342   return (0);
343 }
344
345 /* --- @get_pass@ --- *
346  *
347  * Arguments:   ---
348  *
349  * Returns:     0 if OK, -1 if it failed.
350  *
351  * Use:         Reads a passphrase from somewhere.  The data from the
352  *              passphrase goes straight into the @pass@ buffer without
353  *              touching any other memory.  Of course, if @xgetline@ is used,
354  *              it might end up in unprotected memory there.  That's a shame,
355  *              but @xgetline@ is a much shorter-lived process than this one
356  *              so it shouldn't matter as much.
357  */
358
359 static int get_pass(void)
360 {
361 #ifdef PATH_XGETLINE
362   if (flags & f_xgetline) {
363     int fd[2];
364     pid_t kid;
365     int r;
366
367     /* --- Do everything by hand --- *
368      *
369      * I could, I suppose, use @popen@.  However, (a) that involves a shell
370      * which is extra overhead and makes passing arguments with spaces a
371      * little trickier; and (b) it uses @stdio@ buffers, which might get
372      * swapped to disk.
373      */
374
375     if (pipe(fd))
376       return (-1);
377     kid = fork();
378     if (kid < 0)
379       return (-1);
380     if (kid == 0) {
381       dup2(fd[1], STDOUT_FILENO);
382       close(fd[0]);
383       close(fd[1]);
384       execlp(PATH_XGETLINE, "xgetline",
385              "-i", "-tPGP pixie", "-pPGP passphrase:", (char *)0);
386       _exit(127);
387     }
388     close(fd[1]);
389     r = readpass(fd[0]);
390     close(fd[0]);
391     waitpid(kid, 0, 0);
392     return (r);
393   } else
394 #endif
395   {
396     struct termios o, n;
397     int fd;
398     int r;
399     char prompt[] = "PGP passphrase: ";
400     char nl = '\n';
401
402     /* --- Do this by hand --- *
403      *
404      * I could use @getpass@, but that puts the passphrase in its own memory
405      * rather than mine, so I'd have to scrub it out manually.  This is
406      * probably just as good if you don't mind fiddling with @termios@.
407      * Also, the GNU version uses @stdio@ streams to read from the terminal,
408      * which might be considered a Bad Thing.
409      */
410
411     if ((fd = open("/dev/tty", O_RDWR)) < 0)
412       return (-1);
413     if (tcgetattr(fd, &o))
414       return (-1);
415     n = o;
416     n.c_lflag &= ~(ECHO | ISIG);
417     if (tcsetattr(fd, TCSAFLUSH, &n))
418       return (-1);
419     write(fd, prompt, sizeof(prompt) - 1);
420     r = readpass(fd);
421     tcsetattr(fd, TCSAFLUSH, &o);
422     write(fd, &nl, 1);
423     close(fd);
424     return (r);
425   }
426 }
427
428 /* --- @help@, @version@ @usage@ --- *
429  *
430  * Arguments:   @FILE *fp@ = stream to write on
431  *
432  * Returns:     ---
433  *
434  * Use:         Emit helpful messages.
435  */
436
437 static void usage(FILE *fp)
438 {
439   pquis(fp, "Usage: $ [-xqv] [-t timeout] [-d dir] [socket]\n");
440 }
441
442 static void version(FILE *fp)
443 {
444   pquis(fp, "$ version " VERSION "\n");
445 }
446
447 static void help(FILE *fp)
448 {
449   version(fp);
450   fputc('\n', fp);
451   usage(fp);
452   pquis(fp, "\n\
453 The passphrase pixie remembers a PGP passphrase and passes it on to\n\
454 clients which connect to a Unix-domain socket.\n\
455 \n\
456 The pixie will forget a passphrase after a certain amount of time.  The\n\
457 duration of the pixie's memory is configurable using the `-t' option, and\n\
458 the default is 5 minutes.  By giving a timeout of zero, the pixie can be\n\
459 endowed with a perfect memory.\n\
460 \n\
461 The pixie attempts to lock its passphrase buffer into physical memory.  If\n\
462 this doesn't work (e.g., your operating system doesn't support this\n\
463 feature, or you have insufficient privilege) a warning is emitted.\n\
464 \n\
465 Options available are:\n\
466 \n\
467 -h, --help              Show this help text.\n\
468 -V, --version           Show the pixie's version number.\n\
469 -u, --usage             Show a uselessly terse usage message.\n\
470 \n\
471 "
472 #ifdef PATH_XGETLINE
473 "\
474 -x, --x11               Run `xgetline' to read a passphrase.\n\
475 +x, --no-x11            Don't run `xgetline' to read a passphrase.\n\
476 "
477 #endif
478 "\
479 -d, --directory=DIR     Make secure directory DIR and change to it.\n\
480 -q, --quiet             Don't emit so many messages.\n\
481 -v, --verbose           Emit more messages.\n\
482 ");
483 }
484
485 /* --- @main@ --- *
486  *
487  * Arguments:   @int argc@ = number of arguments
488  *              @char *argv[]@ = vector of argument values
489  *
490  * Returns:     Zero if OK.
491  *
492  * Use:         Main program.  Listens on a socket and responds with a PGP
493  *              passphrase when asked.
494  */
495
496 int main(int argc, char *argv[])
497 {
498   char *dir = 0;
499   int fd;
500   int sigfd_in;
501   unsigned verbose = 1;
502   char *sock = 0;
503   unsigned long timeout = PIXIE_TIMEOUT;
504   char *emsg = 0;
505   int elock = 0;
506
507   ego(argv[0]);
508
509   /* --- Try making a secure locked passphrase buffer --- *
510    *
511    * Drop privileges before emitting diagnostic messages.
512    */
513
514 #ifdef HAVE_MLOCK
515
516   /* --- Memory-map a page from somewhere --- */
517
518 #  ifdef MAP_ANON
519
520   pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE,
521               MAP_PRIVATE | MAP_ANON, -1, 0);
522
523 #  else
524
525   if ((fd = open("/dev/zero", O_RDWR)) < 0) {
526     emsg = "couldn't open `/dev/zero': %s";
527     elock = errno;
528   } else {
529     pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE,
530                 MAP_PRIVATE, fd, 0);
531     close(fd);
532   }
533
534 #  endif
535
536   /* --- Lock the page in memory --- *
537    *
538    * Why does @mmap@ return such a stupid result if it fails?
539    */
540
541   if (pass == 0 || pass == MAP_FAILED) {
542     emsg = "couldn't map a passphrase buffer: %s";
543     elock = errno;
544     pass = 0;
545   } else if (mlock(pass, PIXIE_BUFSZ)) {
546     emsg = "couldn't lock passphrase buffer: %s";
547     elock = errno;
548     munmap(pass, PIXIE_BUFSZ);
549     pass = 0;
550   } else
551     flags |= f_goodbuf;
552
553 #endif
554
555   /* --- Make a standard passphrase buffer --- */
556
557   setuid(getuid());
558
559 #ifdef HAVE_MLOCK
560   if (!pass)
561 #endif
562   {
563     if ((pass = malloc(PIXIE_BUFSZ)) == 0)
564       die(1, "not enough memory for passphrase buffer");
565   }
566
567   /* --- Parse options --- */
568
569   for (;;) {
570     static struct option opts[] = {
571
572       /* --- GNUey help options --- */
573
574       { "help",         0,              0,      'h' },
575       { "usage",        0,              0,      'u' },
576       { "version",      0,              0,      'V' },
577
578       /* --- Other options --- */
579
580       { "timeout",      OPTF_ARGREQ,    0,      't' },
581 #ifdef PATH_XGETLINE
582       { "xgetline",     OPTF_NEGATE,    0,      'x' },
583       { "x11",          OPTF_NEGATE,    0,      'x' },
584 #endif
585       { "directory",    OPTF_ARGREQ,    0,      'd' },
586       { "quiet",        0,              0,      'q' },
587       { "verbose",      0,              0,      'v' },
588
589       /* --- Magic end marker --- */
590
591       { 0,              0,              0,      0 }
592     };
593
594 #ifdef PATH_XGETLINE
595 #  define XOPTS "x+"
596 #else
597 #  define XOPTS
598 #endif
599
600     int i = mdwopt(argc, argv, "huV" XOPTS "t:d:qv",
601                    opts, 0, 0, OPTF_NEGATION);
602
603 #undef XOPTS
604
605     if (i < 0)
606       break;
607     switch (i) {
608       case 'h':
609         help(stdout);
610         exit(0);
611       case 'V':
612         version(stdout);
613         exit(0);
614       case 'u':
615         usage(stdout);
616         exit(0);
617       case 't': {
618         char *p;
619         timeout = strtoul(optarg, &p, 0);
620         switch (*p) {
621           case 'd': timeout *= 24;
622           case 'h': timeout *= 60;
623           case 'm': timeout *= 60;
624           case 's': case 0: break;
625           default:
626             die(1, "unrecognized suffix character `%c'", *p);
627             break;
628         }
629       } break;
630 #ifdef PATH_XGETLINE
631       case 'x':
632         flags |= f_xgetline;
633         flags &= ~f_getpass;
634         break;
635       case 'x' | OPTF_NEGATED:
636         flags |= f_getpass;
637         flags &= ~f_xgetline;
638         break;
639 #endif
640       case 'd':
641         dir = optarg;
642         break;
643       case 'q':
644         if (verbose > 0)
645           verbose--;
646         break;
647       case 'v':
648         verbose++;
649         break;
650       default:
651         flags |= f_bogus;
652         break;
653     }
654   }
655
656   if (optind < argc)
657     sock = argv[optind++];
658
659   if (optind < argc)
660     flags |= f_bogus;
661
662   if (flags & f_bogus) {
663     usage(stderr);
664     exit(1);
665   }
666
667   /* --- Sort out how to request the passphrase --- */
668
669 #ifdef PATH_XGETLINE
670   if ((flags & (f_xgetline | f_getpass)) == 0) {
671     if (isatty(STDIN_FILENO))
672       flags |= f_getpass;
673     else
674       flags |= f_xgetline;
675   }
676 #endif
677
678   /* --- Make the socket directory --- *
679    *
680    * Be very paranoid about the directory.  Very paranoid indeed.
681    */
682
683   if (dir) {
684     struct stat st;
685
686     if (chdir(dir)) {
687       if (errno != ENOENT) {
688         die(1, "couldn't change directory to `%s': %s",
689             dir, strerror(errno));
690       }
691       if (mkdir(dir, 0700))
692         die(1, "couldn't create directory `%s': %s", dir, strerror(errno));
693       if (chdir(dir)) {
694         die(1, "couldn't change directory to `%s': %s",
695             dir, strerror(errno));
696       }
697       if (verbose > 1)
698         log("created directory `%s'", dir);
699     }
700
701     if (stat(".", &st))
702       die(1, "couldn't stat directory `%s': %s", dir, strerror(errno));
703     if ((st.st_mode & 07777) != 0700) {
704       die(1, "directory `%s' has mode %04o; should be 0700",
705           dir, st.st_mode & 07777);
706     }
707     if (st.st_uid != getuid()) {
708       struct passwd *pw = getpwuid(st.st_uid);
709       char b[16];
710       char *p;
711
712       if (pw)
713         p = pw->pw_name;
714       else {
715         sprintf(b, "uid `%i'", st.st_uid);
716         p = b;
717       }
718       die(1, "directory `%s' owned by %s; should be you", dir, p);
719     }
720
721     if (verbose > 2)
722       log("directory `%s' checked out OK", dir);
723   }
724
725   /* --- A little argument checking --- */
726
727   if (!sock) {
728     if (dir)
729       sock = PIXIE_SOCKET;
730     else
731       die(1, "no socket filename given");
732   }
733
734   /* --- Create and bind the socket --- */
735
736   {
737     size_t len = strlen(sock) + 1;
738     size_t sz = offsetof(struct sockaddr_un, sun_path) + len;
739     struct sockaddr_un *sun = malloc(sz);
740     unsigned u = umask(077);
741
742     /* --- Create the file descriptor --- */
743
744     if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
745       die(1, "couldn't create socket: %s", strerror(errno));
746     if (fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
747       die(1, "couldn't configure socket: %s", strerror(errno));
748
749     /* --- Set up the address --- */
750
751     memset(sun, 0, sz);
752     sun->sun_family = AF_UNIX;
753     strcpy(sun->sun_path, sock);
754
755     /* --- Bind to the address --- */
756
757     if (bind(fd, (struct sockaddr *)sun, sz))
758       die(1, "couldn't bind to socket `%s': %s", sock, strerror(errno));
759     free(sun);
760     if (listen(fd, 5))
761       die(1, "couldn't listen on socket: %s", strerror(errno));
762     umask(u);
763   }
764
765   /* --- Set signals up --- *
766    *
767    * I'm using Dan Bernstein's self-pipe trick to catch signals in the main
768    * code.  See http://pobox.com/~djb/docs/selfpipe.html
769    */
770
771   {
772     static int sig[] = { SIGINT, SIGTERM, SIGHUP, SIGQUIT, 0 };
773     struct sigaction sa;
774     int i;
775     int pfd[2];
776
777     /* --- Create the signal pipe --- */
778
779     if (pipe(pfd))
780       die(1, "couldn't create pipe: %s", strerror(errno));
781     
782     if (fdflags(pfd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) ||
783         fdflags(pfd[1], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
784       die(1, "couldn't configure pipe attributes: %s", strerror(errno));
785
786     sigfd_in = pfd[0];
787     sigfd_out = pfd[1];
788
789     /* --- Set up the signal handlers --- */
790
791     sa.sa_handler = sigwrite;
792     sa.sa_flags = 0;
793 #ifdef SA_RESTART
794     sa.sa_flags |= SA_RESTART;
795 #endif
796     sigemptyset(&sa.sa_mask);
797
798     for (i = 0; sig[i]; i++) {
799       struct sigaction osa;
800       if (sigaction(sig[i], 0, &osa) == 0 &&
801           osa.sa_handler != SIG_IGN)
802         sigaction(sig[i], &sa, 0);
803     }
804   }
805
806   /* --- Now listen, and wait --- */
807
808   {
809     int maxfd;
810     fd_set fds;
811     struct timeval tv, now, when, *tvp;
812
813     if (fd > sigfd_in)
814       maxfd = fd + 1;
815     else
816       maxfd = sigfd_in + 1;
817
818     if (flags & f_goodbuf) {
819       if (verbose > 1)
820         log("passphrase buffer created and locked OK");
821     } else {
822       if (emsg && verbose > 1)
823         log(emsg, strerror(elock));
824       else if (verbose)
825         log("couldn't create locked passphrase buffer");
826     }
827
828     if (verbose > 1)
829       log("passphrase pixie initialized OK");
830
831     for (;;) {
832
833       /* --- Set up the file descriptors --- */
834
835       FD_ZERO(&fds);
836       FD_SET(fd, &fds);
837       FD_SET(sigfd_in, &fds);
838
839       /* --- Set up the timeout --- */
840
841       if (!timeout || !(flags & f_pass))
842         tvp = 0;
843       else {
844         gettimeofday(&now, 0);
845         TV_SUB(&tv, &when, &now);
846         tvp = &tv;
847       }
848
849       /* --- Wait for something interesting to happen --- */
850
851       if (select(maxfd, &fds, 0, 0, tvp) < 0) {
852         if (errno == EINTR)
853           continue;
854         die(1, "error from select: %s", strerror(errno));
855       }
856
857       /* --- Act on a signal --- */
858
859       if (FD_ISSET(sigfd_in, &fds)) {
860         char buf[256];
861         int r;
862         sigset_t ss;
863
864         /* --- Go through each signal in turn --- *
865          *
866          * Don't try to respond to duplicates.
867          */
868
869         sigemptyset(&ss);
870         while ((r = read(sigfd_in, buf, sizeof(buf))) > 0) {
871           char *p = buf;
872
873           /* --- A buffer of signals has arrived; grind through it --- */
874
875           for (p = buf; r; r--, p++) {
876
877             /* --- If this signal has been seen, skip on to the next --- */
878
879             if (sigismember(&ss, *p))
880               continue;
881             sigaddset(&ss, *p);
882
883             switch (*p) {
884               const char *s;
885
886               /* --- Various interesting signals --- */
887
888               case SIGINT:
889                 s = "SIGINT";
890                 goto closedown;
891               case SIGTERM:
892                 s = "SIGTERM";
893                 goto closedown;
894               case SIGHUP:
895                 s = "SIGHUP";
896                 goto clear;
897               case SIGQUIT:
898                 s = "SIGQUIT";
899                 goto clear;
900                 
901               /* --- Shut down the program if requested --- */
902
903               closedown:
904                 if (verbose > 1)
905                   log("closing down on %s", s);
906                 goto done;
907
908               /* --- Clear the passphrase if requested --- */
909
910               clear:
911                 if (flags & f_pass) {
912                   memset(pass, 0, PIXIE_BUFSZ);
913                   passlen = 0;
914                   flags &= ~f_pass;
915                   if (verbose)
916                     log("caught %s: passphrase cleared", s);
917                 } else if (verbose > 1)
918                   log("caught %s: passphrase not set", s);
919                 break;
920
921               /* --- Other signals which aren't so interesting --- */
922
923               default:
924                 if (verbose > 2)
925                   log("caught unexpected signal %i: ignoring it", *p);
926                 break;
927             }
928           }
929         }
930       }
931
932       /* --- Act on a passphrase timeout --- */
933
934       if (timeout && (flags & f_pass)) {
935         gettimeofday(&now, 0);
936         if (TV_CMP(&now, >, &when)) {
937           memset(pass, 0, PIXIE_BUFSZ);
938           passlen = 0;
939           flags &= ~f_pass;
940           if (verbose > 1)
941             log("passphrase timed out");
942         }
943       }
944
945       /* --- Act on a new connection --- */
946
947       if (FD_ISSET(fd, &fds)) {
948         int nfd;
949
950         {
951           struct sockaddr_un sun;
952           int sunsz = sizeof(sun);
953
954           if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) {
955             if (verbose > 1)
956               log("accept failed: %s", strerror(errno));
957             goto fail_0;
958           }
959         }
960
961         if (!(flags & f_pass)) {
962           if (get_pass()) {
963             if (verbose)
964               log("couldn't get passphrase: %s", strerror(errno));
965             goto fail_1;
966           }
967           flags |= f_pass;
968           if (timeout) {
969             gettimeofday(&when, 0);
970             when.tv_sec += timeout;
971           }
972         }
973         write(nfd, pass, passlen);
974         if (verbose)
975           log("responded to passphrase request");
976       fail_1:
977         close(nfd);
978       fail_0:
979         ;
980       }
981     }
982   }
983
984 done:
985   memset(pass, 0, PIXIE_BUFSZ);
986   passlen = 0;
987   unlink(sock);
988   return (0);
989 }
990
991 /*----- That's all, folks -------------------------------------------------*/