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