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