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