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