X-Git-Url: https://git.distorted.org.uk/~mdw/become/blobdiff_plain/f3debbd8e99448cd425e8b87e0d9d99850689a85..030c25b77f357ef0657460d7590cc662b6f7a9b3:/src/daemon.c diff --git a/src/daemon.c b/src/daemon.c index f79d76b..fbaf26b 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -1,10 +1,10 @@ /* -*-c-*- * - * $Id: daemon.c,v 1.6 1997/09/09 18:17:06 mdw Exp $ + * $Id: daemon.c,v 1.14 2003/10/17 16:30:22 mdw Exp $ * * Running a `become' daemon * - * (c) 1997 EBI + * (c) 1998 EBI */ /*----- Licensing notice --------------------------------------------------* @@ -29,6 +29,37 @@ /*----- Revision history --------------------------------------------------* * * $Log: daemon.c,v $ + * Revision 1.14 2003/10/17 16:30:22 mdw + * Reload keys and config files automatically. + * + * Revision 1.13 2003/10/12 10:00:06 mdw + * Fix for daemon mode. Oops. + * + * Revision 1.12 2003/10/12 00:14:55 mdw + * Major overhaul. Now uses DSA signatures rather than the bogus symmetric + * encrypt-and-hope thing. Integrated with mLib and Catacomb. + * + * Revision 1.11 1999/05/04 16:17:12 mdw + * Change to header file name for parser. See log for `parse.h' for + * details. + * + * Revision 1.10 1998/04/23 13:23:09 mdw + * Support new interface to configuration file parser. + * + * Revision 1.9 1998/01/12 16:45:59 mdw + * Fix copyright date. + * + * Revision 1.8 1997/09/26 09:14:58 mdw + * Merged blowfish branch into trunk. + * + * Revision 1.7.2.1 1997/09/26 09:08:05 mdw + * Use the Blowfish encryption algorithm instead of IDEA. This is partly + * because I prefer Blowfish (without any particularly strong evidence) but + * mainly because IDEA is patented and Blowfish isn't. + * + * Revision 1.7 1997/09/17 10:23:23 mdw + * Fix a typo. Port numbers are in network order now, so don't change them. + * * Revision 1.6 1997/09/09 18:17:06 mdw * Allow default port to be given as a service name or port number. * @@ -54,7 +85,6 @@ #include #include -#include #include #include #include @@ -73,35 +103,54 @@ #include #include +/* --- mLib headers --- */ + +#include +#include +#include +#include +#include +#include +#include + +/* --- Catacomb headers --- */ + +#include +#include +#include +#include +#include +#include +#include +#include + /* --- Local headers --- */ #include "become.h" #include "config.h" -#include "crypt.h" #include "daemon.h" -#include "idea.h" #include "lexer.h" #include "name.h" #include "netg.h" -#include "parser.h" +#include "parse.h" #include "rule.h" -#include "tx.h" #include "userdb.h" -#include "utils.h" /*----- Arbitrary constants -----------------------------------------------*/ -#define daemon__awakeEvery (30 * 60) /* Awaken this often to rescan */ +/* #define daemon__awakeEvery (5 * 60) /\* Awaken this often to rescan *\/ */ +#define daemon__awakeEvery (10) /* Awaken this often to rescan */ /*----- Static variables --------------------------------------------------*/ -static int daemon__running = 0; /* Am I running as a daemon? */ static int daemon__port = -1; /* No particular port yet */ -static volatile sig_atomic_t daemon__rescan = 0; /* Rescan as soon as poss */ -#define daemon__signum daemon__rescan /* Alias for readbility */ -static int daemon__readKey = 0; /* Have I read a key? */ -static unsigned char daemon__key[IDEA_KEYSIZE]; /* encryption key */ -static jmp_buf daemon__dieBuf; /* Jump here to kill the daemon */ +static fwatch daemon__cwatch, daemon__kwatch; /* Watching key / config files */ +static sel_timer daemon__timer; /* Timer for reading */ +static sel_state daemon__sel; /* Select context */ +static sel_file daemon__listen; /* Listening socket selector */ +static const char *daemon__config; /* Configuration file for daemon */ +static const char *daemon__keyfile; /* Keyring file for daemon */ +static dsa_priv daemon__key; /* The key data */ /*----- Main code ---------------------------------------------------------*/ @@ -119,30 +168,64 @@ void daemon_usePort(int port) daemon__port = port; } +/* --- @daemon__moan@ --- * + * + * Arguments: @const char *f@ = offending file name + * @int line@ = offending line of the file + * @const char *msg@ = message + * @void *p@ = ignored + * + * Returns: --- + * + * Use: Reports an error message about a key file. + */ + +static void daemon__moan(const char *f, int line, const char *msg, void *p) +{ + syslog(LOG_ERR, "key file error: %s: %d: %s", f, line, msg); + T( trace(TRACE_DAEMON, "daemon: key file error: %s: %d: %s", + f, line, msg); ) +} + /* --- @daemon_readKey@ --- * * - * Arguments: @const char *kf@ = name of file containing key + * Arguments: @const char *kf@ = pointer to key file name to use * * Returns: --- * - * Use: Instructs the daemon to read the named key file. + * Use: Loads the private key from the key file. */ void daemon_readKey(const char *kf) { - FILE *fp; + key_packstruct kps[DSA_PRIVFETCHSZ]; + key_packdef *kp; + key_file f; + key *k; + int err; - if (!daemon__running) + if (daemon__keyfile) return; - - if ((fp = fopen(kf, "r")) == 0) { - syslog(LOG_WARNING, "couldn't read key file: %e"); + T( trace(TRACE_DAEMON, "daemon: reading key from `%s'", kf); ) + if (key_open(&f, kf, KOPEN_READ, daemon__moan, 0)) return; + kp = key_fetchinit(dsa_privfetch, kps, &daemon__key); + if ((k = key_bytype(&f, "become-dsa")) == 0) + err = KERR_NOTFOUND; + else + err = key_fetch(kp, k); + if (err) + syslog(LOG_ERR, "couldn't load key: %s", key_strerror(err)); + else { + mp_copy(daemon__key.dp.p); + mp_copy(daemon__key.dp.q); + mp_copy(daemon__key.dp.g); + mp_copy(daemon__key.x); + mp_copy(daemon__key.y); } - tx_getBits(daemon__key, 128, fp); - fclose(fp); - daemon__readKey = 1; - return; + key_fetchdone(kp); + key_close(&f); + daemon__keyfile = kf; } /* --- @daemon__readConfig@ --- * @@ -158,139 +241,258 @@ static int daemon__readConfig(const char *cf) { FILE *fp; - daemon__readKey = 0; + daemon__keyfile = 0; if ((fp = fopen(cf, "r")) == 0) return (-1); lexer_scan(fp); - yyparse(); + parse(); fclose(fp); - if (!daemon__readKey) + if (!daemon__keyfile) daemon_readKey(file_KEY); - daemon__rescan = 0; T( trace(TRACE_DAEMON, "daemon: read config file"); ) return (0); } -/* --- @daemon__restart@ --- * - * - * Arguments: @int sig@ = the signal number - * - * Returns: --- - * - * Use: Handles signals. Causes the configuration file to be reread. - */ - -static void daemon__restart(int sig) -{ - daemon__rescan = 1; - signal(sig, daemon__restart); -} - -/* --- @daemon__die@ --- * - * - * Arguments: @int sig@ = the signal number - * - * Returns: via @longjmp@ - * - * Use: Handles other signals. Causes the daemon to die. - */ - -static void daemon__die(int sig) -{ - daemon__signum = sig; - longjmp(daemon__dieBuf, 1); -} - /* --- @daemon__read@ --- * * * Arguments: @int fd@ = socket handle + * @unsigned mode@ = ignored + * @void *p@ = ignored * * Returns: --- * * Use: Examines a buffer, and returns a response. */ -void daemon__read(int fd) +void daemon__read(int fd, unsigned mode, void *p) { unsigned char buff[65536]; /* Buffer for incoming packets */ - unsigned char rpl[crp_size]; /* Buffer for outgoing replies */ struct sockaddr_in sin; /* Address of packet sender */ char sender[64]; /* Sender's hostname (resolved) */ - unsigned char sk[IDEA_KEYSIZE]; /* Session key for reply */ + octet h[SHA_HASHSZ]; /* Hash of the transmission buffer */ + sha_ctx hc; /* Hashing context */ request rq; /* Request buffer for verification */ + ssize_t sz; /* Length of incoming message */ + socklen_t slen; /* Length of incoming address */ + uint32 u; /* Scratch integer */ + uint16 ul; /* And another */ + struct hostent *he; /* Resolve structure */ + mp *m, *k, *r, *s; /* Integers for signing */ + int ans; /* Answer from the check */ + buf b; /* Buffer for parsing request */ - /* --- Read the message --- */ + /* --- Kick some randomness in the pot --- */ - { - int slen = sizeof(sin); + noise_timer(RAND_GLOBAL); - if (recvfrom(fd, (char *)buff, sizeof(buff), 0, - (struct sockaddr *)&sin, &slen) < 0) { - T( trace(TRACE_DAEMON, "daemon: error reading packet: %s", - strerror(errno)); ) - syslog(LOG_INFO, "duff packet received: %e"); - return; - } + /* --- Read the message --- */ + + slen = sizeof(sin); + if ((sz = recvfrom(fd, (char *)buff, sizeof(buff), 0, + (struct sockaddr *)&sin, &slen)) < 0) { + T( trace(TRACE_DAEMON, "daemon: error reading packet: %s", + strerror(errno)); ) + syslog(LOG_INFO, "duff packet received: %e"); + return; } /* --- Resolve the host name --- */ - { - struct hostent *he = gethostbyaddr((char *)&sin.sin_addr, - sizeof(sin.sin_addr), - AF_INET); - sender[0] = 0; - strncat(sender, - he ? he->h_name : inet_ntoa(sin.sin_addr), - sizeof(sender)); - syslog(LOG_DEBUG, "packet received from %s", sender); - T( trace(TRACE_DAEMON, "daemon: received request from %s", sender); ) - } + he = gethostbyaddr((char *)&sin.sin_addr, sizeof(sin.sin_addr), AF_INET); + sender[0] = 0; + strncat(sender, he ? he->h_name : inet_ntoa(sin.sin_addr), + sizeof(sender) - 1); + syslog(LOG_DEBUG, "packet received from %s", sender); + T( trace(TRACE_DAEMON, "daemon: received request from %s", sender); ) - /* --- Unpack the block --- */ + /* --- Sanity check --- */ - if (crypt_unpackRequest(&rq, buff, daemon__key, sk, rpl) == 0) { - burn(buff); - T( trace(TRACE_DAEMON, "daemon: received corrupt or invalid request"); ) - syslog(LOG_INFO, "packet from %s rejected", sender); + if (!daemon__keyfile) { + syslog(LOG_NOTICE, "no key file: ignoring request"); + T( trace(TRACE_DAEMON, "daemon: no key file: ignoring request"); ) return; } - burn(buff); - /* --- Fill in the sender's address in the request block --- */ + /* --- Unpack the block --- */ rq.host = sin.sin_addr; + buf_init(&b, buff, sz); + if (buf_ensure(&b, SHA_HASHSZ)) goto fail; BSTEP(&b, SHA_HASHSZ); + if (buf_getu32(&b, &u)) goto fail; rq.from = u; + if (buf_getu32(&b, &u)) goto fail; rq.to = u; + if (buf_getu16(&b, &ul) || buf_ensure(&b, ul) || ul >= sizeof(rq.cmd)) + goto fail; + memcpy(rq.cmd, BCUR(&b), ul); + rq.cmd[ul] = 0; + BSTEP(&b, ul); + if (BLEFT(&b)) goto fail; + + /* --- Hash the request block --- */ + + sha_init(&hc); + sha_hash(&hc, buff, sz); + sha_done(&hc, h); /* --- Build a reply block --- */ - { - int answer = rule_check(&rq); - syslog(LOG_INFO, "request from %s for %i to become %i to run %s %s", - sender, rq.from, rq.to, rq.cmd, answer ? "granted" : "denied"); - crypt_packReply(rpl, sk, answer); - burn(sk); - } + ans = rule_check(&rq); + syslog(LOG_INFO, "request from %s for %i to become %i to run %s %s", + sender, rq.from, rq.to, rq.cmd, ans ? "granted" : "denied"); + buf_init(&b, buff, sizeof(buff)); + if (buf_put(&b, h, sizeof(h)) || buf_putbyte(&b, ans)) + goto fail; + + /* --- Sign the reply block --- */ + + sha_init(&hc); + sha_hash(&hc, BBASE(&b), BLEN(&b)); + sha_done(&hc, h); + m = mp_loadb(MP_NEW, h, sizeof(h)); + rand_get(RAND_GLOBAL, h, sizeof(h)); + k = mp_loadb(MP_NEWSEC, h, sizeof(h)); + r = s = MP_NEW; + dsa_mksig(&daemon__key.dp, daemon__key.x, m, k, &r, &s); + buf_putmp(&b, r); + buf_putmp(&b, s); + mp_drop(m); + mp_drop(k); + mp_drop(r); + mp_drop(s); + if (BBAD(&b)) + goto fail; /* --- Send the reply off --- */ - sendto(fd, (char *)rpl, crp_size, 0, (struct sockaddr *)&sin, sizeof(sin)); + sendto(fd, BBASE(&b), BLEN(&b), 0, (struct sockaddr *)&sin, sizeof(sin)); T( trace(TRACE_DAEMON, "daemon: reply sent"); ) - burn(rpl); + return; + +fail: + syslog(LOG_ERR, "couldn't respond to query"); + T( trace(TRACE_DAEMON, "daemon: failed to answer query"); ) +} + +/* --- @daemon__die@ --- * + * + * Arguments: @int n@ = signal number + * @void *p@ = ignored + * + * Returns: Doesn't. + * + * Use: Exits the daemon. + */ + +static void daemon__die(int n, void *p) +{ + T( trace(TRACE_DAEMON, "daemon: killed by signal %i", n); ) + syslog(LOG_NOTICE, "killed by signal type %i", n); + remove(file_PID); + exit(0); +} + +/* --- @daemon__setTimer@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Sets the interval timer up. + */ + +static void daemon__wakeUp(struct timeval *tv, void *p); + +static void daemon__setTimer(void) +{ + struct timeval tv; + + gettimeofday(&tv, 0); + tv.tv_sec += daemon__awakeEvery; + sel_addtimer(&daemon__sel, &daemon__timer, &tv, daemon__wakeUp, 0); +} + +/* --- @daemon__rescan@ --- * + * + * Arguments: @int n@ = signal number + * @void *p@ = ignored + * + * Returns: --- + * + * Use: Forces a rescan of the daemon's configuration. + */ + +static void daemon__rescan(int n, void *p) +{ + syslog(LOG_INFO, "rescanning configuration file"); + name_end(); + rule_end(); + netg_end(); + userdb_end(); + dsa_privfree(&daemon__key); + userdb_init(); + userdb_local(); + userdb_yp(); + netg_init(); + rule_init(); + name_init(); + if (daemon__readConfig(daemon__config)) + syslog(LOG_ERR, "error reading configuration file"); + sel_rmtimer(&daemon__timer); + daemon__setTimer(); + fwatch_update(&daemon__cwatch, daemon__config); + fwatch_update(&daemon__kwatch, daemon__keyfile); +} + +/* --- @daemon__wakeUp@ --- * + * + * Arguments: @struct timeval *tv@ = ignored + * @void *p@ = ignored + * + * Returns: --- + * + * Use: Wakes up periodically to check the configuration file. + */ + +static void daemon__wakeUp(struct timeval *tv, void *p) +{ + T( trace(TRACE_DAEMON, "daemon: interval timer"); ) + rand_seed(RAND_GLOBAL, 160); + daemon__setTimer(); + if (fwatch_update(&daemon__cwatch, daemon__config)) + daemon__rescan(0, 0); + else if (fwatch_update(&daemon__kwatch, daemon__keyfile)) { + const char *kf = daemon__keyfile; + daemon__keyfile = 0; + daemon_readKey(kf); + } } /* --- @daemon_init@ --- * * * Arguments: @const char *cf@ = pointer to name of configuration file * @int port@ = port to listen to, or %$-1$% for default + * @unsigned f@ = various flags * * Returns: Never. * * Use: Starts `become' up in daemon mode. */ -void daemon_init(const char *cf, int port) +void daemon_init(const char *cf, int port, unsigned f) { int s; + int i; + + static struct sigvec { + int sig; + void (*proc)(int n, void *p); + sig s; + } sigs[] = { + { SIGHUP, daemon__rescan }, + { SIGINT, daemon__die }, + { SIGTERM, daemon__die }, + { SIGQUIT, daemon__die }, + { 0, 0 } + }; /* --- Remove my root privileges --- * * @@ -300,10 +502,17 @@ void daemon_init(const char *cf, int port) setuid(getuid()); + /* --- Initialize the random number generator --- */ + + rand_noisesrc(RAND_GLOBAL, &noise_source); + rand_seed(RAND_GLOBAL, 160); + /* --- Initialise bits of the program --- */ - daemon__running = 1; + daemon__config = cf; daemon__port = port; + sel_init(&daemon__sel); + sig_init(&daemon__sel); userdb_init(); userdb_local(); userdb_yp(); @@ -313,8 +522,10 @@ void daemon_init(const char *cf, int port) openlog(quis(), 0, LOG_DAEMON); syslog(LOG_NOTICE, "starting up"); - if (daemon__readConfig(cf)) - die("couldn't read configuration file"); + if (daemon__readConfig(daemon__config)) + die(1, "couldn't read configuration file"); + fwatch_init(&daemon__cwatch, daemon__config); + fwatch_init(&daemon__kwatch, daemon__keyfile); /* --- Decide on a port to use --- * * @@ -322,10 +533,10 @@ void daemon_init(const char *cf, int port) * look it up in /etc/services under whatever name I was started as. */ - if (daemon__port <= 0) { + if (daemon__port == 0) { struct servent *se = getservbyname(quis(), "udp"); if (!se) - die("no idea which port to use"); + die(1, "no idea which port to listen to"); daemon__port = se->s_port; } @@ -335,25 +546,26 @@ void daemon_init(const char *cf, int port) struct sockaddr_in sin; if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) - die("couldn't create socket: %s", strerror(errno)); + die(1, "couldn't create socket: %s", strerror(errno)); sin.sin_family = AF_INET; - sin.sin_port = htons(daemon__port); + sin.sin_port = daemon__port; sin.sin_addr.s_addr = htonl(INADDR_ANY); - if (bind(s, (struct sockaddr *)&sin, sizeof(sin))) - die("couldn't bind socket to port: %s", strerror(errno)); + if (bind(s, (struct sockaddr *)&sin, sizeof(sin))) { + die(1, "couldn't bind socket to port %i: %s", + ntohs(daemon__port), strerror(errno)); + } } /* --- Fork off into the sunset --- */ -#ifdef NDEBUG - { + if (!(f & df_nofork)) { int pid = fork(); FILE *fp; /* --- Make a background process --- */ if (pid == -1) - die("couldn't fork daemon: %s", strerror(errno)); + die(1, "couldn't fork daemon: %s", strerror(errno)); else if (pid != 0) return; @@ -369,89 +581,30 @@ void daemon_init(const char *cf, int port) } T( trace(TRACE_DAEMON, "daemon: forked to pid %li", (long)getpid()); ) } -#endif - - /* --- Program in daemon death mode --- */ - - if (setjmp(daemon__dieBuf)) { -#ifdef TRACING - if (daemon__signum == SIGQUIT && tracing() & TRACE_RULE) { - T( rule_dump(); ) - signal(SIGQUIT, daemon__die); - } else -#endif - { - T( trace(TRACE_DAEMON, "daemon: killed by signal %i", - daemon__signum); ) - syslog(LOG_NOTICE, "killed by signal type %i", daemon__signum); - remove(file_PID); - exit(0); - } - } else { - - /* --- Set signal handlers --- */ - - signal(SIGHUP, daemon__restart); - signal(SIGQUIT, daemon__die); - signal(SIGINT, daemon__die); - signal(SIGTERM, daemon__die); - signal(SIGSEGV, daemon__die); - signal(SIGFPE, daemon__die); - signal(SIGBUS, daemon__die); - } - - /* --- Now wait for something exciting to happen --- * - * - * Actually, every so often (5 minutes, perhaps) I need to wake up and - * rescan the users to see whether they've changed. Time to play with - * @select@. - */ - - { - time_t when; - - /* --- Find when I am, and thus when I need to be awoken again --- */ - - when = time(0) + daemon__awakeEvery; - - for (;;) { - fd_set fds; - int i; - /* --- Set up the file descriptor tables --- */ + /* --- Set signal handlers --- */ - FD_ZERO(&fds); - FD_SET(s, &fds); + for (i = 0; sigs[i].proc; i++) + sig_add(&sigs[i].s, sigs[i].sig, sigs[i].proc, 0); - /* --- Now wait for something interesting --- */ + /* --- Set the timer for rescanning the file --- */ - T( trace(TRACE_DAEMON, "daemon: waiting for requests"); ) - i = select(FD_SETSIZE, &fds, 0, 0, 0); + daemon__setTimer(); - /* --- Now, see if I need to rescan the config --- */ + /* --- Watch for input --- */ - if (daemon__rescan || time(0) - when > 0) { - daemon__rescan = 0; - syslog(LOG_INFO, "rescanning configuration file"); - name_end(); - rule_end(); - netg_end(); - userdb_end(); - userdb_init(); - userdb_local(); - userdb_yp(); - netg_init(); - rule_init(); - name_init(); - if (daemon__readConfig(cf)) - syslog(LOG_ERR, "error reading configuration file"); - when = time(0) + daemon__awakeEvery; - } + sel_initfile(&daemon__sel, &daemon__listen, s, SEL_READ, + daemon__read, 0); + sel_addfile(&daemon__listen); - /* --- Read the data from the request --- */ + /* --- Now wait for something exciting to happen --- */ - if (i > 0) - daemon__read(s); + for (;;) { + if (sel_select(&daemon__sel)) { + if (errno == EINTR || errno == EAGAIN) + continue; + syslog(LOG_ERR, "error from select: %s", strerror(errno)); + exit(1); } } }