/* -*-c-*-
*
- * $Id: daemon.c,v 1.2 1997/08/04 10:24:21 mdw Exp $
+ * $Id: daemon.c,v 1.12 2003/10/12 00:14:55 mdw Exp $
*
* Running a `become' daemon
*
- * (c) 1997 EBI
+ * (c) 1998 EBI
*/
/*----- Licensing notice --------------------------------------------------*
/*----- Revision history --------------------------------------------------*
*
* $Log: daemon.c,v $
+ * 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.
+ *
+ * Revision 1.5 1997/08/20 16:17:10 mdw
+ * More sensible restart routine: `_reinit' functions replaced by `_end' and
+ * `_init' functions.
+ *
+ * Revision 1.4 1997/08/07 10:00:37 mdw
+ * (Log entry for previous version is bogus.) Read netgroups database.
+ * Give up privileges permanently on startup.
+ *
* Revision 1.2 1997/08/04 10:24:21 mdw
* Sources placed under CVS control.
*
#include <errno.h>
#include <signal.h>
-#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
+/* --- mLib headers --- */
+
+#include <mLib/fwatch.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sel.h>
+#include <mLib/sig.h>
+#include <mLib/sym.h>
+#include <mLib/trace.h>
+
+/* --- Catacomb headers --- */
+
+#include <catacomb/buf.h>
+#include <catacomb/dsa.h>
+#include <catacomb/key.h>
+#include <catacomb/mp.h>
+#include <catacomb/noise.h>
+#include <catacomb/paranoia.h>
+#include <catacomb/rand.h>
+#include <catacomb/sha.h>
+
/* --- Local headers --- */
#include "become.h"
#include "config.h"
-#include "crypt.h"
#include "daemon.h"
-#include "idea.h"
#include "lexer.h"
#include "name.h"
-#include "parser.h"
+#include "netg.h"
+#include "parse.h"
#include "rule.h"
-#include "tx.h"
#include "userdb.h"
-#include "utils.h"
/*----- Arbitrary constants -----------------------------------------------*/
/*----- 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__watch;
+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 dsa_priv daemon__key; /* The key data */
/*----- Main code ---------------------------------------------------------*/
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);
+}
+
/* --- @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__readKey)
return;
-
- if ((fp = fopen(kf, "r")) == 0) {
- syslog(LOG_WARNING, "couldn't read key file: %e");
+ 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__readConfig@ --- *
if ((fp = fopen(cf, "r")) == 0)
return (-1);
lexer_scan(fp);
- yyparse();
+ parse();
fclose(fp);
if (!daemon__readKey)
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 --- */
- 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);
- return;
- }
- burn(buff);
-
- /* --- Fill in the sender's address in the request 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__watch, daemon__config);
+}
+
+/* --- @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)
+{
+ rand_seed(RAND_GLOBAL, 160);
+ if (fwatch_update(&daemon__watch, daemon__config))
+ daemon__rescan(0, 0);
}
/* --- @daemon_init@ --- *
void daemon_init(const char *cf, int port)
{
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 --- *
*
* user wants me to start on a funny port.
*/
- seteuid(getuid());
+ 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();
+ netg_init();
name_init();
rule_init();
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__watch, daemon__config);
/* --- Decide on a port to use --- *
*
* 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");
- daemon__port = ntohs(se->s_port);
+ die(1, "no idea which port to listen to");
+ daemon__port = se->s_port;
}
/* --- Now set up a socket --- */
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 --- */
/* --- 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;
}
#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");
- userdb_reinit();
- userdb_local();
- userdb_yp();
- rule_reinit();
- name_reinit();
- 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);
}
}
}