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