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