chiark / gitweb /
infra: Clean up project setup
[pixie] / pixie.c
1 /* -*-c-*-
2  *
3  * $Id: pixie.c,v 1.2 2000/07/07 18:33:16 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.2  2000/07/07 18:33:16  mdw
31  * Fix reading of timeouts
32  *
33  * Revision 1.1.1.1  1999/10/23 10:58:49  mdw
34  * New import.
35  *
36  */
37
38 /*----- Header files ------------------------------------------------------*/
39
40 #include <errno.h>
41 #include <signal.h>
42 #include <stdarg.h>
43 #include <stddef.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <time.h>
48
49 #include <sys/types.h>
50 #include <sys/time.h>
51 #include <unistd.h>
52 #include <sys/stat.h>
53 #include <sys/wait.h>
54 #include <pwd.h>
55 #include <fcntl.h>
56 #include <termios.h>
57
58 #ifdef HAVE_MLOCK
59 #  include <sys/mman.h>
60 #endif
61
62 #include <sys/socket.h>
63 #include <sys/un.h>
64
65 #include "mdwopt.h"
66
67 /*----- Magic constants ---------------------------------------------------*/
68
69 #define PIXIE_BUFSZ 1024                /* Passphrase buffer size */
70 #define PIXIE_TIMEOUT 300               /* Default timeout (in seconds) */
71
72 #define PIXIE_SOCKET "pass-socket"
73
74 /*----- Static variables --------------------------------------------------*/
75
76 static char *pass;
77 static size_t passlen = 0;
78 static int sigfd_out;
79 static unsigned flags;
80
81 enum {
82   f_pass = 1u,
83   f_xgetline = 2u,
84   f_getpass = 4u,
85   f_goodbuf = 8u,
86   f_bogus = 128u
87 };
88
89 /*----- Library code ------------------------------------------------------*/
90
91 const char *pn__name = "<UNNAMED>";     /* Program name */
92 #define QUIS pn__name
93
94 /* --- @quis@ --- *
95  *
96  * Arguments:   ---
97  *
98  * Returns:     Pointer to the program name.
99  *
100  * Use:         Returns the program name.
101  */
102
103 const char *quis(void) { return (QUIS); }
104
105 /* --- @ego@ --- *
106  *
107  * Arguments:   @const char *p@ = pointer to program name
108  *
109  * Returns:     ---
110  *
111  * Use:         Tells mLib what the program's name is.
112  */
113
114 #ifndef PATHSEP
115 #  if defined(__riscos)
116 #    define PATHSEP '.'
117 #  elif defined(__unix) || defined(unix)
118 #    define PATHSEP '/'
119 #  else
120 #    define PATHSEP '\\'
121 #  endif
122 #endif
123
124 void ego(const char *p)
125 {
126   const char *q = p;
127   while (*q) {
128     if (*q++ == PATHSEP)
129       p = q;
130   }
131   if (*p == '-')
132     p++;
133   pn__name = p;
134 }
135
136 #undef PATHSEP
137
138 /* --- @pquis@ --- *
139  *
140  * Arguments:   @FILE *fp@ = output stream to write on
141  *              @const char *p@ = pointer to string to write
142  *
143  * Returns:     Zero if everything worked, EOF if not.
144  *
145  * Use:         Writes the string @p@ to the output stream @fp@.  Occurrences
146  *              of the character `$' in @p@ are replaced by the program name
147  *              as reported by @quis@.  A `$$' is replaced by a single `$'
148  *              sign.
149  */
150
151 int pquis(FILE *fp, const char *p)
152 {
153   size_t sz;
154
155   while (*p) {
156     sz = strcspn(p, "$");
157     if (sz) {
158       if (fwrite(p, 1, sz, fp) < sz)
159         return (EOF);
160       p += sz;
161     }
162     if (*p == '$') {
163       p++;
164       if (*p == '$') {
165         if (fputc('$', fp) == EOF)
166           return (EOF);
167         p++;
168       } else {
169         if (fputs(pn__name, fp) == EOF)
170           return (EOF);
171       }
172     }
173   }
174   return (0);
175 }
176
177 /* --- @die@ --- *
178  *
179  * Arguments:   @int status@ = exit status to return
180  *              @const char *f@ = a @printf@-style format string
181  *              @...@ = other arguments
182  *
183  * Returns:     Never.
184  *
185  * Use:         Reports an error and exits.  Like @moan@ above, only more
186  *              permanent.
187  */
188
189 void die(int status, const char *f, ...)
190 {
191   va_list ap;
192   va_start(ap, f);
193   fprintf(stderr, "%s: ", QUIS);
194   vfprintf(stderr, f, ap);
195   va_end(ap);
196   putc('\n', stderr);
197   exit(status);
198 }
199
200 /* --- @fdflags@ --- *
201  *
202  * Arguments:   @int fd@ = file descriptor to fiddle with
203  *              @unsigned fbic, fxor@ = file flags to set and clear
204  *              @unsigned fdbic, fdxor@ = descriptor flags to set and clear
205  *
206  * Returns:     Zero if successful, @-1@ if not.
207  *
208  * Use:         Sets file descriptor flags in what is, I hope, an obvious
209  *              way.
210  */
211
212 int fdflags(int fd, unsigned fbic, unsigned fxor,
213             unsigned fdbic, unsigned fdxor)
214 {
215   int f;
216
217   if ((f = fcntl(fd, F_GETFL)) == -1 ||
218       fcntl(fd, F_SETFL, (f & ~fbic) ^ fxor) == -1 ||
219       (f = fcntl(fd, F_GETFD)) == -1 ||
220       fcntl(fd, F_SETFD, (f & ~fdbic) ^ fdxor) == -1)
221     return (-1);
222   return (0);
223 }
224
225 /* --- Timeval manipulation macros --- */
226
227 #define MILLION 1000000
228
229 #define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec)
230
231 #define TV_ADDL(dst, a, sec, usec) do {                                 \
232   (dst)->tv_sec = (a)->tv_sec + (sec);                                  \
233   (dst)->tv_usec = (a)->tv_usec + (usec);                               \
234   if ((dst)->tv_usec >= MILLION) {                                      \
235     (dst)->tv_usec -= MILLION;                                          \
236     (dst)->tv_sec++;                                                    \
237   }                                                                     \
238 } while (0)
239
240 #define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec)
241
242 #define TV_SUBL(dst, a, sec, usec) do {                                 \
243   (dst)->tv_sec = (a)->tv_sec - (sec);                                  \
244   if ((a)->tv_usec >= (usec))                                           \
245     (dst)->tv_usec = (a)->tv_usec - (usec);                             \
246   else {                                                                \
247     (dst)->tv_usec = (a)->tv_usec + MILLION - (usec);                   \
248     (dst)->tv_sec--;                                                    \
249   }                                                                     \
250 } while (0)
251
252 #define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ?                  \
253                             (a)->tv_usec op (b)->tv_usec :              \
254                             (a)->tv_sec op (b)->tv_sec)
255
256 /*----- Main code ---------------------------------------------------------*/
257
258 /* --- @log@ --- *
259  *
260  * Arguments:   @const char *p@ = @printf@-style format string
261  *              @...@ = extra arguments to fill in
262  *
263  * Returns:     ---
264  *
265  * Use:         Writes out a timestamped log message.
266  */
267
268 static void log(const char *p, ...)
269 {
270   char b[32];
271   va_list ap;
272   time_t t = time(0);
273   struct tm *tm = localtime(&t);
274
275   strftime(b, sizeof(b), "%Y-%m-%d %H:%M:%S", tm);
276   fprintf(stderr, "%s: %s ", QUIS, b);
277   va_start(ap, p);
278   vfprintf(stderr, p, ap);
279   va_end(ap);
280   fputc('\n', stderr);
281 }
282
283 /* --- @sigwrite@ --- *
284  *
285  * Arguments:   @int sig@ = signal number
286  *
287  * Returns:     ---
288  *
289  * Use:         Handles signals.  It writes the signal number to a pipe and
290  *              exits.  It's possible for signals to be lost if the pipe is
291  *              full.  This isn't likely enough to be worth caring about.
292  *              The implementation in mLib's `sig.c' does the job right but
293  *              it's rather more effort.
294  */
295
296 static void sigwrite(int sig)
297 {
298   int e = errno;
299   char c = sig;
300   write(sigfd_out, &c, 1);
301   errno = e;
302 }
303
304 /* --- @readpass@ --- *
305  *
306  * Arguments:   @int fd@ = file descriptor to read from
307  *
308  * Returns:     0 if OK, -1 if not.
309  *
310  * Use:         Reads a line from a file descriptor.  It continues reading
311  *              buffers until it gets a newline character.  If the buffer
312  *              becomes full, a newline is inserted and no more data is
313  *              read.  This might cause confusion.
314  */
315
316 static int readpass(int fd)
317 {
318   int r;
319   char *p = pass;
320   char *q;
321   size_t sz = PIXIE_BUFSZ;
322
323   for (;;) {
324     r = read(fd, p, sz);
325     if (r < 0)
326       return (-1);
327     if (r == 0) {
328       q = p + r;
329       break;
330     }
331     if ((q = memchr(p, '\n', r)) != 0) {
332       q++;
333       break;
334     }
335     sz -= r;
336     p += r;
337     if (!sz) {
338       q = p;
339       p[-1] = '\n';
340       break;
341     }
342   }
343
344   passlen = q - pass;
345   return (0);
346 }
347
348 /* --- @get_pass@ --- *
349  *
350  * Arguments:   ---
351  *
352  * Returns:     0 if OK, -1 if it failed.
353  *
354  * Use:         Reads a passphrase from somewhere.  The data from the
355  *              passphrase goes straight into the @pass@ buffer without
356  *              touching any other memory.  Of course, if @xgetline@ is used,
357  *              it might end up in unprotected memory there.  That's a shame,
358  *              but @xgetline@ is a much shorter-lived process than this one
359  *              so it shouldn't matter as much.
360  */
361
362 static int get_pass(void)
363 {
364 #ifdef PATH_XGETLINE
365   if (flags & f_xgetline) {
366     int fd[2];
367     pid_t kid;
368     int r;
369
370     /* --- Do everything by hand --- *
371      *
372      * I could, I suppose, use @popen@.  However, (a) that involves a shell
373      * which is extra overhead and makes passing arguments with spaces a
374      * little trickier; and (b) it uses @stdio@ buffers, which might get
375      * swapped to disk.
376      */
377
378     if (pipe(fd))
379       return (-1);
380     kid = fork();
381     if (kid < 0)
382       return (-1);
383     if (kid == 0) {
384       dup2(fd[1], STDOUT_FILENO);
385       close(fd[0]);
386       close(fd[1]);
387       execlp(PATH_XGETLINE, "xgetline",
388              "-i", "-tPGP pixie", "-pPGP passphrase:", (char *)0);
389       _exit(127);
390     }
391     close(fd[1]);
392     r = readpass(fd[0]);
393     close(fd[0]);
394     waitpid(kid, 0, 0);
395     return (r);
396   } else
397 #endif
398   {
399     struct termios o, n;
400     int fd;
401     int r;
402     char prompt[] = "PGP passphrase: ";
403     char nl = '\n';
404
405     /* --- Do this by hand --- *
406      *
407      * I could use @getpass@, but that puts the passphrase in its own memory
408      * rather than mine, so I'd have to scrub it out manually.  This is
409      * probably just as good if you don't mind fiddling with @termios@.
410      * Also, the GNU version uses @stdio@ streams to read from the terminal,
411      * which might be considered a Bad Thing.
412      */
413
414     if ((fd = open("/dev/tty", O_RDWR)) < 0)
415       return (-1);
416     if (tcgetattr(fd, &o))
417       return (-1);
418     n = o;
419     n.c_lflag &= ~(ECHO | ISIG);
420     if (tcsetattr(fd, TCSAFLUSH, &n))
421       return (-1);
422     write(fd, prompt, sizeof(prompt) - 1);
423     r = readpass(fd);
424     tcsetattr(fd, TCSAFLUSH, &o);
425     write(fd, &nl, 1);
426     close(fd);
427     return (r);
428   }
429 }
430
431 /* --- @help@, @version@ @usage@ --- *
432  *
433  * Arguments:   @FILE *fp@ = stream to write on
434  *
435  * Returns:     ---
436  *
437  * Use:         Emit helpful messages.
438  */
439
440 static void usage(FILE *fp)
441 {
442   pquis(fp, "Usage: $ [-xqv] [-t timeout] [-d dir] [socket]\n");
443 }
444
445 static void version(FILE *fp)
446 {
447   pquis(fp, "$ version " VERSION "\n");
448 }
449
450 static void help(FILE *fp)
451 {
452   version(fp);
453   fputc('\n', fp);
454   usage(fp);
455   pquis(fp, "\n\
456 The passphrase pixie remembers a PGP passphrase and passes it on to\n\
457 clients which connect to a Unix-domain socket.\n\
458 \n\
459 The pixie will forget a passphrase after a certain amount of time.  The\n\
460 duration of the pixie's memory is configurable using the `-t' option, and\n\
461 the default is 5 minutes.  By giving a timeout of zero, the pixie can be\n\
462 endowed with a perfect memory.\n\
463 \n\
464 The pixie attempts to lock its passphrase buffer into physical memory.  If\n\
465 this doesn't work (e.g., your operating system doesn't support this\n\
466 feature, or you have insufficient privilege) a warning is emitted.\n\
467 \n\
468 Options available are:\n\
469 \n\
470 -h, --help              Show this help text.\n\
471 -V, --version           Show the pixie's version number.\n\
472 -u, --usage             Show a uselessly terse usage message.\n\
473 \n\
474 "
475 #ifdef PATH_XGETLINE
476 "\
477 -x, --x11               Run `xgetline' to read a passphrase.\n\
478 +x, --no-x11            Don't run `xgetline' to read a passphrase.\n\
479 "
480 #endif
481 "\
482 -d, --directory=DIR     Make secure directory DIR and change to it.\n\
483 -q, --quiet             Don't emit so many messages.\n\
484 -v, --verbose           Emit more messages.\n\
485 ");
486 }
487
488 /* --- @main@ --- *
489  *
490  * Arguments:   @int argc@ = number of arguments
491  *              @char *argv[]@ = vector of argument values
492  *
493  * Returns:     Zero if OK.
494  *
495  * Use:         Main program.  Listens on a socket and responds with a PGP
496  *              passphrase when asked.
497  */
498
499 int main(int argc, char *argv[])
500 {
501   char *dir = 0;
502   int fd;
503   int sigfd_in;
504   unsigned verbose = 1;
505   char *sock = 0;
506   unsigned long timeout = PIXIE_TIMEOUT;
507   char *emsg = 0;
508   int elock = 0;
509
510   ego(argv[0]);
511
512   /* --- Try making a secure locked passphrase buffer --- *
513    *
514    * Drop privileges before emitting diagnostic messages.
515    */
516
517 #ifdef HAVE_MLOCK
518
519   /* --- Memory-map a page from somewhere --- */
520
521 #  ifdef MAP_ANON
522   pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE,
523               MAP_PRIVATE | MAP_ANON, -1, 0);
524 #  else
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 #  endif
534
535   /* --- Lock the page in memory --- *
536    *
537    * Why does @mmap@ return such a stupid result if it fails?
538    */
539
540   if (pass == 0 || pass == MAP_FAILED) {
541     emsg = "couldn't map a passphrase buffer: %s";
542     elock = errno;
543     pass = 0;
544   } else if (mlock(pass, PIXIE_BUFSZ)) {
545     emsg = "couldn't lock passphrase buffer: %s";
546     elock = errno;
547     munmap(pass, PIXIE_BUFSZ);
548     pass = 0;
549   } else
550     flags |= f_goodbuf;
551
552 #endif
553
554   /* --- Make a standard passphrase buffer --- */
555
556   setuid(getuid());
557
558 #ifdef HAVE_MLOCK
559   if (!pass)
560 #endif
561   {
562     if ((pass = malloc(PIXIE_BUFSZ)) == 0)
563       die(1, "not enough memory for passphrase buffer");
564   }
565
566   /* --- Parse options --- */
567
568   for (;;) {
569     static struct option opts[] = {
570
571       /* --- GNUey help options --- */
572
573       { "help",         0,              0,      'h' },
574       { "usage",        0,              0,      'u' },
575       { "version",      0,              0,      'V' },
576
577       /* --- Other options --- */
578
579       { "timeout",      OPTF_ARGREQ,    0,      't' },
580 #ifdef PATH_XGETLINE
581       { "xgetline",     OPTF_NEGATE,    0,      'x' },
582       { "x11",          OPTF_NEGATE,    0,      'x' },
583 #endif
584       { "directory",    OPTF_ARGREQ,    0,      'd' },
585       { "quiet",        0,              0,      'q' },
586       { "verbose",      0,              0,      'v' },
587
588       /* --- Magic end marker --- */
589
590       { 0,              0,              0,      0 }
591     };
592
593 #ifdef PATH_XGETLINE
594 #  define XOPTS "x+"
595 #else
596 #  define XOPTS
597 #endif
598
599     int i = mdwopt(argc, argv, "huV" XOPTS "t:d:qv",
600                    opts, 0, 0, OPTF_NEGATION);
601
602 #undef XOPTS
603
604     if (i < 0)
605       break;
606     switch (i) {
607       case 'h':
608         help(stdout);
609         exit(0);
610       case 'V':
611         version(stdout);
612         exit(0);
613       case 'u':
614         usage(stdout);
615         exit(0);
616       case 't': {
617         char *p;
618         timeout = strtoul(optarg, &p, 0);
619         switch (*p) {
620           case 'd': timeout *= 24;
621           case 'h': timeout *= 60;
622           case 'm': timeout *= 60;
623           case 's': if (p[1] != 0)
624           default:    timeout = 0;
625           case 0:   break;
626         }
627         if (!timeout)
628           die(1, "bad time specification `%s'", optarg);
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 -------------------------------------------------*/