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