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