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