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