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