chiark / gitweb /
Add some more vectors, and a whinge about how Skipjack test vectors are.
[catacomb] / pixie.c
1 /* -*-c-*-
2  *
3  * $Id: pixie.c,v 1.5 2000/07/29 22:05:22 mdw Exp $
4  *
5  * Passphrase pixie for Catacomb
6  *
7  * (c) 1999 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of Catacomb.
13  *
14  * Catacomb is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU Library General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  * 
19  * Catacomb is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  * 
24  * You should have received a copy of the GNU Library General Public
25  * License along with Catacomb; if not, write to the Free
26  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27  * MA 02111-1307, USA.
28  */
29
30 /*----- Revision history --------------------------------------------------* 
31  *
32  * $Log: pixie.c,v $
33  * Revision 1.5  2000/07/29 22:05:22  mdw
34  * Miscellaneous tidyings:
35  *
36  *   * Change the timeout to something more appropriate for real use.
37  *
38  *   * Check assumptions about object types when binding the socket.  In
39  *     particular, don't zap the socket if it's really something else.
40  *
41  *   * In @p_request@, return a failure if the shell command returned
42  *     nonzero.  Fix a bug in @p_get@ which incorrectly passes on a success
43  *     code when this happens.
44  *
45  *   * Dispose of the locked memory in client mode to avoid being
46  *     antisocial.
47  *
48  *   * Also in client mode, don't report closure from the server if we're
49  *     running noninteractively.
50  *
51  *   * Insert a missing option letter into the usage string.
52  *
53  *   * Change to the root directory after forking in daemon mode.
54  *
55  * Revision 1.4  2000/06/17 11:50:53  mdw
56  * New pixie protocol allowing application to request passphrases and send
57  * them to the pixie.  Use the secure arena interface for the input
58  * buffer.  Extend the input buffer.  Other minor fixes.
59  *
60  * Revision 1.3  1999/12/22 22:14:40  mdw
61  * Only produce initialization message if verbose.
62  *
63  * Revision 1.2  1999/12/22 22:13:42  mdw
64  * Fix bug in passphrase flushing loop.
65  *
66  * Revision 1.1  1999/12/22 15:58:41  mdw
67  * Passphrase pixie support.
68  *
69  */
70
71 /*----- Header files ------------------------------------------------------*/
72
73 #include "config.h"
74
75 #include <assert.h>
76 #include <ctype.h>
77 #include <errno.h>
78 #include <signal.h>
79 #include <stdarg.h>
80 #include <stddef.h>
81 #include <stdio.h>
82 #include <stdlib.h>
83 #include <string.h>
84 #include <time.h>
85
86 #include <sys/types.h>
87 #include <sys/time.h>
88 #include <unistd.h>
89 #include <sys/stat.h>
90 #include <sys/wait.h>
91 #include <pwd.h>
92 #include <fcntl.h>
93 #include <sys/ioctl.h>
94 #include <termios.h>
95 #include <syslog.h>
96
97 #include <sys/socket.h>
98 #include <sys/un.h>
99
100 #include <mLib/alloc.h>
101 #include <mLib/dstr.h>
102 #include <mLib/fdflags.h>
103 #include <mLib/mdwopt.h>
104 #include <mLib/quis.h>
105 #include <mLib/report.h>
106 #include <mLib/sel.h>
107 #include <mLib/selbuf.h>
108 #include <mLib/sig.h>
109 #include <mLib/str.h>
110 #include <mLib/sub.h>
111 #include <mLib/tv.h>
112
113 #include "arena.h"
114 #include "lmem.h"
115 #include "passphrase.h"
116 #include "pixie.h"
117
118 /*----- Static variables --------------------------------------------------*/
119
120 static unsigned long timeout = 900;
121 static sel_state sel;
122 static unsigned verbose = 1;
123 static const char *command = 0;
124 static lmem lm;
125 static unsigned flags = 0;
126
127 enum {
128   F_SYSLOG = 1,
129   F_FETCH = 2
130 };
131
132 /*----- Event logging -----------------------------------------------------*/
133
134 /* --- @log@ --- *
135  *
136  * Arguments:   @const char *p@ = @printf@-style format string
137  *              @...@ = extra arguments to fill in
138  *
139  * Returns:     ---
140  *
141  * Use:         Writes out a timestamped log message.
142  */
143
144 static void log(const char *p, ...)
145 {
146   dstr d = DSTR_INIT;
147   va_list ap;
148
149   if (!(flags & F_SYSLOG)) {
150     time_t t = time(0);
151     struct tm *tm = localtime(&t);
152     DENSURE(&d, 64);
153     d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
154   }
155   va_start(ap, p);
156   dstr_vputf(&d, p, ap);
157   va_end(ap);
158
159   if (flags & F_SYSLOG)
160     syslog(LOG_NOTICE, "%s", d.buf);
161   else {
162     DPUTC(&d, '\n');
163     dstr_write(&d, stderr);
164   }
165   DDESTROY(&d);
166 }
167
168 /*----- Passphrase management ---------------------------------------------*/
169
170 /* --- Data structures --- */
171
172 typedef struct phrase {
173   struct phrase *next;
174   struct phrase *prev;
175   char *tag;
176   char *p;
177   unsigned long t;
178   sel_timer timer;
179   unsigned f;
180 } phrase;
181
182 /* --- Variables --- */
183
184 #define P_ROOT ((phrase *)&p_root)
185 static struct { phrase *next; phrase *prev; } p_root = { P_ROOT, P_ROOT };
186
187 /* --- @p_free@ --- *
188  *
189  * Arguments:   @phrase *p@ = pointer to phrase block
190  *
191  * Returns:     ---
192  *
193  * Use:         Frees a phrase block.
194  */
195
196 static void p_free(phrase *p)
197 {
198   if (p->t)
199     sel_rmtimer(&p->timer);
200   free(p->tag);
201   l_free(&lm, p->p);
202   p->next->prev = p->prev;
203   p->prev->next = p->next;
204   DESTROY(p);
205 }
206
207 /* --- @p_timer@ --- *
208  *
209  * Arguments:   @struct timeval *tv@ = current time
210  *              @void *p@ = pointer to phrase
211  *
212  * Returns:     ---
213  *
214  * Use:         Expires a passphrase.
215  */
216
217 static void p_timer(struct timeval *tv, void *p)
218 {
219   phrase *pp = p;
220   if (verbose)
221     log("expiring passphrase `%s'", pp->tag);
222   p_free(pp);
223 }
224
225 /* --- @p_alloc@ --- *
226  *
227  * Arguments:   @size_t sz@ = amount of memory required
228  *
229  * Returns:     Pointer to allocated memory, or null.
230  *
231  * Use:         Allocates some locked memory, flushing old passphrases if
232  *              there's not enough space.
233  */
234
235 static void *p_alloc(size_t sz)
236 {
237   for (;;) {
238     char *p;
239     if ((p = l_alloc(&lm, sz)) != 0)
240       return (p);
241     if (P_ROOT->next == P_ROOT)
242       return (0);
243     if (verbose) { 
244       log("flushing passphrase `%s' to free up needed space",
245           P_ROOT->next->tag);
246     }
247     p_free(P_ROOT->next);
248   }
249 }
250
251 /* --- @p_find@ --- *
252  *
253  * Arguments:   @const char *tag@ = pointer to tag to find
254  *
255  * Returns:     Pointer to passphrase block, or null.
256  *
257  * Use:         Finds a passphrase with a given tag.
258  */
259
260 static phrase *p_find(const char *tag)
261 {
262   phrase *p;
263
264   for (p = P_ROOT->next; p != P_ROOT; p = p->next) {
265     if (strcmp(p->tag, tag) == 0) {
266       if (p->t) {
267         struct timeval tv;
268         sel_rmtimer(&p->timer);
269         gettimeofday(&tv, 0);
270         tv.tv_sec += p->t;
271         sel_addtimer(&sel, &p->timer, &tv, p_timer, p);
272       }
273       p->next->prev = p->prev;
274       p->prev->next = p->next;
275       p->next = P_ROOT;
276       p->prev = P_ROOT->prev;
277       P_ROOT->prev->next = p;
278       P_ROOT->prev = p;
279       return (p);
280     }
281   }
282   return (0);
283 }
284
285 /* --- @p_add@ --- *
286  *
287  * Arguments:   @const char *tag@ = pointer to tag string
288  *              @const char *p@ = pointer to passphrase
289  *              @unsigned long t@ = expiry timeout
290  *
291  * Returns:     Pointer to newly-added passphrase.
292  *
293  * Use:         Adds a new passphrase.  The tag must not already exist.
294  */
295
296 static phrase *p_add(const char *tag, const char *p, unsigned long t)
297 {
298   size_t sz = strlen(p) + 1;
299   char *l = p_alloc(sz);
300   phrase *pp;
301
302   /* --- Make sure the locked memory was allocated --- */
303
304   if (!l)
305     return (0);
306
307   /* --- Fill in some other bits of the block --- */
308
309   pp = CREATE(phrase);
310   memcpy(l, p, sz);
311   pp->p = l;
312   pp->tag = xstrdup(tag);
313   pp->f = 0;
314
315   /* --- Set the timer --- */
316
317   pp->t = t;
318   if (t) {
319     struct timeval tv;
320     gettimeofday(&tv, 0);
321     tv.tv_sec += t;
322     sel_addtimer(&sel, &pp->timer, &tv, p_timer, pp);
323   }
324
325   /* --- Link the block into the chain --- */
326
327   pp->next = P_ROOT;
328   pp->prev = P_ROOT->prev;
329   P_ROOT->prev->next = pp;
330   P_ROOT->prev = pp;
331   return (pp);
332 }
333
334 /* --- @p_flush@ --- *
335  *
336  * Arguments:   @const char *tag@ = pointer to tag string, or zero for all
337  *
338  * Returns:     ---
339  *
340  * Use:         Immediately flushes either a single phrase or all of them.
341  */
342
343 static void p_flush(const char *tag)
344 {
345   phrase *p;
346
347   if (!tag && verbose > 1)
348     log("flushing all passphrases");
349   p = P_ROOT->next;
350   while (p != P_ROOT) {
351     phrase *pp = p->next;
352     if (!tag)
353       p_free(p);
354     else if (strcmp(p->tag, tag) == 0) {
355       if (verbose > 1)
356         log("flushing passphrase `%s'", tag);
357       p_free(p);
358       break;
359     }
360     p = pp;
361   }
362 }
363
364 /*----- Reading passphrases -----------------------------------------------*/
365
366 /* --- @p_request@ --- *
367  *
368  * Arguments:   @const char *msg@ = message string
369  *              @const char *tag@ = pointer to tag string
370  *              @char *buf@ = pointer to (locked) buffer
371  *              @size_t sz@ = size of buffer
372  *
373  * Returns:     Zero if all went well, nonzero otherwise.
374  *
375  * Use:         Requests a passphrase from the user.
376  */
377
378 static int p_request(const char *msg, const char *tag, char *buf, size_t sz)
379 {
380   /* --- If there's a passphrase-fetching command, run it --- */
381
382   if (command) {
383     dstr d = DSTR_INIT;
384     const char *p;
385     int fd[2];
386     pid_t kid;
387     int r;
388     int rc;
389
390     /* --- Substitute the prompt string into the command --- */
391
392     p = command;
393     for (;;) {
394       const char *q = strchr(p, '%');
395       if (!q || !q[1]) {
396         DPUTS(&d, p);
397         break;
398       }
399       DPUTM(&d, p, q - p);
400       p = q + 1;
401       switch (*p) {
402         case 'm':
403           DPUTS(&d, msg);
404           break;
405         case 't':
406           DPUTS(&d, tag);
407           break;
408         default:
409           DPUTC(&d, '%');
410           DPUTC(&d, *p);
411           break;
412       }
413       p++;
414     }
415     DPUTZ(&d);
416
417     /* --- Create a pipe and start a child process --- */
418
419     if (pipe(fd))
420       goto fail_1;
421     if ((kid = fork()) < 0)
422       goto fail_2;
423
424     /* --- Child process --- */
425
426     fflush(0);
427     if (kid == 0) {
428       if (dup2(fd[1], STDOUT_FILENO) < 0)
429         _exit(127);
430       close(fd[0]);
431       execl("/bin/sh", "sh", "-c", d.buf, (void *)0);
432       _exit(127);
433     }
434
435     /* --- Read the data back into my buffer --- */
436
437     close(fd[1]);
438     if ((r = read(fd[0], buf, sz - 1)) >= 0) {
439       char *q = memchr(buf, '\n', r);
440       if (!q)
441         q = buf + r;
442       *q = 0;
443     }
444     close(fd[0]);
445     waitpid(kid, &rc, 0);
446     dstr_destroy(&d);
447     if (r < 0 || rc != 0)
448       goto fail_0;
449     goto ok;
450
451     /* --- Tidy up when things go wrong --- */
452
453   fail_2:
454     close(fd[0]);
455     close(fd[1]);
456   fail_1:
457     dstr_destroy(&d);
458   fail_0:
459     return (-1);
460   }
461
462   /* --- Read a passphrase from the terminal --- *
463    *
464    * Use the standard Catacomb passphrase-reading function, so it'll read the
465    * passphrase from a file descriptor or something if the appropriate
466    * environment variable is set.
467    */
468
469   {
470     dstr d = DSTR_INIT;
471     int rc;
472     dstr_putf(&d, "%s %s: ", msg, tag);
473     rc = pixie_getpass(d.buf, buf, sz);
474     dstr_destroy(&d);
475     if (rc)
476       return (rc);
477     goto ok;
478   }
479
480   /* --- Sort out the buffer --- *
481    *
482    * Strip leading spaces.
483    */
484
485 ok: {
486     char *p = buf;
487     size_t len;
488     while (isspace((unsigned char)*p))
489       p++;
490     len = strlen(p);
491     memmove(buf, p, len);
492     p[len] = 0;
493   }
494
495   /* --- Done --- */
496
497   return (0);
498 }
499
500 /* --- @p_get@ --- *
501  *
502  * Arguments:   @const char **q@ = where to store the result
503  *              @const char *tag@ = pointer to tag string
504  *              @unsigned mode@ = reading mode (verify?)
505  *              @time_t exp@ = expiry time suggestion
506  *
507  * Returns:     Zero if successful, @-1@ on a read failure, or @+1@ if the
508  *              passphrase is missing and there is no fetcher.  (This will
509  *              always happen if there is no fetcher and @mode@ is
510  *              @PMODE_VERIFY@.
511  *
512  * Use:         Reads a passphrase from somewhere.
513  */
514
515 static int p_get(const char **q, const char *tag, unsigned mode, time_t exp)
516 {
517 #define LBUFSZ 1024
518
519   phrase *p;
520   char *pp = 0;
521
522   /* --- Write a log message --- */
523
524   if (verbose > 1)
525     log("passphrase `%s' requested", tag);
526
527   /* --- If there is no fetcher, life is simpler --- */
528
529   if (!(flags & F_FETCH)) {
530     if (mode == PMODE_VERIFY)
531       return (+1);
532     if ((p = p_find(tag)) == 0)
533       return (+1);
534     *q = p->p;
535     return (0);
536   }
537
538   /* --- Try to find the phrase --- */
539
540   if (mode == PMODE_VERIFY)
541     p_flush(tag);
542   if (mode == PMODE_VERIFY || (p = p_find(tag)) == 0) {
543     if ((pp = p_alloc(LBUFSZ)) == 0)
544       goto fail;
545     if (p_request(mode == PMODE_READ ? "Passphrase" : "New passphrase",
546                   tag, pp, LBUFSZ) < 0)
547       goto fail;
548     p = p_add(tag, pp, exp);
549     if (!p)
550       goto fail;
551   }
552
553   /* --- If verification is requested, verify the passphrase --- */
554
555   if (mode == PMODE_VERIFY) {
556     if (!pp && (pp = p_alloc(LBUFSZ)) == 0)
557       goto fail;
558     if (p_request("Verify passphrase", tag, pp, LBUFSZ) < 0)
559       goto fail;
560     if (strcmp(pp, p->p) != 0) {
561       if (verbose)
562         log("passphrases for `%s' don't match", tag);
563       p_free(p);
564       goto fail;
565     }
566   }
567
568   /* --- Tidy up and return the passphrase --- */
569
570   if (pp) {
571     memset(pp, 0, LBUFSZ);
572     l_free(&lm, pp);
573   }
574   *q = p->p;
575   return (0);
576
577   /* --- Tidy up if things went wrong --- */
578
579 fail:
580   if (pp) {
581     memset(pp, 0, LBUFSZ);
582     l_free(&lm, pp);
583   }
584   return (-1);
585
586 #undef LBUFSZ
587 }
588
589 /*----- Server command parsing --------------------------------------------*/
590
591 /* --- Data structures --- */
592
593 typedef struct pixserv {
594   selbuf b;
595   int fd;
596   sel_timer timer;
597   unsigned f;
598 } pixserv;
599
600 enum { px_stdin = 1 };
601
602 #define PIXSERV_TIMEOUT 30
603
604 /* --- @pixserv_expire@ --- *
605  *
606  * Arguments:   @struct timeval *tv@ = pointer to current time
607  *              @void *p@ = pointer to server block
608  *
609  * Returns:     ---
610  *
611  * Use:         Expires a pixie connection if the remote end decides he's not
612  *              interested any more.
613  */
614
615 static void pixserv_expire(struct timeval *tv, void *p)
616 {
617   pixserv *px = p;
618   if (px->fd != px->b.reader.fd)
619     close(px->fd);
620   selbuf_destroy(&px->b);
621   close(px->b.reader.fd);
622   DESTROY(px);
623 }
624
625 /* --- @pixserv_write@ --- *
626  *
627  * Arguments:   @pixserv *px@ = pointer to server block
628  *              @const char *p@ = pointer to skeleton string
629  *              @...@ = other arguments to fill in
630  *
631  * Returns:     ---
632  *
633  * Use:         Formats a string and emits it to the output file.
634  */
635
636 static void pixserv_write(pixserv *px, const char *p, ...)
637 {
638   dstr d = DSTR_INIT;
639   va_list ap;
640
641   va_start(ap, p);
642   dstr_vputf(&d, p, ap);
643   write(px->fd, d.buf, d.len);
644   va_end(ap);
645   dstr_destroy(&d);
646 }
647
648 /* --- @pixserv_timeout@ --- *
649  *
650  * Arguments:   @const char *p@ = pointer to timeout string
651  *
652  * Returns:     Timeout in seconds.
653  *
654  * Use:         Translates a string to a timeout value in seconds.
655  */
656
657 static unsigned long pixserv_timeout(const char *p)
658 {
659   unsigned long t;
660   char *q;
661
662   if (!p)
663     return (timeout);
664
665   t = strtoul(p, &q, 0);
666   switch (*q) {
667     case 'd': t *= 24;
668     case 'h': t *= 60;
669     case 'm': t *= 60;
670     case 's': if (q[1] != 0)
671       default:    t = 0;
672     case 0:   break;
673   }
674   return (t);
675 }
676
677 /* --- @pixserv_line@ --- *
678  *
679  * Arguments:   @char *s@ = pointer to the line read
680  *              @void *p@ = pointer to server block
681  *
682  * Returns:     ---
683  *
684  * Use:         Handles a line read from the client.
685  */
686
687 static void pixserv_line(char *s, void *p)
688 {
689   pixserv *px = p;
690   char *q, *qq;
691   unsigned mode;
692
693   /* --- Handle an end-of-file --- */
694
695   if (!(px->f & px_stdin))
696     sel_rmtimer(&px->timer);
697   if (!s) {
698     if (px->fd != px->b.reader.fd)
699       close(px->fd);
700     selbuf_destroy(&px->b);
701     close(px->b.reader.fd);
702     return;
703   }
704
705   /* --- Fiddle the timeout --- */
706
707   if (!(px->f & px_stdin)) {
708     struct timeval tv;
709     gettimeofday(&tv, 0);
710     tv.tv_sec += PIXSERV_TIMEOUT;
711     sel_addtimer(&sel, &px->timer, &tv, pixserv_expire, px);
712   }
713
714   /* --- Scan out the first word --- */
715
716   if ((q = str_getword(&s)) == 0)
717     return;
718   for (qq = q; *qq; qq++)
719     *qq = tolower((unsigned char)*qq);
720
721   /* --- Handle a help request --- */
722
723   if (strcmp(q, "help") == 0) {
724     pixserv_write(px, "\
725 INFO Commands supported:\n\
726 INFO HELP\n\
727 INFO LIST\n\
728 INFO PASS tag [expire]\n\
729 INFO VERIFY tag [expire]\n\
730 INFO FLUSH [tag]\n\
731 INFO SET tag [expire] -- phrase\n\
732 INFO QUIT\n\
733 OK\n\
734 ");
735   }
736
737   /* --- List the passphrases --- */
738
739   else if (strcmp(q, "list") == 0) {
740     phrase *p;
741
742     for (p = P_ROOT->next; p != P_ROOT; p = p->next) {
743       if (!p->t)
744         pixserv_write(px, "ITEM %s no-expire\n", p->tag);
745       else {
746         struct timeval tv;
747         gettimeofday(&tv, 0);
748         TV_SUB(&tv, &p->timer.tv, &tv);
749         pixserv_write(px, "ITEM %s %i\n", p->tag, tv.tv_sec);
750       }
751     }
752     pixserv_write(px, "OK\n");
753   }
754
755   /* --- Request a passphrase --- */
756
757   else if ((mode = PMODE_READ, strcmp(q, "pass") == 0) ||
758            (mode = PMODE_VERIFY, strcmp(q, "verify") == 0)) {
759     unsigned long t;
760     const char *p;
761     int rc;
762
763     if ((q = str_getword(&s)) == 0)
764       pixserv_write(px, "FAIL missing tag\n");
765     else if ((t = pixserv_timeout(s)) == 0)
766       pixserv_write(px, "FAIL bad timeout\n");
767     else {
768       rc = p_get(&p, q, mode, t > timeout ? timeout : t);
769       switch (rc) {
770         case 0:
771           pixserv_write(px, "OK %s\n", p);
772           break;
773         case -1:
774           pixserv_write(px, "FAIL error reading passphrase\n");
775           break;
776         case +1:
777           pixserv_write(px, "MISSING\n");
778           break;
779       }
780     }
781   }
782
783   /* --- Flush existing passphrases --- */
784
785   else if (strcmp(q, "flush") == 0) {
786     q = str_getword(&s);
787     p_flush(q);
788     pixserv_write(px, "OK\n");
789   }
790
791   /* --- Set a passphrase --- */
792
793   else if (strcmp(q, "set") == 0) {
794     char *tag;
795     unsigned long t;
796     if ((tag = str_getword(&s)) == 0)
797       pixserv_write(px, "FAIL missing tag\n");
798     else if ((q = str_getword(&s)) == 0)
799       pixserv_write(px, "FAIL no passphrase\n");
800     else {
801       if (strcmp(q, "--") != 0) {
802         t = pixserv_timeout(q);
803         q = str_getword(&s);
804       } else
805         t = pixserv_timeout(0);
806       if (!q)
807         pixserv_write(px, "FAIL no passphrase\n");
808       else if (strcmp(q, "--") != 0)
809         pixserv_write(px, "FAIL rubbish found before passphrase\n");
810       else {
811         p_flush(tag);
812         p_add(tag, s, t);
813         pixserv_write(px, "OK\n");
814       }
815     }
816   }
817
818   /* --- Shut the server down --- */
819
820   else if (strcmp(q, "quit") == 0) {
821     if (verbose)
822       log("%s client requested shutdown",
823           px->f & px_stdin ? "local" : "remote");
824     pixserv_write(px, "OK\n");
825     exit(0);
826   }
827
828   /* --- Report an error for other commands --- */
829
830   else
831     pixserv_write(px, "FAIL unknown command `%s'\n", q);
832 }
833
834 /* --- @pixserv_create@ --- *
835  *
836  * Arguments:   @int fd@ = file descriptor to read from
837  *              @int ofd@ = file descriptor to write to
838  *
839  * Returns:     Pointer to the new connection.
840  *
841  * Use:         Creates a new Pixie server instance for a new connection.
842  */
843
844 static pixserv *pixserv_create(int fd, int ofd)
845 {
846   pixserv *px = CREATE(pixserv);
847   struct timeval tv;
848   fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
849   if (ofd != fd)
850     fdflags(ofd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
851   px->fd = ofd;
852   selbuf_init(&px->b, &sel, fd, pixserv_line, px);
853   px->b.b.a = arena_secure;
854   selbuf_setsize(&px->b, 1024);
855   gettimeofday(&tv, 0);
856   tv.tv_sec += PIXSERV_TIMEOUT;
857   sel_addtimer(&sel, &px->timer, &tv, pixserv_expire, px);
858   px->f = 0;
859   return (px);
860 }
861
862 /* --- @pixserv_accept@ --- *
863  *
864  * Arguments:   @int fd@ = file descriptor
865  *              @unsigned mode@ = what's happened
866  *              @void *p@ = an uninteresting argument
867  *
868  * Returns:     ---
869  *
870  * Use:         Accepts a new connection.
871  */
872
873 static void pixserv_accept(int fd, unsigned mode, void *p)
874 {
875   int nfd;
876   struct sockaddr_un sun;
877   int sunsz = sizeof(sun);
878
879   if (mode != SEL_READ)
880     return;
881   if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) {
882     if (verbose)
883       log("new connection failed: %s", strerror(errno));
884     return;
885   }
886   pixserv_create(nfd, nfd);
887 }
888
889 /*----- Setting up the server ---------------------------------------------*/
890
891 /* --- @unlinksocket@ --- *
892  *
893  * Arguments:   ---
894  *
895  * Returns:     ---
896  *
897  * Use:         Tidies up the socket when it's finished with.
898  */
899
900 static char *sockpath;
901
902 static void unlinksocket(void)
903 {
904   unlink(sockpath);
905   l_purge(&lm);
906 }
907
908 /* --- @pix_sigdie@ --- *
909  *
910  * Arguments:   @int sig@ = signal number
911  *              @void *p@ = uninteresting argument
912  *
913  * Returns:     ---
914  *
915  * Use:         Shuts down the program after a fatal signal.
916  */
917
918 static void pix_sigdie(int sig, void *p)
919 {
920   if (verbose) {
921     char *p;
922     char buf[20];
923
924     switch (sig) {
925       case SIGTERM:     p = "SIGTERM"; break;
926       case SIGINT:      p = "SIGINT"; break;
927       default:
928         sprintf(buf, "signal %i", sig);
929         p = buf;
930         break;
931     }
932     log("shutting down on %s", p);
933   }
934   exit(0);
935 }
936
937 /* --- @pix_sigflush@ --- *
938  *
939  * Arguments:   @int sig@ = signal number
940  *              @void *p@ = uninteresting argument
941  *
942  * Returns:     ---
943  *
944  * Use:         Flushes the passphrase cache on receipt of a signal.
945  */
946
947 static void pix_sigflush(int sig, void *p)
948 {
949   if (verbose) {
950     char *p;
951     char buf[20];
952
953     switch (sig) {
954       case SIGHUP:      p = "SIGHUP"; break;
955       case SIGQUIT:     p = "SIGQUIT"; break;
956       default:
957         sprintf(buf, "signal %i", sig);
958         p = buf;
959         break;
960     }
961     log("received %s; flushing passphrases", p);
962   }
963   p_flush(0);
964 }
965
966 /* --- @pix_setup@ --- *
967  *
968  * Arguments:   @struct sockaddr_un *sun@ = pointer to address to use
969  *              @size_t sz@ = size of socket address
970  *
971  * Returns:     ---
972  *
973  * Use:         Sets up the pixie's Unix-domain socket.
974  */
975
976 static void pix_setup(struct sockaddr_un *sun, size_t sz)
977 {
978   int fd;
979
980   /* --- Set up the parent directory --- */
981
982   {
983     char *p = sun->sun_path;
984     char *q = strrchr(p, '/');
985
986     if (q) {
987       dstr d = DSTR_INIT;
988       struct stat st;
989
990       DPUTM(&d, p, q - p);
991       DPUTZ(&d);
992
993       mkdir(d.buf, 0700);
994       if (stat(d.buf, &st))
995         die(1, "couldn't stat `%s': %s", d.buf, strerror(errno));
996       if (!S_ISDIR(st.st_mode))
997         die(1, "object `%s' isn't a directory", d.buf);
998       if (st.st_mode & 0077)
999         die(1, "parent directory `%s' has group or world access", d.buf);
1000       dstr_destroy(&d);
1001     }
1002   }
1003
1004   /* --- Initialize the socket --- */
1005
1006   {
1007     int n = 5;
1008     int e;
1009
1010     umask(0077);
1011   again:
1012     if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
1013       die(1, "couldn't create socket: %s", strerror(errno));
1014     if (bind(fd, (struct sockaddr *)sun, sz) < 0) {
1015       e = errno;
1016       if (errno != EADDRINUSE)
1017         die(1, "couldn't bind to address: %s", strerror(e));
1018       if (!n)
1019         die(1, "too many retries; giving up");
1020       n--;
1021       if (connect(fd, (struct sockaddr *)sun, sz)) {
1022         struct stat st;
1023         if (errno != ECONNREFUSED)
1024           die(1, "couldn't bind to address: %s", strerror(e));
1025         if (stat(sun->sun_path, &st))
1026           die(1, "couldn't stat `%s': %s", sun->sun_path, strerror(errno));
1027         if (!S_ISSOCK(st.st_mode))
1028           die(1, "object `%s' isn't a socket", sun->sun_path);
1029         if (verbose)
1030           log("stale socket found; removing it");
1031         unlink(sun->sun_path);
1032         close(fd);
1033       } else {
1034         if (verbose)
1035           log("server already running; shutting it down");
1036         write(fd, "QUIT\n", 5);
1037         sleep(1);
1038         close(fd);
1039       }
1040       goto again;
1041     }
1042     chmod(sun->sun_path, 0600);
1043     fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
1044     if (listen(fd, 5))
1045       die(1, "couldn't listen on socket: %s", strerror(errno));
1046   }
1047
1048   /* --- Set up the rest of the server --- */
1049
1050   {
1051     static sel_file serv;
1052     sockpath = sun->sun_path;
1053     atexit(unlinksocket);
1054     sel_initfile(&sel, &serv, fd, SEL_READ, pixserv_accept, 0);
1055     sel_addfile(&serv);
1056   }
1057 }
1058
1059 /*----- Client support code -----------------------------------------------*/
1060
1061 /* --- Variables --- */
1062
1063 static selbuf c_server, c_client;
1064 static c_flags = 0;
1065 enum { cf_uclose = 1, cf_sclose = 2 };
1066
1067 /* --- Line handler functions --- */
1068
1069 static void c_uline(char *s, void *p)
1070 {
1071   size_t sz;
1072   if (!s) {
1073     selbuf_destroy(&c_client);
1074     shutdown(c_server.reader.fd, 1);
1075     c_flags |= cf_uclose;
1076   } else {
1077     sz = strlen(s);
1078     s[sz++] = '\n';
1079     write(c_server.reader.fd, s, sz);
1080   }
1081 }
1082
1083 static void c_sline(char *s, void *p)
1084 {
1085   if (!s) {
1086     selbuf_destroy(&c_server);
1087     if (!(c_flags & cf_uclose)) {
1088       moan("server closed the connection");
1089       selbuf_destroy(&c_client);
1090     }
1091     exit(0);
1092   } else 
1093     puts(s);
1094 }
1095
1096 /* --- @pix_client@ --- *
1097  *
1098  * Arguments:   @struct sockaddr_un *sun@ = pointer to socket address
1099  *              @size_t sz@ = size of socket address
1100  *              @char *argv[]@ = pointer to arguments to send
1101  *
1102  * Returns:     ---
1103  *
1104  * Use:         Performs client-side actions for the passphrase pixie.
1105  */
1106
1107 static void pix_client(struct sockaddr_un *sun, size_t sz, char *argv[])
1108 {
1109   int fd;
1110
1111   /* --- Dispose of locked memory --- */
1112
1113   l_destroy(&lm);
1114
1115   /* --- Open the socket --- */
1116
1117   if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
1118     die(1, "couldn't create socket: %s", strerror(errno));
1119   if (connect(fd, (struct sockaddr *)sun, sz))
1120     die(1, "couldn't connect to server: %s", strerror(errno));
1121   selbuf_init(&c_server, &sel, fd, c_sline, 0);
1122
1123   /* --- If there are any arguments, turn them into a string --- */
1124
1125   if (!*argv)
1126     selbuf_init(&c_client, &sel, STDIN_FILENO, c_uline, 0);
1127   else {
1128     dstr d = DSTR_INIT;
1129     DPUTS(&d, *argv++);
1130     while (*argv) {
1131       DPUTC(&d, ' ');
1132       DPUTS(&d, *argv++);
1133     }
1134     DPUTC(&d, '\n');
1135     write(fd, d.buf, d.len);
1136     shutdown(fd, 1);
1137     c_flags |= cf_uclose;
1138     dstr_destroy(&d);
1139   } 
1140
1141   /* --- And repeat --- */
1142
1143   for (;;)
1144     sel_select(&sel);
1145 }
1146
1147 /*----- Main code ---------------------------------------------------------*/
1148
1149 /* --- @help@, @version@, @usage@ --- *
1150  *
1151  * Arguments:   @FILE *fp@ = stream to write on
1152  *
1153  * Returns:     ---
1154  *
1155  * Use:         Emit helpful messages.
1156  */
1157
1158 static void usage(FILE *fp)
1159 {
1160   pquis(fp, "\
1161 Usage:\n\
1162         $ [-qvfidl] [-c command] [-t timeout] [-s socket]\n\
1163         $ [-s socket] -C [command args...]\n\
1164 ");
1165 }
1166
1167 static void version(FILE *fp)
1168 {
1169   pquis(fp, "$, Catacomb version " VERSION "\n");
1170 }
1171
1172 static void help(FILE *fp)
1173 {
1174   version(fp);
1175   fputc('\n', fp);
1176   usage(fp);
1177   pquis(fp, "\n\
1178 The Catacomb passphrase pixie collects and caches passphrases used to\n\
1179 protect important keys.  Options provided:\n\
1180 \n\
1181 -h, --help              Show this help text.\n\
1182 -V, --version           Show the program's version number.\n\
1183 -u, --usage             Show a (very) terse usage summary.\n\
1184 \n\
1185 -C, --client            Connect to a running pixie as a client.\n\
1186 \n\
1187 -q, --quiet             Emit fewer log messages.\n\
1188 -v, --version           Emit more log messages.\n\
1189 -s, --socket=FILE       Name the pixie's socket.\n\
1190 -c, --command=COMMAND   Shell command to read a passphrase.\n\
1191 -f, --fetch             Fetch passphrases from the terminal.\n\
1192 -t, --timeout=TIMEOUT   Length of time to retain a passphrase in memory.\n\
1193 -i, --interactive       Allow commands to be typed interactively.\n\
1194 -d, --daemon            Fork into the background after initialization.\n\
1195 -l, --syslog            Emit log messages to the system log.\n\
1196 \n\
1197 The COMMAND may contain `%m' and `%t' markers which are replaced by a\n\
1198 prompt message and the passphrase tag respectively.  The TIMEOUT is an\n\
1199 integer, optionally followed by `d', `h', `m' or `s' to specify units of\n\
1200 days, hours, minutes or seconds respectively.\n\
1201 \n\
1202 In client mode, if a command is specified on the command line, it is sent\n\
1203 to the running server; otherwise the program reads requests from stdin.\n\
1204 Responses from the pixie are written to stdout.  Send a HELP request for\n\
1205 a quick summary of the pixie communication protocol.\n\
1206 ");
1207 }
1208
1209 /* --- @main@ --- *
1210  *
1211  * Arguments:   @int argc@ = number of arguments
1212  *              @char *argv[]@ = vector of argument values
1213  *
1214  * Returns:     Zero if OK.
1215  *
1216  * Use:         Main program.  Listens on a socket and responds with a PGP
1217  *              passphrase when asked.
1218  */
1219
1220 int main(int argc, char *argv[])
1221 {
1222   char *path = 0;
1223   struct sockaddr_un *sun;
1224   size_t sz;
1225   unsigned f = 0;
1226
1227   enum {
1228     f_bogus = 1,
1229     f_client = 2,
1230     f_stdin = 4,
1231     f_daemon = 8,
1232     f_syslog = 16
1233   };
1234
1235   /* --- Initialize libraries --- */
1236
1237   ego(argv[0]);
1238   sub_init();
1239
1240   /* --- Set up the locked memory area --- */
1241
1242   l_init(&lm, 16384);
1243   setuid(getuid());
1244
1245   /* --- Parse command line arguments --- */
1246
1247   for (;;) {
1248     static struct option opts[] = {
1249
1250       /* --- Standard GNUy help options --- */
1251
1252       { "help",         0,              0,      'h' },
1253       { "version",      0,              0,      'V' },
1254       { "usage",        0,              0,      'u' },
1255
1256       /* --- Other options --- */
1257
1258       { "quiet",        0,              0,      'q' },
1259       { "verbose",      0,              0,      'v' },
1260       { "client",       0,              0,      'C' },
1261       { "socket",       OPTF_ARGREQ,    0,      's' },
1262       { "command",      OPTF_ARGREQ,    0,      'c' },
1263       { "fetch",        0,              0,      'f' },
1264       { "timeout",      OPTF_ARGREQ,    0,      't' },
1265       { "interactive",  0,              0,      'i' },
1266       { "stdin",        0,              0,      'i' },
1267       { "daemon",       0,              0,      'd' },
1268       { "log",          0,              0,      'l' },
1269       { "syslog",       0,              0,      'l' },
1270
1271       /* --- Magic terminator --- */
1272
1273       { 0,              0,              0,      0 }      
1274     };
1275
1276     int i = mdwopt(argc, argv, "hVuqvCs:c:ft:idl", opts, 0, 0, 0);
1277     if (i < 0)
1278       break;
1279
1280     switch (i) {
1281
1282       /* --- GNUy help options --- */
1283
1284       case 'h':
1285         help(stdout);
1286         exit(0);
1287       case 'V':
1288         version(stdout);
1289         exit(0);
1290       case 'u':
1291         usage(stdout);
1292         exit(0);
1293
1294       /* --- Other interesting things --- */
1295
1296       case 'q':
1297         if (verbose)
1298           verbose--;
1299         break;
1300       case 'v':
1301         verbose++;
1302         break;
1303       case 'C':
1304         f |= f_client;
1305         break;
1306       case 's':
1307         path = optarg;
1308         break;
1309       case 't':
1310         if ((timeout = pixserv_timeout(optarg)) == 0)
1311           die(1, "bad timeout `%s'", optarg);
1312         break;
1313       case 'c':
1314         command = optarg;
1315         flags |= F_FETCH;
1316         break;
1317       case 'f':
1318         flags |= F_FETCH;
1319         break;
1320       case 'i':
1321         f |= f_stdin;
1322         break;
1323       case 'd':
1324         f |= f_daemon;
1325         break;
1326       case 'l':
1327         f |= f_syslog;
1328         break;
1329
1330       /* --- Something else --- */
1331
1332       default:
1333         f |= f_bogus;
1334         break;
1335     }
1336   }
1337
1338   if (f & f_bogus || (optind < argc && !(f & f_client))) {
1339     usage(stderr);
1340     exit(1);
1341   }
1342
1343   /* --- Set up the socket address --- */
1344
1345   sun = pixie_address(path, &sz);
1346
1347   /* --- Initialize selectory --- */
1348
1349   sel_init(&sel);
1350   signal(SIGPIPE, SIG_IGN);
1351
1352   /* --- Be a client if a client's wanted --- */
1353
1354   if (f & f_client)
1355     pix_client(sun, sz, argv + optind);
1356
1357   /* --- Open the syslog if requested --- */
1358
1359   if (f & f_syslog) {
1360     flags |= F_SYSLOG;
1361     openlog(QUIS, 0, LOG_DAEMON);
1362   }
1363
1364   /* --- Check on the locked memory area --- */
1365
1366   {
1367     dstr d = DSTR_INIT;
1368     int rc = l_report(&lm, &d);
1369     if (rc < 0)
1370       die(EXIT_FAILURE, d.buf);
1371     else if (rc && verbose) {
1372       log(d.buf);
1373       log("couldn't lock passphrase buffer");
1374     }
1375     dstr_destroy(&d);
1376     arena_setsecure(&lm.a);
1377   }
1378
1379   /* --- Set signal behaviours --- */
1380
1381   {
1382     static sig sigint, sigterm, sigquit, sighup;
1383     sig_init(&sel);
1384     sig_add(&sigint, SIGINT, pix_sigdie, 0);
1385     sig_add(&sigterm, SIGTERM, pix_sigdie, 0);
1386     sig_add(&sigquit, SIGQUIT, pix_sigflush, 0);
1387     sig_add(&sighup, SIGHUP, pix_sigflush, 0);
1388   }
1389
1390   /* --- Set up the server --- */
1391
1392   pix_setup(sun, sz);
1393   if (f & f_stdin) {
1394     pixserv *px = pixserv_create(STDIN_FILENO, STDOUT_FILENO);
1395     sel_rmtimer(&px->timer);
1396     px->f |= px_stdin;
1397   }
1398
1399   /* --- Fork into the background if requested --- */
1400
1401   if (f & f_daemon) {    
1402     pid_t kid;
1403
1404     if (((f & f_stdin) &&
1405          (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))) ||
1406         (!command && (flags & F_FETCH)))
1407       die(1, "can't become a daemon if terminal required");
1408
1409     if ((kid = fork()) < 0)
1410       die(1, "fork failed: %s", strerror(errno));
1411     if (kid)
1412       _exit(0);
1413 #ifdef TIOCNOTTY
1414     {
1415       int fd;
1416       if ((fd = open("/dev/tty", O_RDONLY)) >= 0) {
1417         ioctl(fd, TIOCNOTTY);
1418         close(fd);
1419       }
1420     }
1421 #endif
1422     chdir("/");
1423     setsid();
1424
1425     if (fork() > 0)
1426       _exit(0);
1427   }
1428
1429   if (verbose)
1430     log("initialized ok");
1431   for (;;)
1432     sel_select(&sel);
1433   return (0);
1434 }
1435
1436 /*----- That's all, folks -------------------------------------------------*/