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