9d2e2c65 |
1 | /* -*-c-*- |
2 | * |
cfd10afa |
3 | * $Id: pixie.c,v 1.2 2000/07/07 18:33:16 mdw Exp $ |
9d2e2c65 |
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 $ |
cfd10afa |
30 | * Revision 1.2 2000/07/07 18:33:16 mdw |
31 | * Fix reading of timeouts |
32 | * |
33 | * Revision 1.1.1.1 1999/10/23 10:58:49 mdw |
34 | * New import. |
9d2e2c65 |
35 | * |
36 | */ |
37 | |
38 | /*----- Header files ------------------------------------------------------*/ |
39 | |
40 | #include <errno.h> |
41 | #include <signal.h> |
42 | #include <stdarg.h> |
43 | #include <stddef.h> |
44 | #include <stdio.h> |
45 | #include <stdlib.h> |
46 | #include <string.h> |
47 | #include <time.h> |
48 | |
49 | #include <sys/types.h> |
50 | #include <sys/time.h> |
51 | #include <unistd.h> |
52 | #include <sys/stat.h> |
53 | #include <sys/wait.h> |
54 | #include <pwd.h> |
55 | #include <fcntl.h> |
56 | #include <termios.h> |
57 | |
58 | #ifdef HAVE_MLOCK |
59 | # include <sys/mman.h> |
60 | #endif |
61 | |
62 | #include <sys/socket.h> |
63 | #include <sys/un.h> |
64 | |
65 | #include "mdwopt.h" |
66 | |
67 | /*----- Magic constants ---------------------------------------------------*/ |
68 | |
69 | #define PIXIE_BUFSZ 1024 /* Passphrase buffer size */ |
70 | #define PIXIE_TIMEOUT 300 /* Default timeout (in seconds) */ |
71 | |
72 | #define PIXIE_SOCKET "pass-socket" |
73 | |
74 | /*----- Static variables --------------------------------------------------*/ |
75 | |
76 | static char *pass; |
77 | static size_t passlen = 0; |
78 | static int sigfd_out; |
79 | static unsigned flags; |
80 | |
81 | enum { |
82 | f_pass = 1u, |
83 | f_xgetline = 2u, |
84 | f_getpass = 4u, |
85 | f_goodbuf = 8u, |
86 | f_bogus = 128u |
87 | }; |
88 | |
89 | /*----- Library code ------------------------------------------------------*/ |
90 | |
91 | const char *pn__name = "<UNNAMED>"; /* Program name */ |
92 | #define QUIS pn__name |
93 | |
94 | /* --- @quis@ --- * |
95 | * |
96 | * Arguments: --- |
97 | * |
98 | * Returns: Pointer to the program name. |
99 | * |
100 | * Use: Returns the program name. |
101 | */ |
102 | |
103 | const char *quis(void) { return (QUIS); } |
104 | |
105 | /* --- @ego@ --- * |
106 | * |
107 | * Arguments: @const char *p@ = pointer to program name |
108 | * |
109 | * Returns: --- |
110 | * |
111 | * Use: Tells mLib what the program's name is. |
112 | */ |
113 | |
114 | #ifndef PATHSEP |
115 | # if defined(__riscos) |
116 | # define PATHSEP '.' |
117 | # elif defined(__unix) || defined(unix) |
118 | # define PATHSEP '/' |
119 | # else |
120 | # define PATHSEP '\\' |
121 | # endif |
122 | #endif |
123 | |
124 | void ego(const char *p) |
125 | { |
126 | const char *q = p; |
127 | while (*q) { |
128 | if (*q++ == PATHSEP) |
129 | p = q; |
130 | } |
131 | if (*p == '-') |
132 | p++; |
133 | pn__name = p; |
134 | } |
135 | |
136 | #undef PATHSEP |
137 | |
138 | /* --- @pquis@ --- * |
139 | * |
140 | * Arguments: @FILE *fp@ = output stream to write on |
141 | * @const char *p@ = pointer to string to write |
142 | * |
143 | * Returns: Zero if everything worked, EOF if not. |
144 | * |
145 | * Use: Writes the string @p@ to the output stream @fp@. Occurrences |
146 | * of the character `$' in @p@ are replaced by the program name |
147 | * as reported by @quis@. A `$$' is replaced by a single `$' |
148 | * sign. |
149 | */ |
150 | |
151 | int pquis(FILE *fp, const char *p) |
152 | { |
153 | size_t sz; |
154 | |
155 | while (*p) { |
156 | sz = strcspn(p, "$"); |
157 | if (sz) { |
158 | if (fwrite(p, 1, sz, fp) < sz) |
159 | return (EOF); |
160 | p += sz; |
161 | } |
162 | if (*p == '$') { |
163 | p++; |
164 | if (*p == '$') { |
165 | if (fputc('$', fp) == EOF) |
166 | return (EOF); |
167 | p++; |
168 | } else { |
169 | if (fputs(pn__name, fp) == EOF) |
170 | return (EOF); |
171 | } |
172 | } |
173 | } |
174 | return (0); |
175 | } |
176 | |
177 | /* --- @die@ --- * |
178 | * |
179 | * Arguments: @int status@ = exit status to return |
180 | * @const char *f@ = a @printf@-style format string |
181 | * @...@ = other arguments |
182 | * |
183 | * Returns: Never. |
184 | * |
185 | * Use: Reports an error and exits. Like @moan@ above, only more |
186 | * permanent. |
187 | */ |
188 | |
189 | void die(int status, const char *f, ...) |
190 | { |
191 | va_list ap; |
192 | va_start(ap, f); |
193 | fprintf(stderr, "%s: ", QUIS); |
194 | vfprintf(stderr, f, ap); |
195 | va_end(ap); |
196 | putc('\n', stderr); |
197 | exit(status); |
198 | } |
199 | |
200 | /* --- @fdflags@ --- * |
201 | * |
202 | * Arguments: @int fd@ = file descriptor to fiddle with |
203 | * @unsigned fbic, fxor@ = file flags to set and clear |
204 | * @unsigned fdbic, fdxor@ = descriptor flags to set and clear |
205 | * |
206 | * Returns: Zero if successful, @-1@ if not. |
207 | * |
208 | * Use: Sets file descriptor flags in what is, I hope, an obvious |
209 | * way. |
210 | */ |
211 | |
212 | int fdflags(int fd, unsigned fbic, unsigned fxor, |
213 | unsigned fdbic, unsigned fdxor) |
214 | { |
215 | int f; |
216 | |
217 | if ((f = fcntl(fd, F_GETFL)) == -1 || |
218 | fcntl(fd, F_SETFL, (f & ~fbic) ^ fxor) == -1 || |
219 | (f = fcntl(fd, F_GETFD)) == -1 || |
220 | fcntl(fd, F_SETFD, (f & ~fdbic) ^ fdxor) == -1) |
221 | return (-1); |
222 | return (0); |
223 | } |
224 | |
225 | /* --- Timeval manipulation macros --- */ |
226 | |
227 | #define MILLION 1000000 |
228 | |
229 | #define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec) |
230 | |
231 | #define TV_ADDL(dst, a, sec, usec) do { \ |
232 | (dst)->tv_sec = (a)->tv_sec + (sec); \ |
233 | (dst)->tv_usec = (a)->tv_usec + (usec); \ |
234 | if ((dst)->tv_usec >= MILLION) { \ |
235 | (dst)->tv_usec -= MILLION; \ |
236 | (dst)->tv_sec++; \ |
237 | } \ |
238 | } while (0) |
239 | |
240 | #define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec) |
241 | |
242 | #define TV_SUBL(dst, a, sec, usec) do { \ |
243 | (dst)->tv_sec = (a)->tv_sec - (sec); \ |
244 | if ((a)->tv_usec >= (usec)) \ |
245 | (dst)->tv_usec = (a)->tv_usec - (usec); \ |
246 | else { \ |
247 | (dst)->tv_usec = (a)->tv_usec + MILLION - (usec); \ |
248 | (dst)->tv_sec--; \ |
249 | } \ |
250 | } while (0) |
251 | |
252 | #define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ? \ |
253 | (a)->tv_usec op (b)->tv_usec : \ |
254 | (a)->tv_sec op (b)->tv_sec) |
255 | |
256 | /*----- Main code ---------------------------------------------------------*/ |
257 | |
258 | /* --- @log@ --- * |
259 | * |
260 | * Arguments: @const char *p@ = @printf@-style format string |
261 | * @...@ = extra arguments to fill in |
262 | * |
263 | * Returns: --- |
264 | * |
265 | * Use: Writes out a timestamped log message. |
266 | */ |
267 | |
268 | static void log(const char *p, ...) |
269 | { |
270 | char b[32]; |
271 | va_list ap; |
272 | time_t t = time(0); |
273 | struct tm *tm = localtime(&t); |
274 | |
275 | strftime(b, sizeof(b), "%Y-%m-%d %H:%M:%S", tm); |
276 | fprintf(stderr, "%s: %s ", QUIS, b); |
277 | va_start(ap, p); |
278 | vfprintf(stderr, p, ap); |
279 | va_end(ap); |
280 | fputc('\n', stderr); |
281 | } |
282 | |
283 | /* --- @sigwrite@ --- * |
284 | * |
285 | * Arguments: @int sig@ = signal number |
286 | * |
287 | * Returns: --- |
288 | * |
289 | * Use: Handles signals. It writes the signal number to a pipe and |
290 | * exits. It's possible for signals to be lost if the pipe is |
291 | * full. This isn't likely enough to be worth caring about. |
292 | * The implementation in mLib's `sig.c' does the job right but |
293 | * it's rather more effort. |
294 | */ |
295 | |
296 | static void sigwrite(int sig) |
297 | { |
298 | int e = errno; |
299 | char c = sig; |
300 | write(sigfd_out, &c, 1); |
301 | errno = e; |
302 | } |
303 | |
304 | /* --- @readpass@ --- * |
305 | * |
306 | * Arguments: @int fd@ = file descriptor to read from |
307 | * |
308 | * Returns: 0 if OK, -1 if not. |
309 | * |
310 | * Use: Reads a line from a file descriptor. It continues reading |
311 | * buffers until it gets a newline character. If the buffer |
312 | * becomes full, a newline is inserted and no more data is |
313 | * read. This might cause confusion. |
314 | */ |
315 | |
316 | static int readpass(int fd) |
317 | { |
318 | int r; |
319 | char *p = pass; |
320 | char *q; |
321 | size_t sz = PIXIE_BUFSZ; |
322 | |
323 | for (;;) { |
324 | r = read(fd, p, sz); |
325 | if (r < 0) |
326 | return (-1); |
327 | if (r == 0) { |
328 | q = p + r; |
329 | break; |
330 | } |
331 | if ((q = memchr(p, '\n', r)) != 0) { |
332 | q++; |
333 | break; |
334 | } |
335 | sz -= r; |
336 | p += r; |
337 | if (!sz) { |
338 | q = p; |
339 | p[-1] = '\n'; |
340 | break; |
341 | } |
342 | } |
343 | |
344 | passlen = q - pass; |
345 | return (0); |
346 | } |
347 | |
348 | /* --- @get_pass@ --- * |
349 | * |
350 | * Arguments: --- |
351 | * |
352 | * Returns: 0 if OK, -1 if it failed. |
353 | * |
354 | * Use: Reads a passphrase from somewhere. The data from the |
355 | * passphrase goes straight into the @pass@ buffer without |
356 | * touching any other memory. Of course, if @xgetline@ is used, |
357 | * it might end up in unprotected memory there. That's a shame, |
358 | * but @xgetline@ is a much shorter-lived process than this one |
359 | * so it shouldn't matter as much. |
360 | */ |
361 | |
362 | static int get_pass(void) |
363 | { |
364 | #ifdef PATH_XGETLINE |
365 | if (flags & f_xgetline) { |
366 | int fd[2]; |
367 | pid_t kid; |
368 | int r; |
369 | |
370 | /* --- Do everything by hand --- * |
371 | * |
372 | * I could, I suppose, use @popen@. However, (a) that involves a shell |
373 | * which is extra overhead and makes passing arguments with spaces a |
374 | * little trickier; and (b) it uses @stdio@ buffers, which might get |
375 | * swapped to disk. |
376 | */ |
377 | |
378 | if (pipe(fd)) |
379 | return (-1); |
380 | kid = fork(); |
381 | if (kid < 0) |
382 | return (-1); |
383 | if (kid == 0) { |
384 | dup2(fd[1], STDOUT_FILENO); |
385 | close(fd[0]); |
386 | close(fd[1]); |
387 | execlp(PATH_XGETLINE, "xgetline", |
388 | "-i", "-tPGP pixie", "-pPGP passphrase:", (char *)0); |
389 | _exit(127); |
390 | } |
391 | close(fd[1]); |
392 | r = readpass(fd[0]); |
393 | close(fd[0]); |
394 | waitpid(kid, 0, 0); |
395 | return (r); |
396 | } else |
397 | #endif |
398 | { |
399 | struct termios o, n; |
400 | int fd; |
401 | int r; |
402 | char prompt[] = "PGP passphrase: "; |
403 | char nl = '\n'; |
404 | |
405 | /* --- Do this by hand --- * |
406 | * |
407 | * I could use @getpass@, but that puts the passphrase in its own memory |
408 | * rather than mine, so I'd have to scrub it out manually. This is |
409 | * probably just as good if you don't mind fiddling with @termios@. |
410 | * Also, the GNU version uses @stdio@ streams to read from the terminal, |
411 | * which might be considered a Bad Thing. |
412 | */ |
413 | |
414 | if ((fd = open("/dev/tty", O_RDWR)) < 0) |
415 | return (-1); |
416 | if (tcgetattr(fd, &o)) |
417 | return (-1); |
418 | n = o; |
419 | n.c_lflag &= ~(ECHO | ISIG); |
420 | if (tcsetattr(fd, TCSAFLUSH, &n)) |
421 | return (-1); |
422 | write(fd, prompt, sizeof(prompt) - 1); |
423 | r = readpass(fd); |
424 | tcsetattr(fd, TCSAFLUSH, &o); |
425 | write(fd, &nl, 1); |
426 | close(fd); |
427 | return (r); |
428 | } |
429 | } |
430 | |
431 | /* --- @help@, @version@ @usage@ --- * |
432 | * |
433 | * Arguments: @FILE *fp@ = stream to write on |
434 | * |
435 | * Returns: --- |
436 | * |
437 | * Use: Emit helpful messages. |
438 | */ |
439 | |
440 | static void usage(FILE *fp) |
441 | { |
442 | pquis(fp, "Usage: $ [-xqv] [-t timeout] [-d dir] [socket]\n"); |
443 | } |
444 | |
445 | static void version(FILE *fp) |
446 | { |
447 | pquis(fp, "$ version " VERSION "\n"); |
448 | } |
449 | |
450 | static void help(FILE *fp) |
451 | { |
452 | version(fp); |
453 | fputc('\n', fp); |
454 | usage(fp); |
455 | pquis(fp, "\n\ |
456 | The passphrase pixie remembers a PGP passphrase and passes it on to\n\ |
457 | clients which connect to a Unix-domain socket.\n\ |
458 | \n\ |
459 | The pixie will forget a passphrase after a certain amount of time. The\n\ |
460 | duration of the pixie's memory is configurable using the `-t' option, and\n\ |
461 | the default is 5 minutes. By giving a timeout of zero, the pixie can be\n\ |
462 | endowed with a perfect memory.\n\ |
463 | \n\ |
464 | The pixie attempts to lock its passphrase buffer into physical memory. If\n\ |
465 | this doesn't work (e.g., your operating system doesn't support this\n\ |
466 | feature, or you have insufficient privilege) a warning is emitted.\n\ |
467 | \n\ |
468 | Options available are:\n\ |
469 | \n\ |
470 | -h, --help Show this help text.\n\ |
471 | -V, --version Show the pixie's version number.\n\ |
472 | -u, --usage Show a uselessly terse usage message.\n\ |
473 | \n\ |
474 | " |
475 | #ifdef PATH_XGETLINE |
476 | "\ |
477 | -x, --x11 Run `xgetline' to read a passphrase.\n\ |
478 | +x, --no-x11 Don't run `xgetline' to read a passphrase.\n\ |
479 | " |
480 | #endif |
481 | "\ |
482 | -d, --directory=DIR Make secure directory DIR and change to it.\n\ |
483 | -q, --quiet Don't emit so many messages.\n\ |
484 | -v, --verbose Emit more messages.\n\ |
485 | "); |
486 | } |
487 | |
488 | /* --- @main@ --- * |
489 | * |
490 | * Arguments: @int argc@ = number of arguments |
491 | * @char *argv[]@ = vector of argument values |
492 | * |
493 | * Returns: Zero if OK. |
494 | * |
495 | * Use: Main program. Listens on a socket and responds with a PGP |
496 | * passphrase when asked. |
497 | */ |
498 | |
499 | int main(int argc, char *argv[]) |
500 | { |
501 | char *dir = 0; |
502 | int fd; |
503 | int sigfd_in; |
504 | unsigned verbose = 1; |
505 | char *sock = 0; |
506 | unsigned long timeout = PIXIE_TIMEOUT; |
507 | char *emsg = 0; |
508 | int elock = 0; |
509 | |
510 | ego(argv[0]); |
511 | |
512 | /* --- Try making a secure locked passphrase buffer --- * |
513 | * |
514 | * Drop privileges before emitting diagnostic messages. |
515 | */ |
516 | |
517 | #ifdef HAVE_MLOCK |
518 | |
519 | /* --- Memory-map a page from somewhere --- */ |
520 | |
521 | # ifdef MAP_ANON |
9d2e2c65 |
522 | pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE, |
523 | MAP_PRIVATE | MAP_ANON, -1, 0); |
9d2e2c65 |
524 | # else |
9d2e2c65 |
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 | } |
9d2e2c65 |
533 | # endif |
534 | |
535 | /* --- Lock the page in memory --- * |
536 | * |
537 | * Why does @mmap@ return such a stupid result if it fails? |
538 | */ |
539 | |
540 | if (pass == 0 || pass == MAP_FAILED) { |
541 | emsg = "couldn't map a passphrase buffer: %s"; |
542 | elock = errno; |
543 | pass = 0; |
544 | } else if (mlock(pass, PIXIE_BUFSZ)) { |
545 | emsg = "couldn't lock passphrase buffer: %s"; |
546 | elock = errno; |
547 | munmap(pass, PIXIE_BUFSZ); |
548 | pass = 0; |
549 | } else |
550 | flags |= f_goodbuf; |
551 | |
552 | #endif |
553 | |
554 | /* --- Make a standard passphrase buffer --- */ |
555 | |
556 | setuid(getuid()); |
557 | |
558 | #ifdef HAVE_MLOCK |
559 | if (!pass) |
560 | #endif |
561 | { |
562 | if ((pass = malloc(PIXIE_BUFSZ)) == 0) |
563 | die(1, "not enough memory for passphrase buffer"); |
564 | } |
565 | |
566 | /* --- Parse options --- */ |
567 | |
568 | for (;;) { |
569 | static struct option opts[] = { |
570 | |
571 | /* --- GNUey help options --- */ |
572 | |
573 | { "help", 0, 0, 'h' }, |
574 | { "usage", 0, 0, 'u' }, |
575 | { "version", 0, 0, 'V' }, |
576 | |
577 | /* --- Other options --- */ |
578 | |
579 | { "timeout", OPTF_ARGREQ, 0, 't' }, |
580 | #ifdef PATH_XGETLINE |
581 | { "xgetline", OPTF_NEGATE, 0, 'x' }, |
582 | { "x11", OPTF_NEGATE, 0, 'x' }, |
583 | #endif |
584 | { "directory", OPTF_ARGREQ, 0, 'd' }, |
585 | { "quiet", 0, 0, 'q' }, |
586 | { "verbose", 0, 0, 'v' }, |
587 | |
588 | /* --- Magic end marker --- */ |
589 | |
590 | { 0, 0, 0, 0 } |
591 | }; |
592 | |
593 | #ifdef PATH_XGETLINE |
594 | # define XOPTS "x+" |
595 | #else |
596 | # define XOPTS |
597 | #endif |
598 | |
599 | int i = mdwopt(argc, argv, "huV" XOPTS "t:d:qv", |
600 | opts, 0, 0, OPTF_NEGATION); |
601 | |
602 | #undef XOPTS |
603 | |
604 | if (i < 0) |
605 | break; |
606 | switch (i) { |
607 | case 'h': |
608 | help(stdout); |
609 | exit(0); |
610 | case 'V': |
611 | version(stdout); |
612 | exit(0); |
613 | case 'u': |
614 | usage(stdout); |
615 | exit(0); |
616 | case 't': { |
617 | char *p; |
618 | timeout = strtoul(optarg, &p, 0); |
619 | switch (*p) { |
620 | case 'd': timeout *= 24; |
621 | case 'h': timeout *= 60; |
622 | case 'm': timeout *= 60; |
cfd10afa |
623 | case 's': if (p[1] != 0) |
624 | default: timeout = 0; |
625 | case 0: break; |
9d2e2c65 |
626 | } |
cfd10afa |
627 | if (!timeout) |
628 | die(1, "bad time specification `%s'", optarg); |
9d2e2c65 |
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 -------------------------------------------------*/ |