3 * $Id: pixie.c,v 1.1 1999/12/22 15:58:41 mdw Exp $
5 * Passphrase pixie for Catacomb
7 * (c) 1999 Straylight/Edgeware
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of Catacomb.
14 * Catacomb is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU Library General Public License as
16 * published by the Free Software Foundation; either version 2 of the
17 * License, or (at your option) any later version.
19 * Catacomb is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Library General Public License for more details.
24 * You should have received a copy of the GNU Library General Public
25 * License along with Catacomb; if not, write to the Free
26 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
30 /*----- Revision history --------------------------------------------------*
33 * Revision 1.1 1999/12/22 15:58:41 mdw
34 * Passphrase pixie support.
38 /*----- Header files ------------------------------------------------------*/
53 #include <sys/types.h>
60 #include <sys/ioctl.h>
64 #include <sys/socket.h>
67 #include <mLib/alloc.h>
68 #include <mLib/dstr.h>
69 #include <mLib/fdflags.h>
70 #include <mLib/mdwopt.h>
71 #include <mLib/quis.h>
72 #include <mLib/report.h>
74 #include <mLib/selbuf.h>
81 #include "passphrase.h"
84 /*----- Static variables --------------------------------------------------*/
86 static unsigned long timeout
= 300;
88 static unsigned verbose
= 1;
89 static const char *command
= 0;
91 static unsigned flags
= 0;
97 /*----- Event logging -----------------------------------------------------*/
101 * Arguments: @const char *p@ = @printf@-style format string
102 * @...@ = extra arguments to fill in
106 * Use: Writes out a timestamped log message.
109 static void log(const char *p
, ...)
114 if (!(flags
& F_SYSLOG
)) {
116 struct tm
*tm
= localtime(&t
);
118 d
.len
+= strftime(d
.buf
, d
.sz
, "%Y-%m-%d %H:%M:%S ", tm
);
121 dstr_vputf(&d
, p
, ap
);
124 if (flags
& F_SYSLOG
)
125 syslog(LOG_NOTICE
, "%s", d
.buf
);
128 dstr_write(&d
, stderr
);
133 /*----- Passphrase management ---------------------------------------------*/
135 /* --- Data structures --- */
137 typedef struct phrase
{
147 /* --- Variables --- */
149 #define P_ROOT ((phrase *)&p_root)
150 static struct { phrase
*next
; phrase
*prev
; } p_root
= { P_ROOT
, P_ROOT
};
152 /* --- @p_free@ --- *
154 * Arguments: @phrase *p@ = pointer to phrase block
158 * Use: Frees a phrase block.
161 static void p_free(phrase
*p
)
164 sel_rmtimer(&p
->timer
);
167 p
->next
->prev
= p
->prev
;
168 p
->prev
->next
= p
->next
;
172 /* --- @p_timer@ --- *
174 * Arguments: @struct timeval *tv@ = current time
175 * @void *p@ = pointer to phrase
179 * Use: Expires a passphrase.
182 static void p_timer(struct timeval
*tv
, void *p
)
186 log("expiring passphrase `%s'", pp
->tag
);
190 /* --- @p_alloc@ --- *
192 * Arguments: @size_t sz@ = amount of memory required
194 * Returns: Pointer to allocated memory, or null.
196 * Use: Allocates some locked memory, flushing old passphrases if
197 * there's not enough space.
200 static void *p_alloc(size_t sz
)
204 if ((p
= l_alloc(&lm
, sz
)) != 0)
206 if (P_ROOT
->next
== P_ROOT
)
209 log("flushing passphrase `%s' to free up needed space",
212 p_free(P_ROOT
->next
);
216 /* --- @p_find@ --- *
218 * Arguments: @const char *tag@ = pointer to tag to find
220 * Returns: Pointer to passphrase block, or null.
222 * Use: Finds a passphrase with a given tag.
225 static phrase
*p_find(const char *tag
)
229 for (p
= P_ROOT
->next
; p
!= P_ROOT
; p
= p
->next
) {
230 if (strcmp(p
->tag
, tag
) == 0) {
233 sel_rmtimer(&p
->timer
);
234 gettimeofday(&tv
, 0);
236 sel_addtimer(&sel
, &p
->timer
, &tv
, p_timer
, p
);
238 p
->next
->prev
= p
->prev
;
239 p
->prev
->next
= p
->next
;
241 p
->prev
= P_ROOT
->prev
;
242 P_ROOT
->prev
->next
= p
;
252 * Arguments: @const char *tag@ = pointer to tag string
253 * @const char *p@ = pointer to passphrase
254 * @unsigned long t@ = expiry timeout
256 * Returns: Pointer to newly-added passphrase.
258 * Use: Adds a new passphrase. The tag must not already exist.
261 static phrase
*p_add(const char *tag
, const char *p
, unsigned long t
)
263 size_t sz
= strlen(p
) + 1;
264 char *l
= p_alloc(sz
);
267 /* --- Make sure the locked memory was allocated --- */
272 /* --- Fill in some other bits of the block --- */
277 pp
->tag
= xstrdup(tag
);
280 /* --- Set the timer --- */
285 gettimeofday(&tv
, 0);
287 sel_addtimer(&sel
, &pp
->timer
, &tv
, p_timer
, pp
);
290 /* --- Link the block into the chain --- */
293 pp
->prev
= P_ROOT
->prev
;
294 P_ROOT
->prev
->next
= pp
;
299 /* --- @p_flush@ --- *
301 * Arguments: @const char *tag@ = pointer to tag string, or zero for all
305 * Use: Immediately flushes either a single phrase or all of them.
308 static void p_flush(const char *tag
)
312 if (!tag
&& verbose
> 1)
313 log("flushing all passphrases");
314 for (p
= P_ROOT
->next
; p
!= P_ROOT
; p
= p
->next
) {
317 else if (strcmp(p
->tag
, tag
) == 0) {
319 log("flushing passphrase `%s'", tag
);
326 /*----- Reading passphrases -----------------------------------------------*/
328 /* --- @p_request@ --- *
330 * Arguments: @const char *msg@ = message string
331 * @const char *tag@ = pointer to tag string
332 * @char *buf@ = pointer to (locked) buffer
333 * @size_t sz@ = size of buffer
335 * Returns: Zero if all went well, nonzero otherwise.
337 * Use: Requests a passphrase from the user.
340 static int p_request(const char *msg
, const char *tag
, char *buf
, size_t sz
)
342 /* --- If there's a passphrase-fetching command, run it --- */
351 /* --- Substitute the prompt string into the command --- */
355 const char *q
= strchr(p
, '%');
378 /* --- Create a pipe and start a child process --- */
382 if ((kid
= fork()) < 0)
385 /* --- Child process --- */
389 if (dup2(fd
[1], STDOUT_FILENO
) < 0)
392 execl("/bin/sh", "sh", "-c", d
.buf
, (void *)0);
396 /* --- Read the data back into my buffer --- */
399 if ((r
= read(fd
[0], buf
, sz
- 1)) >= 0) {
400 char *q
= memchr(buf
, '\n', r
);
412 /* --- Tidy up when things go wrong --- */
423 /* --- Read a passphrase from the terminal --- *
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.
433 dstr_putf(&d
, "%s %s: ", msg
, tag
);
434 rc
= pixie_getpass(d
.buf
, buf
, sz
);
442 * Arguments: @const char *tag@ = pointer to tag string
443 * @unsigned mode@ = reading mode (verify?)
444 * @time_t exp@ = expiry time suggestion
446 * Returns: Pointer to passphrase, or zero.
448 * Use: Reads a passphrase from somewhere.
451 static const char *p_get(const char *tag
, unsigned mode
, time_t exp
)
458 /* --- Write a log message --- */
461 log("passphrase `%s' requested", tag
);
463 /* --- Try to find the phrase --- */
465 if ((p
= p_find(tag
)) == 0) {
466 if ((pp
= p_alloc(LBUFSZ
)) == 0)
468 if (p_request("Passphrase", tag
, pp
, LBUFSZ
) < 0)
470 p
= p_add(tag
, pp
, exp
);
475 /* --- If verification is requested, verify the passphrase --- */
477 if (mode
== PMODE_VERIFY
) {
478 if (!pp
&& (pp
= p_alloc(LBUFSZ
)) == 0)
480 if (p_request("Verify passphrase", tag
, pp
, LBUFSZ
) < 0)
482 if (strcmp(pp
, p
->p
) != 0) {
484 log("passphrases for `%s' don't match", tag
);
490 /* --- Tidy up and return the passphrase --- */
493 memset(pp
, 0, LBUFSZ
);
498 /* --- Tidy up if things went wrong --- */
502 memset(pp
, 0, LBUFSZ
);
510 /*----- Server command parsing --------------------------------------------*/
512 /* --- Data structures --- */
514 typedef struct pixserv
{
520 #define PIXSERV_TIMEOUT 300
522 /* --- @pixserv_expire@ --- *
524 * Arguments: @struct timeval *tv@ = pointer to current time
525 * @void *p@ = pointer to server block
529 * Use: Expires a pixie connection if the remote end decides he's not
530 * interested any more.
533 static void pixserv_expire(struct timeval
*tv
, void *p
)
536 if (px
->fd
!= px
->b
.reader
.fd
)
538 selbuf_disable(&px
->b
);
539 close(px
->b
.reader
.fd
);
543 /* --- @pixserv_write@ --- *
545 * Arguments: @pixserv *px@ = pointer to server block
546 * @const char *p@ = pointer to skeleton string
547 * @...@ = other arguments to fill in
551 * Use: Formats a string and emits it to the output file.
554 static void pixserv_write(pixserv
*px
, const char *p
, ...)
560 dstr_vputf(&d
, p
, ap
);
561 write(px
->fd
, d
.buf
, d
.len
);
566 /* --- @pixserv_timeout@ --- *
568 * Arguments: @const char *p@ = pointer to timeout string
570 * Returns: Timeout in seconds.
572 * Use: Translates a string to a timeout value in seconds.
575 static unsigned long pixserv_timeout(const char *p
)
583 t
= strtoul(p
, &q
, 0);
588 case 's': if (q
[1] != 0)
595 /* --- @pixserv_line@ --- *
597 * Arguments: @char *s@ = pointer to the line read
598 * @void *p@ = pointer to server block
602 * Use: Handles a line read from the client.
605 static void pixserv_line(char *s
, void *p
)
611 /* --- Handle an end-of-file --- */
613 sel_rmtimer(&px
->timer
);
615 if (px
->fd
!= px
->b
.reader
.fd
)
617 selbuf_disable(&px
->b
);
618 close(px
->b
.reader
.fd
);
622 /* --- Fiddle the timeout --- */
626 gettimeofday(&tv
, 0);
627 tv
.tv_sec
+= PIXSERV_TIMEOUT
;
628 sel_addtimer(&sel
, &px
->timer
, &tv
, pixserv_expire
, px
);
631 /* --- Scan out the first word --- */
633 if ((q
= str_getword(&s
)) == 0)
635 for (qq
= q
; *qq
; qq
++)
636 *qq
= tolower((unsigned char)*qq
);
638 /* --- Handle a help request --- */
640 if (strcmp(q
, "help") == 0) {
642 INFO Commands supported:\n\
645 INFO PASS tag [expire]\n\
646 INFO VERIFY tag [expire]\n\
653 /* --- List the passphrases --- */
655 else if (strcmp(q
, "list") == 0) {
658 for (p
= P_ROOT
->next
; p
!= P_ROOT
; p
= p
->next
) {
660 pixserv_write(px
, "ITEM %s no-expire\n", p
->tag
);
663 gettimeofday(&tv
, 0);
664 TV_SUB(&tv
, &p
->timer
.tv
, &tv
);
665 pixserv_write(px
, "ITEM %s %i\n", p
->tag
, tv
.tv_sec
);
668 pixserv_write(px
, "OK\n");
671 /* --- Request a passphrase --- */
673 else if ((mode
= PMODE_READ
, strcmp(q
, "pass") == 0) ||
674 (mode
= PMODE_VERIFY
, strcmp(q
, "verify") == 0)) {
678 if ((q
= str_getword(&s
)) == 0)
679 pixserv_write(px
, "FAIL missing tag\n");
680 else if ((t
= pixserv_timeout(s
)) == 0)
681 pixserv_write(px
, "FAIL bad timeout\n");
682 else if ((p
= p_get(q
, mode
, t
> timeout ? timeout
: t
)) == 0)
683 pixserv_write(px
, "FAIL error reading passphrase\n");
685 pixserv_write(px
, "OK %s\n", p
);
688 /* --- Flush existing passphrases --- */
690 else if (strcmp(q
, "flush") == 0) {
693 pixserv_write(px
, "OK\n");
696 /* --- Shut the server down --- */
698 else if (strcmp(q
, "quit") == 0) {
700 log("client requested shutdown");
701 pixserv_write(px
, "OK\n");
705 /* --- Report an error for other commands --- */
708 pixserv_write(px
, "FAIL unknown command `%s'\n", q
);
711 /* --- @pixserv_create@ --- *
713 * Arguments: @int fd@ = file descriptor to read from
714 * @int ofd@ = file descriptor to write to
718 * Use: Creates a new Pixie server instance for a new connection.
721 static void pixserv_create(int fd
, int ofd
)
723 pixserv
*px
= CREATE(pixserv
);
725 fdflags(fd
, O_NONBLOCK
, O_NONBLOCK
, FD_CLOEXEC
, FD_CLOEXEC
);
727 fdflags(ofd
, O_NONBLOCK
, O_NONBLOCK
, FD_CLOEXEC
, FD_CLOEXEC
);
729 selbuf_init(&px
->b
, &sel
, fd
, pixserv_line
, px
);
730 gettimeofday(&tv
, 0);
731 tv
.tv_sec
+= PIXSERV_TIMEOUT
;
732 sel_addtimer(&sel
, &px
->timer
, &tv
, pixserv_expire
, px
);
735 /* --- @pixserv_accept@ --- *
737 * Arguments: @int fd@ = file descriptor
738 * @unsigned mode@ = what's happened
739 * @void *p@ = an uninteresting argument
743 * Use: Accepts a new connection.
746 static void pixserv_accept(int fd
, unsigned mode
, void *p
)
749 struct sockaddr_un sun
;
750 int sunsz
= sizeof(sun
);
752 if (mode
!= SEL_READ
)
754 if ((nfd
= accept(fd
, (struct sockaddr
*)&sun
, &sunsz
)) < 0) {
756 log("new connection failed: %s", strerror(errno
));
759 pixserv_create(nfd
, nfd
);
762 /*----- Setting up the server ---------------------------------------------*/
764 /* --- @unlinksocket@ --- *
770 * Use: Tidies up the socket when it's finished with.
773 static char *sockpath
;
775 static void unlinksocket(void)
781 /* --- @pix_sigdie@ --- *
783 * Arguments: @int sig@ = signal number
784 * @void *p@ = uninteresting argument
788 * Use: Shuts down the program after a fatal signal.
791 static void pix_sigdie(int sig
, void *p
)
798 case SIGTERM
: p
= "SIGTERM"; break;
799 case SIGINT
: p
= "SIGINT"; break;
801 sprintf(buf
, "signal %i", sig
);
805 log("shutting down on %s", p
);
810 /* --- @pix_sigflush@ --- *
812 * Arguments: @int sig@ = signal number
813 * @void *p@ = uninteresting argument
817 * Use: Flushes the passphrase cache on receipt of a signal.
820 static void pix_sigflush(int sig
, void *p
)
827 case SIGHUP
: p
= "SIGHUP"; break;
828 case SIGQUIT
: p
= "SIGQUIT"; break;
830 sprintf(buf
, "signal %i", sig
);
834 log("received %s; flushing passphrases", p
);
839 /* --- @pix_setup@ --- *
841 * Arguments: @struct sockaddr_un *sun@ = pointer to address to use
842 * @size_t sz@ = size of socket address
846 * Use: Sets up the pixie's Unix-domain socket.
849 static void pix_setup(struct sockaddr_un
*sun
, size_t sz
)
853 /* --- Set up the parent directory --- */
857 char *p
= sun
->sun_path
;
858 char *q
= strrchr(p
, '/');
867 if (stat(d
.buf
, &st
))
868 die(1, "couldn't stat `%s': %s", d
.buf
, strerror(errno
));
869 if (st
.st_mode
& 0077)
870 die(1, "parent directory `%s' has group or world access", d
.buf
);
874 /* --- Initialize the socket --- */
882 if ((fd
= socket(PF_UNIX
, SOCK_STREAM
, 0)) < 0)
883 die(1, "couldn't create socket: %s", strerror(errno
));
884 if (bind(fd
, (struct sockaddr
*)sun
, sz
) < 0) {
886 if (errno
!= EADDRINUSE
)
887 die(1, "couldn't bind to address: %s", strerror(e
));
889 die(1, "too many retries; giving up");
891 if (connect(fd
, (struct sockaddr
*)sun
, sz
)) {
892 if (errno
!= ECONNREFUSED
)
893 die(1, "couldn't bind to address: %s", strerror(e
));
895 log("stale socket found; removing it");
896 unlink(sun
->sun_path
);
900 log("server already running; shutting it down");
901 write(fd
, "QUIT\n", 5);
907 chmod(sun
->sun_path
, 0600);
908 fdflags(fd
, O_NONBLOCK
, O_NONBLOCK
, FD_CLOEXEC
, FD_CLOEXEC
);
910 die(1, "couldn't listen on socket: %s", strerror(errno
));
913 /* --- Set up the rest of the server --- */
916 static sel_file serv
;
917 sockpath
= sun
->sun_path
;
918 atexit(unlinksocket
);
919 sel_initfile(&sel
, &serv
, fd
, SEL_READ
, pixserv_accept
, 0);
924 /*----- Client support code -----------------------------------------------*/
926 /* --- Variables --- */
928 static selbuf c_server
, c_client
;;
930 /* --- Line handler functions --- */
932 static void c_uline(char *s
, void *p
)
939 write(c_server
.reader
.fd
, s
, sz
);
942 static void c_sline(char *s
, void *p
)
946 moan("server closed the connection");
952 /* --- @pix_client@ --- *
954 * Arguments: @struct sockaddr_un *sun@ = pointer to socket address
955 * @size_t sz@ = size of socket address
956 * @char *argv[]@ = pointer to arguments to send
960 * Use: Performs client-side actions for the passphrase pixie.
963 static void pix_client(struct sockaddr_un
*sun
, size_t sz
, char *argv
[])
967 /* --- Open the socket --- */
969 if ((fd
= socket(PF_UNIX
, SOCK_STREAM
, 0)) < 0)
970 die(1, "couldn't create socket: %s", strerror(errno
));
971 if (connect(fd
, (struct sockaddr
*)sun
, sz
))
972 die(1, "couldn't connect to server: %s", strerror(errno
));
973 selbuf_init(&c_server
, &sel
, fd
, c_sline
, 0);
975 /* --- If there are any arguments, turn them into a string --- */
978 selbuf_init(&c_client
, &sel
, STDIN_FILENO
, c_uline
, 0);
987 write(fd
, d
.buf
, d
.len
);
992 /* --- And repeat --- */
998 /*----- Main code ---------------------------------------------------------*/
1000 /* --- @help@, @version@, @usage@ --- *
1002 * Arguments: @FILE *fp@ = stream to write on
1006 * Use: Emit helpful messages.
1009 static void usage(FILE *fp
)
1013 $ [-qvidl] [-c command] [-t timeout] [-s socket]\n\
1014 $ [-s socket] -C [command args...]\n\
1018 static void version(FILE *fp
)
1020 pquis(fp
, "$, Catacomb version " VERSION
"\n");
1023 static void help(FILE *fp
)
1029 The Catacomb passphrase pixie collects and caches passphrases used to\n\
1030 protect important keys. Options provided:\n\
1032 -h, --help Show this help text.\n\
1033 -V, --version Show the program's version number.\n\
1034 -u, --usage Show a (very) terse usage summary.\n\
1036 -C, --client Connect to a running pixie as a client.\n\
1038 -q, --quiet Emit fewer log messages.\n\
1039 -v, --version Emit more log messages.\n\
1040 -s, --socket=FILE Name the pixie's socket.\n\
1041 -c, --command=COMMAND Shell command to read a passphrase.\n\
1042 -t, --timeout=TIMEOUT Length of time to retain a passphrase in memory.\n\
1043 -i, --interactive Allow commands to be typed interactively.\n\
1044 -l, --syslog Emit log messages to the system log.\n\
1046 The COMMAND may contain `%m' and `%t' markers which are replaced by a\n\
1047 prompt message and the passphrase tag respectively. The TIMEOUT is an\n\
1048 integer, optionally followed by `d', `h', `m' or `s' to specify units of\n\
1049 days, hours, minutes or seconds respectively.\n\
1051 In client mode, if a command is specified on the command line, it is sent\n\
1052 to the running server; otherwise the program reads requests from stdin.\n\
1053 Responses from the pixie are written to stdout. Send a HELP request for\n\
1054 a quick summary of the pixie communication protocol.\n\
1060 * Arguments: @int argc@ = number of arguments
1061 * @char *argv[]@ = vector of argument values
1063 * Returns: Zero if OK.
1065 * Use: Main program. Listens on a socket and responds with a PGP
1066 * passphrase when asked.
1069 int main(int argc
, char *argv
[])
1072 struct sockaddr_un
*sun
;
1084 /* --- Initialize libraries --- */
1089 /* --- Set up the locked memory area --- */
1094 /* --- Parse command line arguments --- */
1097 static struct option opts
[] = {
1099 /* --- Standard GNUy help options --- */
1101 { "help", 0, 0, 'h' },
1102 { "version", 0, 0, 'V' },
1103 { "usage", 0, 0, 'u' },
1105 /* --- Other options --- */
1107 { "quiet", 0, 0, 'q' },
1108 { "verbose", 0, 0, 'v' },
1109 { "client", 0, 0, 'C' },
1110 { "socket", OPTF_ARGREQ
, 0, 's' },
1111 { "command", OPTF_ARGREQ
, 0, 'c' },
1112 { "timeout", OPTF_ARGREQ
, 0, 't' },
1113 { "interactive", 0, 0, 'i' },
1114 { "stdin", 0, 0, 'i' },
1115 { "daemon", 0, 0, 'd' },
1116 { "log", 0, 0, 'l' },
1117 { "syslog", 0, 0, 'l' },
1119 /* --- Magic terminator --- */
1124 int i
= mdwopt(argc
, argv
, "hVuqvCs:c:t:idl", opts
, 0, 0, 0);
1130 /* --- GNUy help options --- */
1142 /* --- Other interesting things --- */
1158 if ((timeout
= pixserv_timeout(optarg
)) == 0)
1159 die(1, "bad timeout `%s'", optarg
);
1174 /* --- Something else --- */
1182 if (f
& f_bogus
|| (optind
< argc
&& !(f
& f_client
))) {
1187 /* --- Set up the socket address --- */
1189 sun
= pixie_address(path
, &sz
);
1191 /* --- Initialize selectory --- */
1194 signal(SIGPIPE
, SIG_IGN
);
1196 /* --- Be a client if a client's wanted --- */
1199 pix_client(sun
, sz
, argv
+ optind
);
1201 /* --- Open the syslog if requested --- */
1205 openlog(QUIS
, 0, LOG_DAEMON
);
1208 /* --- Check on the locked memory area --- */
1212 int rc
= l_report(&lm
, &d
);
1214 die(EXIT_FAILURE
, d
.buf
);
1215 else if (rc
&& verbose
) {
1217 log("couldn't lock passphrase buffer");
1222 /* --- Set signal behaviours --- */
1225 static sig sigint
, sigterm
, sigquit
, sighup
;
1227 sig_add(&sigint
, SIGINT
, pix_sigdie
, 0);
1228 sig_add(&sigterm
, SIGTERM
, pix_sigdie
, 0);
1229 sig_add(&sigquit
, SIGQUIT
, pix_sigflush
, 0);
1230 sig_add(&sighup
, SIGHUP
, pix_sigflush
, 0);
1233 /* --- Set up the server --- */
1237 pixserv_create(STDIN_FILENO
, STDOUT_FILENO
);
1239 /* --- Fork into the background if requested --- */
1244 if (((f
& f_stdin
) &&
1245 (isatty(STDIN_FILENO
) || isatty(STDOUT_FILENO
))) ||
1247 die(1, "can't become a daemon if terminal required");
1249 if ((kid
= fork()) < 0)
1250 die(1, "fork failed: %s", strerror(errno
));
1256 if ((fd
= open("/dev/tty", O_RDONLY
)) >= 0) {
1257 ioctl(fd
, TIOCNOTTY
);
1268 log("initialized ok");
1274 /*----- That's all, folks -------------------------------------------------*/