/* $Id: sfsagent.C,v 1.53 2001/10/29 23:05:49 ericp Exp $ */ /* * * Copyright (C) 1998, 1999 David Mazieres (dm@uun.org) * Copyright (C) 1999, 2000 Michael Kaminsky (kaminsky@lcs.mit.edu) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * */ #include "agent.h" EXITFN (cleanup); list agents; list keys; sfsagent *cdagent; bool opt_forwarding = true; bool opt_killstart; bool opt_nodaemon; str opt_keytime; str opt_socket; static sfs_time exp_time; static timecb_t *exp_tmo; static void start_listening (); static bool rndok; static void setrnd () { random_update (); rndok = true; } inline void checkrnd () { while (!rndok) acheck (); } void key_timeout (bool cb = false) { if (!cb) timecb_remove (exp_tmo); exp_tmo = NULL; exp_time = 0; sfs_time now = time (NULL); for (key *k = keys.first, *nk; k; k = nk) { nk = keys.next (k); if (k->expire && k->expire <= now) { keys.remove (k); delete k; } else if (k->expire && (!exp_time || k->expire < exp_time)) exp_time = k->expire; } if (exp_time) exp_tmo = timecb (exp_time, wrap (key_timeout, true)); } static key * klookup (const bigint &n) { key *k; for (k = keys.first; k; k = k->link.next) if (k->k->n == n) return k; return NULL; } static key * clookup (str c) { key *k; for (k = keys.first; k; k = k->link.next) if (k->comment == c) return k; return NULL; } key * keynum (u_int n) { key *k = keys.first; while (k && n--) k = k->link.next; return k; } static inline axprt_unix * xprt2unix (axprt *x) { // XXX - dynamic_cast is busted in egcs axprt_unix *ux = static_cast (&*x); if (typeid (*ux) == typeid (refcounted)) return ux; else return NULL; } sfsagent::sfsagent (int fd) : x (axprt_unix::alloc (fd)), cs (asrv::alloc (x, agentctl_prog_1, wrap (this, &sfsagent::dispatch))) { agents.insert_head (this); } sfsagent::sfsagent (ref xx) : x (xx), cs (asrv::alloc (x, agentctl_prog_1, wrap (this, &sfsagent::dispatch))) { agents.insert_head (this); } sfsagent::~sfsagent () { agents.remove (this); if (this == cdagent) { warn << "exiting\n"; exit (0); } } void sfsagent::authinit (svccb *sbp) { sfsagent_authinit_arg *aa = sbp->template getarg (); key *k = keynum (aa->ntries); if (!k || aa->authinfo.type != SFS_AUTHINFO) { sbp->replyref (sfsagent_auth_res (false)); return; } sfsagent_auth_res res (true); sfs_autharg ar (SFS_AUTHREQ); sfs_signed_authreq sar; str rawsar; ar.req->usrkey = k->k->n; sar.type = SFS_SIGNED_AUTHREQ; sar.seqno = aa->seqno; bzero (sar.usrinfo.base (), sar.usrinfo.size ()); if (!sha1_hashxdr (sar.authid.base (), aa->authinfo) || !(rawsar = xdr2str (sar)) || !(ar.req->signed_req = k->k->sign_r (rawsar), xdr2bytes (*res.certificate, ar))) { warn ("sfsagent::authinit: xdr failed\n"); res.set_authenticate (false); } warn << aa->requestor << ": " << aa->authinfo.name << ":" << armor32 (str (aa->authinfo.hostid.base (), aa->authinfo.hostid.size ())) << " (" << implicit_cast (aa->authinfo.service) << ")\n"; sbp->replyref (res); } void sfsagent::dispatch (svccb *sbp) { if (!sbp) { if (name) warn << "EOF from " << name << "\n"; delete this; return; } if (sbp->prog () == AGENTCB_PROG && sbp->vers () == AGENTCB_VERS) agentdisp (sbp); else if (sbp->prog () == AGENTCTL_PROG && sbp->vers () == AGENTCTL_VERS) ctldisp (sbp); else panic ("invalid prog/vers\n"); } static void lookupres (svccb *sbp, str path) { sfsagent_lookup_res res; if (path && path.len () <= sfsagent_path::maxsize) { res.set_makelink (true); *res.path = path; } sbp->replyref (res); } static void revokedres (svccb *sbp, const sfsagent_revoked_res *res) { sbp->reply (res); } void rexres (svccb *sbp, ptr res) { if (!res) sbp->replyref (sfsagent_rex_res (false)); else sbp->reply (res); } void sfsagent::agentdisp (svccb *sbp) { checkrnd (); switch (sbp->proc ()) { case AGENTCB_NULL: sbp->reply (NULL); break; case AGENTCB_AUTHINIT: authinit (sbp); break; case AGENTCB_AUTHMORE: sbp->replyref (sfsagent_auth_res (false)); break; case AGENTCB_LOOKUP: sfslookup (*sbp->template getarg (), wrap (lookupres, sbp)); break; case AGENTCB_REVOKED: revcheck (*sbp->template getarg (), wrap (revokedres, sbp)); break; case AGENTCB_CLONE: if (axprt_unix *ux = xprt2unix (x)) { int fd = ux->recvfd (); if (fd < 0) warn << "failed to receive fd for AGENTCB_CLONE\n"; else vNew sfsagent (fd); } sbp->reply (NULL); break; default: warn ("invalid AGENTCB procno %d\n", sbp->proc ()); sbp->reject (PROC_UNAVAIL); } } template void pushit (vec *vecp, const T &obj) { vecp->push_back (obj); } void sfsagent::ctldisp (svccb *sbp) { switch (sbp->proc ()) { case AGENTCTL_NULL: sbp->reply (NULL); break; case AGENTCTL_ADDKEY: { sfs_addkey_arg *aa = sbp->template getarg (); key *nk = New key; nk->k = rabin_priv::make (aa->p, aa->q); if (key *k = klookup (nk->k->n)) { keys.remove (k); delete k; } nk->expire = aa->expire; nk->comment = aa->comment; keys.insert_head (nk); key_timeout (); agentmsg (AGENT_START); sbp->replyref (true); break; } case AGENTCTL_REMKEY: { sfs_remkey_arg *ra = sbp->template getarg (); key *k; bool ok = false; switch (ra->type) { case SFS_REM_PUBKEY: if ((k = klookup (*ra->pubkey))) { ok = true; keys.remove (k); delete k; } break; case SFS_REM_COMMENT: while ((k = clookup (*ra->comment))) { ok = true; keys.remove (k); delete k; } break; } if (ok) agentmsg (AGENT_START); sbp->replyref (ok); break; } case AGENTCTL_REMALLKEYS: { while (key *k = keys.first) { keys.remove (k); delete k; } agentmsg (AGENT_START); sbp->replyref (NULL); // XXX race-prone break; } case AGENTCTL_DUMPKEYS: { sfs_keylist kl; sfs_keylist *klp = &kl; for (key *k = keys.first; k; k = k->link.next) { (*klp).alloc (); (*klp)->key = k->k->n; (*klp)->expire = k->expire; (*klp)->comment = k->comment; klp = &(*klp)->next; } sbp->reply (&kl); break; } case AGENTCTL_ADDCERTPROG: { sfsagent_certprog *arg = sbp->template getarg (); str av0; if (arg->av.size () > 0 && (av0 = find_program (arg->av[0]))) { arg->av[0] = av0; certprogs.push_back (*arg); certfilters.push_back (rxfilter (arg->filter, arg->exclude)); agentmsg (AGENT_FLUSHNEG); sbp->replyref (true); } else sbp->replyref (false); break; } case AGENTCTL_CLRCERTPROGS: certprogs.clear (); certfilters.clear (); agentmsg (AGENT_KILLSTART); // XXX sbp->reply (NULL); break; case AGENTCTL_DUMPCERTPROGS: { sfsagent_certprogs res; res.set (certprogs.base (), certprogs.size (), freemode::NOFREE); sbp->replyref (res); break; } case AGENTCTL_ADDREVOKEPROG: { sfsagent_revokeprog *arg = sbp->template getarg (); str av0; if (arg->av.size () > 0 && (av0 = find_program (arg->av[0]))) { arg->av[0] = av0; revokeprogs.push_back (*arg); if (arg->block) revokefilters.push_back (rxfilter (arg->block->filter, arg->block->exclude)); else revokefilters.push_back (); sbp->replyref (true); } else sbp->replyref (false); break; } case AGENTCTL_CLRREVOKEPROGS: revokeprogs.clear (); revokefilters.clear (); agentmsg (AGENT_KILLSTART); // XXX norevoke.clear (); sbp->reply (NULL); break; case AGENTCTL_DUMPREVOKEPROGS: { sfsagent_revokeprogs res; res.set (revokeprogs.base (), revokeprogs.size (), freemode::NOFREE); sbp->replyref (res); break; } case AGENTCTL_SETNOREVOKE: { sfsagent_norevoke_list *arg = sbp->template getarg (); for (sfs_hash *hid = arg->base (); hid < arg->lim (); hid++) norevoke.insert (*hid); sbp->reply (NULL); break; } case AGENTCTL_GETNOREVOKE: { vec revvec; norevoke.traverse (wrap (pushit, &revvec)); sfsagent_norevoke_list res; res.set (revvec.base (), revvec.size (), freemode::NOFREE); sbp->reply (&res); break; } case AGENTCTL_SYMLINK: agentmsg (AGENT_SYMLINK, sbp->getvoidarg ()); sbp->reply (NULL); break; case AGENTCTL_RESET: agentmsg (AGENT_KILLSTART); sbp->reply (NULL); break; case AGENTCTL_FORWARD: if (!opt_forwarding) sbp->replyref ((int32_t) EPERM); else if (name) sbp->replyref ((int32_t) EBUSY); else { setname (*sbp->template getarg ()); sbp->replyref ((int32_t) 0); } break; case AGENTCTL_REX: { sfsagent_rex_arg *prca = sbp->template getarg (); rex_connect (prca->dest, name, prca->forwardagent, wrap (rexres, sbp)); } break; case AGENTCTL_LISTSESS: { list_rexsess (sbp); break; } case AGENTCTL_KILLSESS: { sbp->replyref (kill_rexsess (*sbp->template getarg ())); break; } default: warn ("invalid AGENTCTL procno %d\n", sbp->proc ()); sbp->reject (PROC_UNAVAIL); break; } } void sfsagent::setname (str n) { if (!name) { name = n; ac = aclnt::alloc (x, agent_prog_1); as = asrv::alloc (x, agentcb_prog_1, wrap (this, &sfsagent::dispatch)); } } void agentmsg (u_int32_t proc, const void *arg) { static int32_t garbage_int; for (sfsagent *a = agents.first; a; a = agents.next (a)) if (a->ac) a->ac->call (proc, arg, &garbage_int, aclnt_cb_null); } static void ctlaccept (int lfd) { sockaddr_un sun; socklen_t sunlen = sizeof (sun); bzero (&sun, sizeof (sun)); int fd = accept (lfd, (sockaddr *) &sun, &sunlen); if (fd > 0) vNew sfsagent (fd); else if (errno != EAGAIN) warn ("ctlaccept: %m\n"); } static bool socket_bound; static void cleanup () { // XXX - race-prone when dying from "sfsagent -k" if (socket_bound) unlink (opt_socket); } static void start_listening_cb (int fd, int status) { if (status) close (fd); else if (listen (fd, 5) < 0) { warn ("not listening for sfskey: listen: %m\n"); close (fd); } else { socket_bound = true; fdcb (fd, selread, wrap (ctlaccept, fd)); } } static void start_listening () { if (!opt_socket) return; if (opt_socket == "-") { vNew sfsagent (0); return; } sockaddr_un sun; if (opt_socket.len () >= sizeof (sun.sun_path)) { warn ("not listening on socket: path too long: %s\n", opt_socket.cstr ()); return; } int ctlfd = socket (AF_UNIX, SOCK_STREAM, 0); if (ctlfd < 0) { warn ("not listening on socket: socket: %m\n"); return; } make_async (ctlfd); bzero (&sun, sizeof (sun)); sun.sun_family = AF_UNIX; strcpy (sun.sun_path, opt_socket); pid_t pid = afork (); if (pid == -1) { warn ("not listening on socket: fork: %m\n"); return; } else if (!pid) { umask (077); if (bind (ctlfd, (sockaddr *) &sun, sizeof (sun)) < 0) { if (errno == EADDRINUSE) unlink (sun.sun_path); if (bind (ctlfd, (sockaddr *) &sun, sizeof (sun)) < 0) { warn ("not listening on socket: %s: %m\n", sun.sun_path); err_flush (); _exit (1); } } err_flush (); _exit (0); } chldcb (pid, wrap (start_listening_cb, ctlfd)); } static void agent_daemonize (int status = 0) { if (status) fatal ("sfskey failed\n"); checkrnd (); int32_t err = EIO; if (opt_killstart && (cdagent->ac->scall (AGENT_KILLSTART, NULL, &err) || err)) fatal ("could not start agent: %s\n", strerror (err)); if (!opt_nodaemon) { switch (fork ()) { case -1: fatal ("fork: %m\n"); case 0: break; default: _exit (0); } if (setsid () < 0) fatal ("setsid: %m\n"); if (!runinplace) chdir ("/"); } start_listening (); } static void startagent (bool optc, int argc, char **argv) { char **arge = argv + argc; vec av; if (optc) { if (!argc) { agent_daemonize (); return; } av.push_back (find_program (*argv)); if (!av[0]) fatal ("cannot find program %s\n", *argv); argv++; } else { av.push_back (find_program ("sfskey")); if (!av[0]) fatal ("cannot find sfskey\n"); av.push_back ("add"); if (opt_keytime) { av.push_back ("-t"); av.push_back (opt_keytime); } } while (argv < arge) av.push_back (*argv++); putenv ("SFS_AGENTSOCK=-0"); ptr x = axprt_unix_spawnv (av[0], av); if (!x) fatal << av[0] << ": " << strerror (errno) << "\n"; chldcb (axprt_unix_spawn_pid, wrap (agent_daemonize)); vNew sfsagent (x); } static void usage () { warnx << "usage: " << progname << " [-S socket] [-dnkF] [-c [sfskey ...] | key-source]\n"; exit (1); } int main (int argc, char **argv) { setprogname (argv[0]); sfsconst_init (); if (!runinplace) { /* In any case, try to avoid core dumps */ #ifdef RLIMIT_CORE struct rlimit rlcore; if (getrlimit (RLIMIT_CORE, &rlcore) >= 0) { rlcore.rlim_cur = 0; setrlimit (RLIMIT_CORE, &rlcore); } #endif /* RLIMIT_CORE */ } bool opt_c = false, opt_n = false; int ch; while ((ch = getopt (argc, argv, "FS:cdknt:")) != -1) switch (ch) { case 'F': opt_forwarding = false; break; case 'S': opt_socket = optarg; break; case 'c': opt_c = true; break; case 'd': opt_nodaemon = true; break; case 'k': opt_killstart = true; break; case 'n': opt_n = true; break; case 't': opt_keytime = optarg; break; default: usage (); break; } argc -= optind; argv += optind; if (!runinplace && !opt_nodaemon) /* setuid makes ptrace fail on some OS's (a good thing, when we * hold private keys). */ setuid (getuid ()); if (opt_n) { if (!opt_socket) fatal ("-S option required with -n option\n"); random_set_seedfile ("~/.sfs/random_seed"); } else { int fd = suidgetfd_required ("agent"); sfsagent *a = cdagent = New sfsagent (fd); a->setname ("sfscd"); a->cs = NULL; int32_t err = EIO; if (!opt_killstart && (a->ac->scall (AGENT_START, NULL, &err) || err)) fatal ("could not start agent: %s\n", strerror (err)); sfsagent_seed seed; if (a->ac->scall (AGENT_RNDSEED, NULL, &seed)) fatal ("I/O error from sfscd\n"); rnd_input.update (seed.base (), seed.size ()); bzero (seed.base (), seed.size ()); random_update (); // Unnecessary paranoia - done in checkrnd() } getsysnoise (&rnd_input, wrap (setrnd)); sigcb (SIGTERM, wrap (exit, 1)); startagent (opt_c, argc, argv); amain (); }