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