/* -*-c-*-
*
- * $Id: daemon.c,v 1.4 1997/08/07 10:00:37 mdw Exp $
+ * $Id$
*
* Running a `become' daemon
*
- * (c) 1997 EBI
+ * (c) 1998 EBI
*/
/*----- Licensing notice --------------------------------------------------*
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/*----- Revision history --------------------------------------------------*
- *
- * $Log: daemon.c,v $
- * 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.
- *
- * Revision 1.1 1997/07/21 13:47:50 mdw
- * Initial revision
- *
- */
-
/*----- Header files ------------------------------------------------------*/
/* --- ANSI headers --- */
#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/gdsa.h>
+#include <catacomb/key.h>
+#include <catacomb/dh.h>
+#include <catacomb/ec-keys.h>
+#include <catacomb/mp.h>
+#include <catacomb/noise.h>
+#include <catacomb/paranoia.h>
+#include <catacomb/rand.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 "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 (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 gdsa 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);
+ 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;
-
- if (!daemon__running)
- return;
-
- if ((fp = fopen(kf, "r")) == 0) {
- syslog(LOG_WARNING, "couldn't read key file: %e");
+ key_packdef *kp;
+ key_file f;
+ key *k;
+ int err;
+ const char *sn;
+ const char *hn;
+ const char *errmsg;
+ gdsa g;
+
+ if (daemon__keyfile)
return;
+ T( trace(TRACE_DAEMON, "daemon: reading key from `%s'", kf); )
+ if (key_open(&f, kf, KOPEN_READ, daemon__moan, 0))
+ goto fail_0;
+ if ((k = key_bytype(&f, "become")) == 0) {
+ syslog(LOG_ERR, "no key of type `become' found");
+ goto fail_1;
}
- tx_getBits(daemon__key, 128, fp);
- fclose(fp);
- daemon__readKey = 1;
+ if ((hn = key_getattr(&f, k, "hash")) == 0)
+ hn = "sha";
+ sn = key_getattr(&f, k, "sig");
+ g.r = &rand_global;
+ if ((g.h = ghash_byname(hn)) == 0) {
+ syslog(LOG_ERR, "key uses unknown hash algorithm `%s'", hn);
+ goto fail_1;
+ }
+ if (!sn || strcmp(sn, "dsa") == 0) {
+ dh_priv dp;
+ kp = key_fetchinit(dh_privfetch, 0, &dp);
+ if ((err = key_fetch(kp, k)) != 0) {
+ syslog(LOG_ERR, "error loading key: %s", key_strerror(err));
+ goto fail_2;
+ }
+ if ((g.g = group_prime(&dp.dp)) == 0) {
+ syslog(LOG_ERR, "bad prime group in key");
+ goto fail_3;
+ }
+ g.p = G_CREATE(g.g);
+ if (G_FROMINT(g.g, g.p, dp.y)) {
+ syslog(LOG_ERR, "bad public key");
+ goto fail_4;
+ }
+ g.u = mp_copy(dp.x);
+ } else if (strcmp(sn, "ecdsa") == 0) {
+ ec_priv ep;
+ ec_info ei;
+ kp = key_fetchinit(ec_privfetch, 0, &ep);
+ if ((err = key_fetch(kp, k)) != 0) {
+ syslog(LOG_ERR, "error loading key: %s", key_strerror(err));
+ goto fail_2;
+ }
+ if ((errmsg = ec_getinfo(&ei, ep.cstr)) != 0) {
+ syslog(LOG_ERR, "bad curve in key: %s", errmsg);
+ goto fail_3;
+ }
+ g.g = group_ec(&ei);
+ g.p = G_CREATE(g.g);
+ if (G_FROMEC(g.g, g.p, &ep.p)) {
+ syslog(LOG_ERR, "bad public point");
+ goto fail_4;
+ }
+ g.u = mp_copy(ep.x);
+ } else {
+ syslog(LOG_ERR, "key uses unknown signature scheme `%s'", sn);
+ goto fail_1;
+ }
+ key_fetchdone(kp);
+ daemon__keyfile = kf;
+ key_close(&f);
+ if (daemon__key.g) {
+ mp_drop(daemon__key.u);
+ G_DESTROY(daemon__key.g, daemon__key.p);
+ G_DESTROYGROUP(daemon__key.g);
+ }
+ daemon__key = g;
+ T( trace(TRACE_DAEMON, "daemon: key read ok"); )
+ return;
+
+fail_4:
+ G_DESTROY(g.g, g.p);
+fail_3:
+ G_DESTROYGROUP(g.g);
+fail_2:
+ key_fetchdone(kp);
+fail_1:
+ key_close(&f);
+fail_0:
+ T( trace(TRACE_DAEMON, "daemon: failed to read key"); )
return;
}
{
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 */
+ ghash *h; /* Hash 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 */
+ gdsa_sig s = GDSA_SIG_INIT; /* Signature block */
+ 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, 64)) goto fail; BSTEP(&b, 64);
+ 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 --- */
+
+ h = GH_INIT(daemon__key.h);
+ GH_HASH(h, buff, sz);
/* --- 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));
+ buf_put(&b, GH_DONE(h, 0), GH_CLASS(h)->hashsz);
+ buf_putbyte(&b, ans);
+ if (BBAD(&b)) goto fail;
+
+ /* --- Sign the reply block --- */
+
+ h = gdsa_beginhash(&daemon__key);
+ GH_HASH(h, BBASE(&b), BLEN(&b));
+ gdsa_endhash(&daemon__key, h);
+ gdsa_sign(&daemon__key, &s, GH_DONE(h, 0), 0);
+ GH_DESTROY(h);
+ buf_putmp(&b, s.r); buf_putmp(&b, s.s);
+ mp_drop(s.r); mp_drop(s.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();
+ 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 --- *
*
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();
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 --- *
*
* 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);
+ if (se)
+ daemon__port = se->s_port;
+ else
+ daemon__port = htons(SERVER_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 --- */
-#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;
}
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");
- userdb_reinit();
- userdb_local();
- userdb_yp();
- netg_reinit();
- 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);
}
}
}