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