9d2e2c65 |
1 | /* -*-c-*- |
2 | * |
3 | * $Id: pixie.c,v 1.1 1999/10/23 10:58:49 mdw Exp $ |
4 | * |
5 | * New, improved PGP pixie for auto-pgp |
6 | * |
7 | * (c) 1999 Mark Wooding |
8 | */ |
9 | |
10 | /*----- Licensing notice --------------------------------------------------* |
11 | * |
12 | * PGP pixie is free software; you can redistribute it and/or modify |
13 | * it under the terms of the GNU General Public License as published by |
14 | * the Free Software Foundation; either version 2 of the License, or |
15 | * (at your option) any later version. |
16 | * |
17 | * PGP pixie 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 General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU General Public License |
23 | * along with PGP pixie; if not, write to the Free Software Foundation, |
24 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
25 | */ |
26 | |
27 | /*----- Revision history --------------------------------------------------* |
28 | * |
29 | * $Log: pixie.c,v $ |
30 | * Revision 1.1 1999/10/23 10:58:49 mdw |
31 | * Initial revision |
32 | * |
33 | */ |
34 | |
35 | /*----- Header files ------------------------------------------------------*/ |
36 | |
37 | #include <errno.h> |
38 | #include <signal.h> |
39 | #include <stdarg.h> |
40 | #include <stddef.h> |
41 | #include <stdio.h> |
42 | #include <stdlib.h> |
43 | #include <string.h> |
44 | #include <time.h> |
45 | |
46 | #include <sys/types.h> |
47 | #include <sys/time.h> |
48 | #include <unistd.h> |
49 | #include <sys/stat.h> |
50 | #include <sys/wait.h> |
51 | #include <pwd.h> |
52 | #include <fcntl.h> |
53 | #include <termios.h> |
54 | |
55 | #ifdef HAVE_MLOCK |
56 | # include <sys/mman.h> |
57 | #endif |
58 | |
59 | #include <sys/socket.h> |
60 | #include <sys/un.h> |
61 | |
62 | #include "mdwopt.h" |
63 | |
64 | /*----- Magic constants ---------------------------------------------------*/ |
65 | |
66 | #define PIXIE_BUFSZ 1024 /* Passphrase buffer size */ |
67 | #define PIXIE_TIMEOUT 300 /* Default timeout (in seconds) */ |
68 | |
69 | #define PIXIE_SOCKET "pass-socket" |
70 | |
71 | /*----- Static variables --------------------------------------------------*/ |
72 | |
73 | static char *pass; |
74 | static size_t passlen = 0; |
75 | static int sigfd_out; |
76 | static unsigned flags; |
77 | |
78 | enum { |
79 | f_pass = 1u, |
80 | f_xgetline = 2u, |
81 | f_getpass = 4u, |
82 | f_goodbuf = 8u, |
83 | f_bogus = 128u |
84 | }; |
85 | |
86 | /*----- Library code ------------------------------------------------------*/ |
87 | |
88 | const char *pn__name = "<UNNAMED>"; /* Program name */ |
89 | #define QUIS pn__name |
90 | |
91 | /* --- @quis@ --- * |
92 | * |
93 | * Arguments: --- |
94 | * |
95 | * Returns: Pointer to the program name. |
96 | * |
97 | * Use: Returns the program name. |
98 | */ |
99 | |
100 | const char *quis(void) { return (QUIS); } |
101 | |
102 | /* --- @ego@ --- * |
103 | * |
104 | * Arguments: @const char *p@ = pointer to program name |
105 | * |
106 | * Returns: --- |
107 | * |
108 | * Use: Tells mLib what the program's name is. |
109 | */ |
110 | |
111 | #ifndef PATHSEP |
112 | # if defined(__riscos) |
113 | # define PATHSEP '.' |
114 | # elif defined(__unix) || defined(unix) |
115 | # define PATHSEP '/' |
116 | # else |
117 | # define PATHSEP '\\' |
118 | # endif |
119 | #endif |
120 | |
121 | void ego(const char *p) |
122 | { |
123 | const char *q = p; |
124 | while (*q) { |
125 | if (*q++ == PATHSEP) |
126 | p = q; |
127 | } |
128 | if (*p == '-') |
129 | p++; |
130 | pn__name = p; |
131 | } |
132 | |
133 | #undef PATHSEP |
134 | |
135 | /* --- @pquis@ --- * |
136 | * |
137 | * Arguments: @FILE *fp@ = output stream to write on |
138 | * @const char *p@ = pointer to string to write |
139 | * |
140 | * Returns: Zero if everything worked, EOF if not. |
141 | * |
142 | * Use: Writes the string @p@ to the output stream @fp@. Occurrences |
143 | * of the character `$' in @p@ are replaced by the program name |
144 | * as reported by @quis@. A `$$' is replaced by a single `$' |
145 | * sign. |
146 | */ |
147 | |
148 | int pquis(FILE *fp, const char *p) |
149 | { |
150 | size_t sz; |
151 | |
152 | while (*p) { |
153 | sz = strcspn(p, "$"); |
154 | if (sz) { |
155 | if (fwrite(p, 1, sz, fp) < sz) |
156 | return (EOF); |
157 | p += sz; |
158 | } |
159 | if (*p == '$') { |
160 | p++; |
161 | if (*p == '$') { |
162 | if (fputc('$', fp) == EOF) |
163 | return (EOF); |
164 | p++; |
165 | } else { |
166 | if (fputs(pn__name, fp) == EOF) |
167 | return (EOF); |
168 | } |
169 | } |
170 | } |
171 | return (0); |
172 | } |
173 | |
174 | /* --- @die@ --- * |
175 | * |
176 | * Arguments: @int status@ = exit status to return |
177 | * @const char *f@ = a @printf@-style format string |
178 | * @...@ = other arguments |
179 | * |
180 | * Returns: Never. |
181 | * |
182 | * Use: Reports an error and exits. Like @moan@ above, only more |
183 | * permanent. |
184 | */ |
185 | |
186 | void die(int status, const char *f, ...) |
187 | { |
188 | va_list ap; |
189 | va_start(ap, f); |
190 | fprintf(stderr, "%s: ", QUIS); |
191 | vfprintf(stderr, f, ap); |
192 | va_end(ap); |
193 | putc('\n', stderr); |
194 | exit(status); |
195 | } |
196 | |
197 | /* --- @fdflags@ --- * |
198 | * |
199 | * Arguments: @int fd@ = file descriptor to fiddle with |
200 | * @unsigned fbic, fxor@ = file flags to set and clear |
201 | * @unsigned fdbic, fdxor@ = descriptor flags to set and clear |
202 | * |
203 | * Returns: Zero if successful, @-1@ if not. |
204 | * |
205 | * Use: Sets file descriptor flags in what is, I hope, an obvious |
206 | * way. |
207 | */ |
208 | |
209 | int fdflags(int fd, unsigned fbic, unsigned fxor, |
210 | unsigned fdbic, unsigned fdxor) |
211 | { |
212 | int f; |
213 | |
214 | if ((f = fcntl(fd, F_GETFL)) == -1 || |
215 | fcntl(fd, F_SETFL, (f & ~fbic) ^ fxor) == -1 || |
216 | (f = fcntl(fd, F_GETFD)) == -1 || |
217 | fcntl(fd, F_SETFD, (f & ~fdbic) ^ fdxor) == -1) |
218 | return (-1); |
219 | return (0); |
220 | } |
221 | |
222 | /* --- Timeval manipulation macros --- */ |
223 | |
224 | #define MILLION 1000000 |
225 | |
226 | #define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec) |
227 | |
228 | #define TV_ADDL(dst, a, sec, usec) do { \ |
229 | (dst)->tv_sec = (a)->tv_sec + (sec); \ |
230 | (dst)->tv_usec = (a)->tv_usec + (usec); \ |
231 | if ((dst)->tv_usec >= MILLION) { \ |
232 | (dst)->tv_usec -= MILLION; \ |
233 | (dst)->tv_sec++; \ |
234 | } \ |
235 | } while (0) |
236 | |
237 | #define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec) |
238 | |
239 | #define TV_SUBL(dst, a, sec, usec) do { \ |
240 | (dst)->tv_sec = (a)->tv_sec - (sec); \ |
241 | if ((a)->tv_usec >= (usec)) \ |
242 | (dst)->tv_usec = (a)->tv_usec - (usec); \ |
243 | else { \ |
244 | (dst)->tv_usec = (a)->tv_usec + MILLION - (usec); \ |
245 | (dst)->tv_sec--; \ |
246 | } \ |
247 | } while (0) |
248 | |
249 | #define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ? \ |
250 | (a)->tv_usec op (b)->tv_usec : \ |
251 | (a)->tv_sec op (b)->tv_sec) |
252 | |
253 | /*----- Main code ---------------------------------------------------------*/ |
254 | |
255 | /* --- @log@ --- * |
256 | * |
257 | * Arguments: @const char *p@ = @printf@-style format string |
258 | * @...@ = extra arguments to fill in |
259 | * |
260 | * Returns: --- |
261 | * |
262 | * Use: Writes out a timestamped log message. |
263 | */ |
264 | |
265 | static void log(const char *p, ...) |
266 | { |
267 | char b[32]; |
268 | va_list ap; |
269 | time_t t = time(0); |
270 | struct tm *tm = localtime(&t); |
271 | |
272 | strftime(b, sizeof(b), "%Y-%m-%d %H:%M:%S", tm); |
273 | fprintf(stderr, "%s: %s ", QUIS, b); |
274 | va_start(ap, p); |
275 | vfprintf(stderr, p, ap); |
276 | va_end(ap); |
277 | fputc('\n', stderr); |
278 | } |
279 | |
280 | /* --- @sigwrite@ --- * |
281 | * |
282 | * Arguments: @int sig@ = signal number |
283 | * |
284 | * Returns: --- |
285 | * |
286 | * Use: Handles signals. It writes the signal number to a pipe and |
287 | * exits. It's possible for signals to be lost if the pipe is |
288 | * full. This isn't likely enough to be worth caring about. |
289 | * The implementation in mLib's `sig.c' does the job right but |
290 | * it's rather more effort. |
291 | */ |
292 | |
293 | static void sigwrite(int sig) |
294 | { |
295 | int e = errno; |
296 | char c = sig; |
297 | write(sigfd_out, &c, 1); |
298 | errno = e; |
299 | } |
300 | |
301 | /* --- @readpass@ --- * |
302 | * |
303 | * Arguments: @int fd@ = file descriptor to read from |
304 | * |
305 | * Returns: 0 if OK, -1 if not. |
306 | * |
307 | * Use: Reads a line from a file descriptor. It continues reading |
308 | * buffers until it gets a newline character. If the buffer |
309 | * becomes full, a newline is inserted and no more data is |
310 | * read. This might cause confusion. |
311 | */ |
312 | |
313 | static int readpass(int fd) |
314 | { |
315 | int r; |
316 | char *p = pass; |
317 | char *q; |
318 | size_t sz = PIXIE_BUFSZ; |
319 | |
320 | for (;;) { |
321 | r = read(fd, p, sz); |
322 | if (r < 0) |
323 | return (-1); |
324 | if (r == 0) { |
325 | q = p + r; |
326 | break; |
327 | } |
328 | if ((q = memchr(p, '\n', r)) != 0) { |
329 | q++; |
330 | break; |
331 | } |
332 | sz -= r; |
333 | p += r; |
334 | if (!sz) { |
335 | q = p; |
336 | p[-1] = '\n'; |
337 | break; |
338 | } |
339 | } |
340 | |
341 | passlen = q - pass; |
342 | return (0); |
343 | } |
344 | |
345 | /* --- @get_pass@ --- * |
346 | * |
347 | * Arguments: --- |
348 | * |
349 | * Returns: 0 if OK, -1 if it failed. |
350 | * |
351 | * Use: Reads a passphrase from somewhere. The data from the |
352 | * passphrase goes straight into the @pass@ buffer without |
353 | * touching any other memory. Of course, if @xgetline@ is used, |
354 | * it might end up in unprotected memory there. That's a shame, |
355 | * but @xgetline@ is a much shorter-lived process than this one |
356 | * so it shouldn't matter as much. |
357 | */ |
358 | |
359 | static int get_pass(void) |
360 | { |
361 | #ifdef PATH_XGETLINE |
362 | if (flags & f_xgetline) { |
363 | int fd[2]; |
364 | pid_t kid; |
365 | int r; |
366 | |
367 | /* --- Do everything by hand --- * |
368 | * |
369 | * I could, I suppose, use @popen@. However, (a) that involves a shell |
370 | * which is extra overhead and makes passing arguments with spaces a |
371 | * little trickier; and (b) it uses @stdio@ buffers, which might get |
372 | * swapped to disk. |
373 | */ |
374 | |
375 | if (pipe(fd)) |
376 | return (-1); |
377 | kid = fork(); |
378 | if (kid < 0) |
379 | return (-1); |
380 | if (kid == 0) { |
381 | dup2(fd[1], STDOUT_FILENO); |
382 | close(fd[0]); |
383 | close(fd[1]); |
384 | execlp(PATH_XGETLINE, "xgetline", |
385 | "-i", "-tPGP pixie", "-pPGP passphrase:", (char *)0); |
386 | _exit(127); |
387 | } |
388 | close(fd[1]); |
389 | r = readpass(fd[0]); |
390 | close(fd[0]); |
391 | waitpid(kid, 0, 0); |
392 | return (r); |
393 | } else |
394 | #endif |
395 | { |
396 | struct termios o, n; |
397 | int fd; |
398 | int r; |
399 | char prompt[] = "PGP passphrase: "; |
400 | char nl = '\n'; |
401 | |
402 | /* --- Do this by hand --- * |
403 | * |
404 | * I could use @getpass@, but that puts the passphrase in its own memory |
405 | * rather than mine, so I'd have to scrub it out manually. This is |
406 | * probably just as good if you don't mind fiddling with @termios@. |
407 | * Also, the GNU version uses @stdio@ streams to read from the terminal, |
408 | * which might be considered a Bad Thing. |
409 | */ |
410 | |
411 | if ((fd = open("/dev/tty", O_RDWR)) < 0) |
412 | return (-1); |
413 | if (tcgetattr(fd, &o)) |
414 | return (-1); |
415 | n = o; |
416 | n.c_lflag &= ~(ECHO | ISIG); |
417 | if (tcsetattr(fd, TCSAFLUSH, &n)) |
418 | return (-1); |
419 | write(fd, prompt, sizeof(prompt) - 1); |
420 | r = readpass(fd); |
421 | tcsetattr(fd, TCSAFLUSH, &o); |
422 | write(fd, &nl, 1); |
423 | close(fd); |
424 | return (r); |
425 | } |
426 | } |
427 | |
428 | /* --- @help@, @version@ @usage@ --- * |
429 | * |
430 | * Arguments: @FILE *fp@ = stream to write on |
431 | * |
432 | * Returns: --- |
433 | * |
434 | * Use: Emit helpful messages. |
435 | */ |
436 | |
437 | static void usage(FILE *fp) |
438 | { |
439 | pquis(fp, "Usage: $ [-xqv] [-t timeout] [-d dir] [socket]\n"); |
440 | } |
441 | |
442 | static void version(FILE *fp) |
443 | { |
444 | pquis(fp, "$ version " VERSION "\n"); |
445 | } |
446 | |
447 | static void help(FILE *fp) |
448 | { |
449 | version(fp); |
450 | fputc('\n', fp); |
451 | usage(fp); |
452 | pquis(fp, "\n\ |
453 | The passphrase pixie remembers a PGP passphrase and passes it on to\n\ |
454 | clients which connect to a Unix-domain socket.\n\ |
455 | \n\ |
456 | The pixie will forget a passphrase after a certain amount of time. The\n\ |
457 | duration of the pixie's memory is configurable using the `-t' option, and\n\ |
458 | the default is 5 minutes. By giving a timeout of zero, the pixie can be\n\ |
459 | endowed with a perfect memory.\n\ |
460 | \n\ |
461 | The pixie attempts to lock its passphrase buffer into physical memory. If\n\ |
462 | this doesn't work (e.g., your operating system doesn't support this\n\ |
463 | feature, or you have insufficient privilege) a warning is emitted.\n\ |
464 | \n\ |
465 | Options available are:\n\ |
466 | \n\ |
467 | -h, --help Show this help text.\n\ |
468 | -V, --version Show the pixie's version number.\n\ |
469 | -u, --usage Show a uselessly terse usage message.\n\ |
470 | \n\ |
471 | " |
472 | #ifdef PATH_XGETLINE |
473 | "\ |
474 | -x, --x11 Run `xgetline' to read a passphrase.\n\ |
475 | +x, --no-x11 Don't run `xgetline' to read a passphrase.\n\ |
476 | " |
477 | #endif |
478 | "\ |
479 | -d, --directory=DIR Make secure directory DIR and change to it.\n\ |
480 | -q, --quiet Don't emit so many messages.\n\ |
481 | -v, --verbose Emit more messages.\n\ |
482 | "); |
483 | } |
484 | |
485 | /* --- @main@ --- * |
486 | * |
487 | * Arguments: @int argc@ = number of arguments |
488 | * @char *argv[]@ = vector of argument values |
489 | * |
490 | * Returns: Zero if OK. |
491 | * |
492 | * Use: Main program. Listens on a socket and responds with a PGP |
493 | * passphrase when asked. |
494 | */ |
495 | |
496 | int main(int argc, char *argv[]) |
497 | { |
498 | char *dir = 0; |
499 | int fd; |
500 | int sigfd_in; |
501 | unsigned verbose = 1; |
502 | char *sock = 0; |
503 | unsigned long timeout = PIXIE_TIMEOUT; |
504 | char *emsg = 0; |
505 | int elock = 0; |
506 | |
507 | ego(argv[0]); |
508 | |
509 | /* --- Try making a secure locked passphrase buffer --- * |
510 | * |
511 | * Drop privileges before emitting diagnostic messages. |
512 | */ |
513 | |
514 | #ifdef HAVE_MLOCK |
515 | |
516 | /* --- Memory-map a page from somewhere --- */ |
517 | |
518 | # ifdef MAP_ANON |
519 | |
520 | pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE, |
521 | MAP_PRIVATE | MAP_ANON, -1, 0); |
522 | |
523 | # else |
524 | |
525 | if ((fd = open("/dev/zero", O_RDWR)) < 0) { |
526 | emsg = "couldn't open `/dev/zero': %s"; |
527 | elock = errno; |
528 | } else { |
529 | pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE, |
530 | MAP_PRIVATE, fd, 0); |
531 | close(fd); |
532 | } |
533 | |
534 | # endif |
535 | |
536 | /* --- Lock the page in memory --- * |
537 | * |
538 | * Why does @mmap@ return such a stupid result if it fails? |
539 | */ |
540 | |
541 | if (pass == 0 || pass == MAP_FAILED) { |
542 | emsg = "couldn't map a passphrase buffer: %s"; |
543 | elock = errno; |
544 | pass = 0; |
545 | } else if (mlock(pass, PIXIE_BUFSZ)) { |
546 | emsg = "couldn't lock passphrase buffer: %s"; |
547 | elock = errno; |
548 | munmap(pass, PIXIE_BUFSZ); |
549 | pass = 0; |
550 | } else |
551 | flags |= f_goodbuf; |
552 | |
553 | #endif |
554 | |
555 | /* --- Make a standard passphrase buffer --- */ |
556 | |
557 | setuid(getuid()); |
558 | |
559 | #ifdef HAVE_MLOCK |
560 | if (!pass) |
561 | #endif |
562 | { |
563 | if ((pass = malloc(PIXIE_BUFSZ)) == 0) |
564 | die(1, "not enough memory for passphrase buffer"); |
565 | } |
566 | |
567 | /* --- Parse options --- */ |
568 | |
569 | for (;;) { |
570 | static struct option opts[] = { |
571 | |
572 | /* --- GNUey help options --- */ |
573 | |
574 | { "help", 0, 0, 'h' }, |
575 | { "usage", 0, 0, 'u' }, |
576 | { "version", 0, 0, 'V' }, |
577 | |
578 | /* --- Other options --- */ |
579 | |
580 | { "timeout", OPTF_ARGREQ, 0, 't' }, |
581 | #ifdef PATH_XGETLINE |
582 | { "xgetline", OPTF_NEGATE, 0, 'x' }, |
583 | { "x11", OPTF_NEGATE, 0, 'x' }, |
584 | #endif |
585 | { "directory", OPTF_ARGREQ, 0, 'd' }, |
586 | { "quiet", 0, 0, 'q' }, |
587 | { "verbose", 0, 0, 'v' }, |
588 | |
589 | /* --- Magic end marker --- */ |
590 | |
591 | { 0, 0, 0, 0 } |
592 | }; |
593 | |
594 | #ifdef PATH_XGETLINE |
595 | # define XOPTS "x+" |
596 | #else |
597 | # define XOPTS |
598 | #endif |
599 | |
600 | int i = mdwopt(argc, argv, "huV" XOPTS "t:d:qv", |
601 | opts, 0, 0, OPTF_NEGATION); |
602 | |
603 | #undef XOPTS |
604 | |
605 | if (i < 0) |
606 | break; |
607 | switch (i) { |
608 | case 'h': |
609 | help(stdout); |
610 | exit(0); |
611 | case 'V': |
612 | version(stdout); |
613 | exit(0); |
614 | case 'u': |
615 | usage(stdout); |
616 | exit(0); |
617 | case 't': { |
618 | char *p; |
619 | timeout = strtoul(optarg, &p, 0); |
620 | switch (*p) { |
621 | case 'd': timeout *= 24; |
622 | case 'h': timeout *= 60; |
623 | case 'm': timeout *= 60; |
624 | case 's': case 0: break; |
625 | default: |
626 | die(1, "unrecognized suffix character `%c'", *p); |
627 | break; |
628 | } |
629 | } break; |
630 | #ifdef PATH_XGETLINE |
631 | case 'x': |
632 | flags |= f_xgetline; |
633 | flags &= ~f_getpass; |
634 | break; |
635 | case 'x' | OPTF_NEGATED: |
636 | flags |= f_getpass; |
637 | flags &= ~f_xgetline; |
638 | break; |
639 | #endif |
640 | case 'd': |
641 | dir = optarg; |
642 | break; |
643 | case 'q': |
644 | if (verbose > 0) |
645 | verbose--; |
646 | break; |
647 | case 'v': |
648 | verbose++; |
649 | break; |
650 | default: |
651 | flags |= f_bogus; |
652 | break; |
653 | } |
654 | } |
655 | |
656 | if (optind < argc) |
657 | sock = argv[optind++]; |
658 | |
659 | if (optind < argc) |
660 | flags |= f_bogus; |
661 | |
662 | if (flags & f_bogus) { |
663 | usage(stderr); |
664 | exit(1); |
665 | } |
666 | |
667 | /* --- Sort out how to request the passphrase --- */ |
668 | |
669 | #ifdef PATH_XGETLINE |
670 | if ((flags & (f_xgetline | f_getpass)) == 0) { |
671 | if (isatty(STDIN_FILENO)) |
672 | flags |= f_getpass; |
673 | else |
674 | flags |= f_xgetline; |
675 | } |
676 | #endif |
677 | |
678 | /* --- Make the socket directory --- * |
679 | * |
680 | * Be very paranoid about the directory. Very paranoid indeed. |
681 | */ |
682 | |
683 | if (dir) { |
684 | struct stat st; |
685 | |
686 | if (chdir(dir)) { |
687 | if (errno != ENOENT) { |
688 | die(1, "couldn't change directory to `%s': %s", |
689 | dir, strerror(errno)); |
690 | } |
691 | if (mkdir(dir, 0700)) |
692 | die(1, "couldn't create directory `%s': %s", dir, strerror(errno)); |
693 | if (chdir(dir)) { |
694 | die(1, "couldn't change directory to `%s': %s", |
695 | dir, strerror(errno)); |
696 | } |
697 | if (verbose > 1) |
698 | log("created directory `%s'", dir); |
699 | } |
700 | |
701 | if (stat(".", &st)) |
702 | die(1, "couldn't stat directory `%s': %s", dir, strerror(errno)); |
703 | if ((st.st_mode & 07777) != 0700) { |
704 | die(1, "directory `%s' has mode %04o; should be 0700", |
705 | dir, st.st_mode & 07777); |
706 | } |
707 | if (st.st_uid != getuid()) { |
708 | struct passwd *pw = getpwuid(st.st_uid); |
709 | char b[16]; |
710 | char *p; |
711 | |
712 | if (pw) |
713 | p = pw->pw_name; |
714 | else { |
715 | sprintf(b, "uid `%i'", st.st_uid); |
716 | p = b; |
717 | } |
718 | die(1, "directory `%s' owned by %s; should be you", dir, p); |
719 | } |
720 | |
721 | if (verbose > 2) |
722 | log("directory `%s' checked out OK", dir); |
723 | } |
724 | |
725 | /* --- A little argument checking --- */ |
726 | |
727 | if (!sock) { |
728 | if (dir) |
729 | sock = PIXIE_SOCKET; |
730 | else |
731 | die(1, "no socket filename given"); |
732 | } |
733 | |
734 | /* --- Create and bind the socket --- */ |
735 | |
736 | { |
737 | size_t len = strlen(sock) + 1; |
738 | size_t sz = offsetof(struct sockaddr_un, sun_path) + len; |
739 | struct sockaddr_un *sun = malloc(sz); |
740 | unsigned u = umask(077); |
741 | |
742 | /* --- Create the file descriptor --- */ |
743 | |
744 | if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) |
745 | die(1, "couldn't create socket: %s", strerror(errno)); |
746 | if (fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC)) |
747 | die(1, "couldn't configure socket: %s", strerror(errno)); |
748 | |
749 | /* --- Set up the address --- */ |
750 | |
751 | memset(sun, 0, sz); |
752 | sun->sun_family = AF_UNIX; |
753 | strcpy(sun->sun_path, sock); |
754 | |
755 | /* --- Bind to the address --- */ |
756 | |
757 | if (bind(fd, (struct sockaddr *)sun, sz)) |
758 | die(1, "couldn't bind to socket `%s': %s", sock, strerror(errno)); |
759 | free(sun); |
760 | if (listen(fd, 5)) |
761 | die(1, "couldn't listen on socket: %s", strerror(errno)); |
762 | umask(u); |
763 | } |
764 | |
765 | /* --- Set signals up --- * |
766 | * |
767 | * I'm using Dan Bernstein's self-pipe trick to catch signals in the main |
768 | * code. See http://pobox.com/~djb/docs/selfpipe.html |
769 | */ |
770 | |
771 | { |
772 | static int sig[] = { SIGINT, SIGTERM, SIGHUP, SIGQUIT, 0 }; |
773 | struct sigaction sa; |
774 | int i; |
775 | int pfd[2]; |
776 | |
777 | /* --- Create the signal pipe --- */ |
778 | |
779 | if (pipe(pfd)) |
780 | die(1, "couldn't create pipe: %s", strerror(errno)); |
781 | |
782 | if (fdflags(pfd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) || |
783 | fdflags(pfd[1], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC)) |
784 | die(1, "couldn't configure pipe attributes: %s", strerror(errno)); |
785 | |
786 | sigfd_in = pfd[0]; |
787 | sigfd_out = pfd[1]; |
788 | |
789 | /* --- Set up the signal handlers --- */ |
790 | |
791 | sa.sa_handler = sigwrite; |
792 | sa.sa_flags = 0; |
793 | #ifdef SA_RESTART |
794 | sa.sa_flags |= SA_RESTART; |
795 | #endif |
796 | sigemptyset(&sa.sa_mask); |
797 | |
798 | for (i = 0; sig[i]; i++) { |
799 | struct sigaction osa; |
800 | if (sigaction(sig[i], 0, &osa) == 0 && |
801 | osa.sa_handler != SIG_IGN) |
802 | sigaction(sig[i], &sa, 0); |
803 | } |
804 | } |
805 | |
806 | /* --- Now listen, and wait --- */ |
807 | |
808 | { |
809 | int maxfd; |
810 | fd_set fds; |
811 | struct timeval tv, now, when, *tvp; |
812 | |
813 | if (fd > sigfd_in) |
814 | maxfd = fd + 1; |
815 | else |
816 | maxfd = sigfd_in + 1; |
817 | |
818 | if (flags & f_goodbuf) { |
819 | if (verbose > 1) |
820 | log("passphrase buffer created and locked OK"); |
821 | } else { |
822 | if (emsg && verbose > 1) |
823 | log(emsg, strerror(elock)); |
824 | else if (verbose) |
825 | log("couldn't create locked passphrase buffer"); |
826 | } |
827 | |
828 | if (verbose > 1) |
829 | log("passphrase pixie initialized OK"); |
830 | |
831 | for (;;) { |
832 | |
833 | /* --- Set up the file descriptors --- */ |
834 | |
835 | FD_ZERO(&fds); |
836 | FD_SET(fd, &fds); |
837 | FD_SET(sigfd_in, &fds); |
838 | |
839 | /* --- Set up the timeout --- */ |
840 | |
841 | if (!timeout || !(flags & f_pass)) |
842 | tvp = 0; |
843 | else { |
844 | gettimeofday(&now, 0); |
845 | TV_SUB(&tv, &when, &now); |
846 | tvp = &tv; |
847 | } |
848 | |
849 | /* --- Wait for something interesting to happen --- */ |
850 | |
851 | if (select(maxfd, &fds, 0, 0, tvp) < 0) { |
852 | if (errno == EINTR) |
853 | continue; |
854 | die(1, "error from select: %s", strerror(errno)); |
855 | } |
856 | |
857 | /* --- Act on a signal --- */ |
858 | |
859 | if (FD_ISSET(sigfd_in, &fds)) { |
860 | char buf[256]; |
861 | int r; |
862 | sigset_t ss; |
863 | |
864 | /* --- Go through each signal in turn --- * |
865 | * |
866 | * Don't try to respond to duplicates. |
867 | */ |
868 | |
869 | sigemptyset(&ss); |
870 | while ((r = read(sigfd_in, buf, sizeof(buf))) > 0) { |
871 | char *p = buf; |
872 | |
873 | /* --- A buffer of signals has arrived; grind through it --- */ |
874 | |
875 | for (p = buf; r; r--, p++) { |
876 | |
877 | /* --- If this signal has been seen, skip on to the next --- */ |
878 | |
879 | if (sigismember(&ss, *p)) |
880 | continue; |
881 | sigaddset(&ss, *p); |
882 | |
883 | switch (*p) { |
884 | const char *s; |
885 | |
886 | /* --- Various interesting signals --- */ |
887 | |
888 | case SIGINT: |
889 | s = "SIGINT"; |
890 | goto closedown; |
891 | case SIGTERM: |
892 | s = "SIGTERM"; |
893 | goto closedown; |
894 | case SIGHUP: |
895 | s = "SIGHUP"; |
896 | goto clear; |
897 | case SIGQUIT: |
898 | s = "SIGQUIT"; |
899 | goto clear; |
900 | |
901 | /* --- Shut down the program if requested --- */ |
902 | |
903 | closedown: |
904 | if (verbose > 1) |
905 | log("closing down on %s", s); |
906 | goto done; |
907 | |
908 | /* --- Clear the passphrase if requested --- */ |
909 | |
910 | clear: |
911 | if (flags & f_pass) { |
912 | memset(pass, 0, PIXIE_BUFSZ); |
913 | passlen = 0; |
914 | flags &= ~f_pass; |
915 | if (verbose) |
916 | log("caught %s: passphrase cleared", s); |
917 | } else if (verbose > 1) |
918 | log("caught %s: passphrase not set", s); |
919 | break; |
920 | |
921 | /* --- Other signals which aren't so interesting --- */ |
922 | |
923 | default: |
924 | if (verbose > 2) |
925 | log("caught unexpected signal %i: ignoring it", *p); |
926 | break; |
927 | } |
928 | } |
929 | } |
930 | } |
931 | |
932 | /* --- Act on a passphrase timeout --- */ |
933 | |
934 | if (timeout && (flags & f_pass)) { |
935 | gettimeofday(&now, 0); |
936 | if (TV_CMP(&now, >, &when)) { |
937 | memset(pass, 0, PIXIE_BUFSZ); |
938 | passlen = 0; |
939 | flags &= ~f_pass; |
940 | if (verbose > 1) |
941 | log("passphrase timed out"); |
942 | } |
943 | } |
944 | |
945 | /* --- Act on a new connection --- */ |
946 | |
947 | if (FD_ISSET(fd, &fds)) { |
948 | int nfd; |
949 | |
950 | { |
951 | struct sockaddr_un sun; |
952 | int sunsz = sizeof(sun); |
953 | |
954 | if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) { |
955 | if (verbose > 1) |
956 | log("accept failed: %s", strerror(errno)); |
957 | goto fail_0; |
958 | } |
959 | } |
960 | |
961 | if (!(flags & f_pass)) { |
962 | if (get_pass()) { |
963 | if (verbose) |
964 | log("couldn't get passphrase: %s", strerror(errno)); |
965 | goto fail_1; |
966 | } |
967 | flags |= f_pass; |
968 | if (timeout) { |
969 | gettimeofday(&when, 0); |
970 | when.tv_sec += timeout; |
971 | } |
972 | } |
973 | write(nfd, pass, passlen); |
974 | if (verbose) |
975 | log("responded to passphrase request"); |
976 | fail_1: |
977 | close(nfd); |
978 | fail_0: |
979 | ; |
980 | } |
981 | } |
982 | } |
983 | |
984 | done: |
985 | memset(pass, 0, PIXIE_BUFSZ); |
986 | passlen = 0; |
987 | unlink(sock); |
988 | return (0); |
989 | } |
990 | |
991 | /*----- That's all, folks -------------------------------------------------*/ |