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