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