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