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