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