/* $Id: rexd.C,v 1.20 2001/11/12 20:02:42 kaminsky Exp $ */ /* * * Copyright (C) 2000-2001 Eric Peterson (ericp@lcs.mit.edu) * Copyright (C) 2000-2001 Michael Kaminsky (kaminsky@lcs.mit.edu) * Copyright (C) 2000 David Mazieres (dm@uun.org) * * 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 "arpc.h" #include "rex_prot.h" #include "crypt.h" #include "sfsserv.h" #define REXSESS_DEFAULT_PATH "/bin:/sbin:/usr/bin:/usr/sbin" ptr sk; sfs_servinfo servinfo; str newaid; class rexsess { ref destroyed; rex_sesskeydat kscdat; rex_sesskeydat kcsdat; seqcheck seqstate; ptr x; ptr c; static void postfork (const sfsauth_cred *credp); void ctlconnect (ref abort, ref si, ref xs); void seq2sessinfo (u_int64_t seqno, sfs_hash *sidp, sfs_sessinfo *sip); void eof () { delete this; } public: sfs_hash sessid; ihash_entry link; rexsess (const sfsauth_cred *credp, const rexd_spawn_arg *argp, rexd_spawn_res *resp); ~rexsess (); void attach (svccb *sbp); }; ihash sesstab; rexsess::rexsess (const sfsauth_cred *credp, const rexd_spawn_arg *argp, rexd_spawn_res *resp) : destroyed (New refcounted (false)), seqstate (32) { resp->set_err (SFS_OK); rnd.getbytes (resp->resok->kmsg.kcs_share.base (), resp->resok->kmsg.kcs_share.size ()); rnd.getbytes (resp->resok->kmsg.ksc_share.base (), resp->resok->kmsg.ksc_share.size ()); kcsdat.type = SFS_KCS; kcsdat.sshare = resp->resok->kmsg.kcs_share; kcsdat.cshare = argp->kmsg.kcs_share; kscdat.type = SFS_KSC; kscdat.sshare = resp->resok->kmsg.ksc_share; kscdat.cshare = argp->kmsg.ksc_share; seqstate.check (0); seq2sessinfo (0, &sessid, NULL); sesstab.insert (this); vec av; av.push_back (newaid); if (credp->type == SFS_UNIXCRED) av.push_back (strbuf ("-U%d", credp->unixcred->uid)); else av.push_back (strbuf ("-U-2")); av.push_back ("-G"); av.push_back ("--"); if (argp->command.size ()) { av.push_back (fix_exec_path (argp->command[0])); for (size_t i = 1; i < argp->command.size (); i++) av.push_back (argp->command[i]); } av.push_back (NULL); /* Set up environment just for exec */ const char *evarstosave[] = { #ifdef MAINTAINER "SFS_RELEASE", "SFS_RUNINPLACE", "SFS_HASHCOST", "SFS_PORT", "ASRV_TRACE", "ACLNT_TRACE", #endif /* MAINTAINER */ NULL }; vec envs; for (int v = 0; evarstosave[v]; v++) { char *val = getenv (evarstosave[v]); if (val) envs.push_back (strbuf () << evarstosave[v] << "=" << val); } envs.push_back (strbuf () << "USER=" << credp->unixcred->username); envs.push_back (strbuf () << "LOGNAME=" << credp->unixcred->username); envs.push_back (strbuf () << "HOME=" << credp->unixcred->homedir); envs.push_back (strbuf () << "SHELL=" << credp->unixcred->shell); envs.push_back (strbuf () << "PATH=" REXSESS_DEFAULT_PATH); vec env; for (const str *s = envs.base (), *e = envs.lim (); s < e; s++) env.push_back (const_cast (s->cstr ())); env.push_back (NULL); x = axprt_unix_aspawnv (newaid, av, 0, wrap (&postfork, credp), env.base ()); x->allow_recvfd = false; c = aclnt::alloc (x, rexctl_prog_1); c->seteofcb (wrap (this, &rexsess::eof)); } rexsess::~rexsess () { *destroyed = true; bzero (&kscdat, sizeof (kscdat)); bzero (&kcsdat, sizeof (kcsdat)); sesstab.remove (this); } void rexsess::seq2sessinfo (u_int64_t seqno, sfs_hash *sidp, sfs_sessinfo *sip) { kcsdat.seqno = seqno; kscdat.seqno = seqno; sfs_sessinfo si; si.type = SFS_SESSINFO; si.kcs.setsize (sha1::hashsize); sha1_hashxdr (si.kcs.base (), kcsdat, true); si.ksc.setsize (sha1::hashsize); sha1_hashxdr (si.ksc.base (), kscdat, true); if (sidp) sha1_hashxdr (sidp->base (), si, true); if (sip) *sip = si; bzero (si.kcs.base (), si.kcs.size ()); bzero (si.ksc.base (), si.ksc.size ()); } void rexsess::postfork (const sfsauth_cred *credp) { if (credp->type != SFS_UNIXCRED) fatal ("setpriv: invalid credential type %d\n", int (credp->type)); GETGROUPS_T groups[NGROUPS_MAX]; size_t ngroups = min (credp->unixcred->groups.size () + 1, NGROUPS_MAX); groups[0] = credp->unixcred->gid; for (size_t i = 1; i < ngroups; i++) groups[i] = credp->unixcred->groups[i - 1]; /* No setuid. This must happen in an execed process, because we are * too paranoid about ptrace, signals, core dumps, etc. (given how * many private keys we have in memory). */ if (setgroups (ngroups, groups) < 0) fatal ("setgroups: %m\n"); if (setgid (groups[0]) < 0) fatal ("setgid: %m\n"); if (chdir (credp->unixcred->homedir) < 0) warn << "Could not chdir to home directory " << credp->unixcred->homedir << ": " << strerror (errno) << "\n"; /* XXX - need to reduce hard limit on file descriptors. */ } void rexsess::attach (svccb *sbp) { // XXX - dynamic_cast is busted in egcs axprt_stream *xsp = static_cast (sbp->getsrv ()->xprt ().get ()); ref xs (mkref (xsp)); /* XXX - Note that what we are doing here does not pipeline. If a * cilent sends a REXD_ATTACH RPC followed by another RPC (before * getting thre reply from the REXD_ATTACH), we may end up reading * both RPC's and discarding the second one (because we pass the * file descriptor off to the child process and discard any extra * data we have read and buffered). */ xhinfo::xon (xs, false); rexd_attach_arg *argp = sbp->template getarg (); sfs_hash sid; ref si = New refcounted; seq2sessinfo (argp->seqno, &sid, si); if (sid == argp->newsessid && seqstate.check (argp->seqno)) { sbp->replyref (rexd_attach_res (SFS_OK)); xs->setwcb (wrap (this, &rexsess::ctlconnect, destroyed, si, xs)); } else { bzero (si->kcs.base (), si->kcs.size ()); bzero (si->ksc.base (), si->ksc.size ()); warn ("newsessid mistatch\n"); sbp->replyref (rexd_attach_res (SFS_BADLOGIN)); } } void rexsess::ctlconnect (ref abort, ref si, ref xs) { int fd = xs->reclaim (); if (*abort || fd < 0) { bzero (si->kcs.base (), si->kcs.size ()); bzero (si->ksc.base (), si->ksc.size ()); if (fd >= 0) close (fd); return; } x->sendfd (fd); // XXX - c->call may leave un-bzeroed copies of session key around c->call (REXCTL_CONNECT, si, NULL, aclnt_cb_null); bzero (si->kcs.base (), si->kcs.size ()); bzero (si->ksc.base (), si->ksc.size ()); } struct rexclient : public sfsserv { ptr rexs; rexclient (ref x) : sfsserv (x), rexs (asrv::alloc (x, rexd_prog_1, wrap (this, &rexclient::dispatch))) {} ptr doconnect (const sfs_connectarg *ci, sfs_servinfo *si) { *si = servinfo; return ::sk; } void dispatch (svccb *sbp); }; void rexclient::dispatch (svccb *sbp) { if (!sbp) { delete this; return; } switch (sbp->proc ()) { case REXD_NULL: sbp->reply (NULL); break; case REXD_SPAWN: { u_int32_t authno = sbp->getaui (); if (authno >= credtab.size () || credtab[authno].type != SFS_UNIXCRED) { sbp->reject (AUTH_BADCRED); break; } rexd_spawn_res res; vNew rexsess (&credtab[authno], sbp->template getarg (), &res); sbp->replyref (res); break; } case REXD_ATTACH: { rexd_attach_arg *argp = sbp->template getarg (); if (rexsess *sp = sesstab[argp->sessid]) { sp->attach (sbp); delete this; } else sbp->replyref (rexd_attach_res (SFS_BADLOGIN)); break; } default: sbp->reject (PROC_UNAVAIL); break; } } void client_accept (ptr x) { if (!x) fatal ("EOF from sfssd\n"); vNew rexclient (x); } static void loadkey (const char *path) { if (!path) path = "sfs_host_key"; str keyfile = sfsconst_etcfile (path); if (!keyfile) fatal << path << ": " << strerror (errno) << "\n"; str key = file2wstr (keyfile); if (!key) fatal << keyfile << ": " << strerror (errno) << "\n"; sk = import_rabin_priv (key, NULL); if (!sk) fatal << "could not decode " << keyfile << "\n"; } static void usage () { fatal << "usage: " << progname << " [-k keyfile]\n"; } int main (int argc, char **argv) { const char *keyfile = NULL; setprogname (argv[0]); sfsconst_init (); servinfo.host.hostname = ""; int ch; while ((ch = getopt (argc, argv, "k:h:")) != -1) switch (ch) { case 'k': keyfile = optarg; break; case 'h': servinfo.host.hostname = optarg; break; default: usage (); } if (optind < argc) usage (); warn ("version %s, pid %d\n", VERSION, getpid ()); loadkey (keyfile); servinfo.release = SFS_RELEASE; servinfo.host.type = SFS_HOSTINFO; if (servinfo.host.hostname == "" && !(servinfo.host.hostname = myname ())) fatal ("could not get my host name\n"); servinfo.host.pubkey = sk->n; servinfo.prog = REXD_PROG; servinfo.vers = REXD_VERS; warn << "serving " << sfs_hostinfo2path (servinfo.host) << "\n"; newaid = fix_exec_path ("newaid"); if (!newaid) fatal ("could not find newaid (should be in %s)\n", execdir.cstr ()); str ptydpath = fix_exec_path ("ptyd"); if (!ptydpath) warn ("could not find ptyd (should be in %s)\n", execdir.cstr ()); else { char *av[2] = { "ptyd", NULL }; int pid = spawn (ptydpath, av); if (pid < 0) warn << ptydpath << ": " << strerror (errno) << "\n"; else warn << "spawning " << ptydpath << "\n"; } sfssd_slave (wrap (client_accept)); amain (); }