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