/* $Id: agentrex.C,v 1.38 2002/11/27 18:51:05 kaminsky Exp $ */ /* * * Copyright (C) 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 "aios.h" #include "sfsmisc.h" #include "sfsconnect.h" #include "list.h" #include "agent.h" #include "rex_prot.h" #include "rex.h" class agentstartfd: public rexfd { str schost; // schost where first rexconnect came from cbi succeedcb; bool waitnewfd; void agentstarted (ref resp, str schost, cbi succeedcb, clnt_stat err) { if (*resp || err) { warn << "could not start agent on "<< schost << " : "; if (err) warn << err << "\n"; else warn << strerror (*resp) << "\n"; // should still succeed even if there's an agent already running succeedcb (1); } else { succeedcb (0); warn << "agent forwarding connection started\n"; } } public: agentstartfd (rexchannel *pch, int fd, cbi succeedcb, str schost): rexfd (pch, fd), schost (schost), succeedcb (succeedcb), waitnewfd (true) {} virtual void newfd (svccb *sbp) { rexcb_newfd_arg *argp = sbp->template getarg (); waitnewfd = false; int s[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, s)) { warn << "error creating socketpair for agent forwarding"; sbp->replyref(false); return; } make_async (s[1]); make_async (s[0]); sfsagent *a = New sfsagent (s[1]); a->setname (schost); a->cs = NULL; ref resp = New refcounted ; a->ac->call (AGENT_START, NULL, resp, wrap (this, &agentstartfd::agentstarted, resp, schost, succeedcb)); vNew refcounted (pch, argp->newfd, s[0]); sbp->replyref (true); } virtual void data (svccb *sbp) { rex_payload *argp = sbp->template getarg (); if (waitnewfd && !argp->data.size ()) { warn ("agent forward channel failure: EOF from suidconnect agent\n"); warn ("NOTE: sfscd must be running on the remote machine for agent " "forwarding to work.\n"); succeedcb (1); rexfd::data (sbp); return; } } }; class agentchannel: public rexchannel { str schost; cbi succeedcb; public: agentchannel (rexsession *sess, vec command, str schost, cbi succeedcb) : rexchannel (sess, 1, command), schost (schost), succeedcb (succeedcb) {} void madechannel (int error) { if (error) { //should probably still succeed even if we can't run "suidconnect agent" succeedcb (1); } else { vNew refcounted (this, 0, succeedcb, schost); } } }; class rexsess { ptr sessconn; ptr sessclnt; ptr sfsclnt; ptr rexclnt; sfs_sessinfo sessinfo; rex_sesskeydat kscdat; rex_sesskeydat kcsdat; u_int32_t myauthno; sfs_seqno seqno; sfsagent_authinit_arg *aarg; ptr ares; sfs_loginres *lres; bool inprogress; bool afpending; // agent forwarding channel is being made vec afcbq; vec cbq; rexsession *sess; void fail (); void sessinit_done (int error); void attached (rexd_attach_res *resp, clnt_stat err); void attach (); void spawned (rexd_spawn_res *resp, clnt_stat err); void spawn (); void loggedin (clnt_stat err); void dologin (clnt_stat err); void signauthreq (); void connected (ptr sc, str err); void seq2sessinfo (u_int64_t seqno, sfs_hash *sidp, sfs_sessinfo *sip); void eof () { // todo: we need to be able to distinguish these warn ("either agent channel closed or EOF from proxy on %s\n", path.cstr ()); delete this; } public: bool forwardagent; // rex client requested agent forwarding bool agentforwarded; // agent forwarding has been established str path; str rexconnect_origin; ptr cbres; ihash_entry link; void reply (cb_rex::ptr cb); void agentf_init_done (int err); void succeed (cb_rex::ptr cb, int error = 0); void abort () { sess->abort (); } rexsess (str path, str frompath, bool forwardagent, cb_rex::ptr cb); ~rexsess (); }; ihash sesstab; 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 ()); } rexsess::rexsess (str path, str pathfrom, bool forwardagent, cb_rex::ptr firstcb) : aarg (NULL), lres (NULL), inprogress (true), afpending (false), forwardagent (forwardagent), agentforwarded (false), path (path), rexconnect_origin (pathfrom) { cbq.push_back (firstcb); myauthno = 0; seqno = 1; sfs_connect_path (path, SFS_REX, wrap (this, &rexsess::connected)); } rexsess::~rexsess () { bzero (&kscdat, sizeof (kscdat)); bzero (&kcsdat, sizeof (kcsdat)); if (cbres) { bzero (&cbres->resok->kcs, sizeof (cbres->resok->kcs)); bzero (&cbres->resok->ksc, sizeof (cbres->resok->ksc)); } if (sesstab[path]) sesstab.remove (this); if (aarg) delete aarg; if (lres) delete lres; } void rexsess::fail () { sessinit_done (1); } void rexsess::sessinit_done (int error) { inprogress = false; int cbql = cbq.size (); for (int c = 0; c < cbql; c++) succeed (cbq[c], error); cbq.clear (); if (error) delete this; else sess->setendcb (wrap (this, &rexsess::eof)); } void rexsess::reply (cb_rex::ptr cb) { cbres->resok->seqno = ++seqno; cb (cbres); } void rexsess::agentf_init_done (int err) { afpending = false; agentforwarded = (err == 0); int afcbql = afcbq.size (); for (int c = 0; c < afcbql; c++) reply (afcbq[c]); afcbq.clear (); } void rexsess::succeed (cb_rex::ptr cb, int error) { if (inprogress) cbq.push_back (cb); else { if (error) cb (New refcounted (false)); else { if (forwardagent && !agentforwarded) { afcbq.push_back (cb); if (!afpending) { afpending = true; vec suidcommand; suidcommand.setsize (2); suidcommand[0] = "suidconnect"; suidcommand[1] = "agent"; sess->makechannel (New refcounted (sess, suidcommand, path, wrap (this, &rexsess::agentf_init_done))); } } else reply (cb); } } } void rexsess::attached (rexd_attach_res *resp, clnt_stat err) { if (err) { warn << "FAILED (" << err << ")\n"; } else if (*resp != SFS_OK) { // XXX warn << "FAILED (proxy attach err " << int (*resp) << ")\n"; } delete resp; warnx << "attached to proxy\n"; sessconn->x = axprt_crypt::alloc (sessconn->x->reclaim ()); sessconn->x->encrypt (sessinfo.kcs.base (), sessinfo.kcs.size (), sessinfo.ksc.base (), sessinfo.ksc.size ()); sesstab.insert (this); sess = New rexsession (path, sessconn->x); sessinit_done (0); } void rexsess::attach () { rexd_attach_arg arg; arg.seqno = seqno++; seq2sessinfo (0, &arg.sessid, NULL); seq2sessinfo (arg.seqno, &arg.newsessid, &sessinfo); rexd_attach_res *resp = New rexd_attach_res; sessclnt->call (REXD_ATTACH, &arg, resp, wrap (this, &rexsess::attached, resp)); } void rexsess::spawned (rexd_spawn_res *resp, clnt_stat err) { if (err) { warn << "REXD_SPAWN proxy RPC FAILED (" << err << ")\n"; fail (); return; } else if (resp->err != SFS_OK) { // XXX warn << "FAILED (spawn proxy err " << int (resp->err) << ")\n"; fail (); return; } warnx << "spawned proxy\n"; kcsdat.sshare = resp->resok->kmsg.kcs_share; kscdat.sshare = resp->resok->kmsg.ksc_share; delete resp; cbres = New refcounted (true); cbres->resok->kcs.kcs_share = kcsdat.cshare; cbres->resok->kcs.ksc_share = kcsdat.sshare; cbres->resok->ksc.kcs_share = kscdat.cshare; cbres->resok->ksc.ksc_share = kscdat.sshare; attach (); } void rexsess::spawn () { rexd_spawn_arg arg; rnd.getbytes (arg.kmsg.kcs_share.base (), arg.kmsg.kcs_share.size ()); rnd.getbytes (arg.kmsg.ksc_share.base (), arg.kmsg.ksc_share.size ()); kcsdat.type = SFS_KCS; kcsdat.cshare = arg.kmsg.kcs_share; kscdat.type = SFS_KSC; kscdat.cshare = arg.kmsg.ksc_share; arg.command.setsize (1); arg.command[0] = "proxy"; rexd_spawn_res *resp = New rexd_spawn_res; sessclnt->call (REXD_SPAWN, &arg, resp, wrap (this, &rexsess::spawned, resp), authuint_create (myauthno)); } void rexsess::loggedin (clnt_stat err) { if (err) { warn << "loggedin: error\n"; fail (); return; } if (!lres) { warn << "loggedin: lres is NULL error\n"; fail (); return; } switch (lres->status) { case SFSLOGIN_OK: myauthno = *lres->authno; break; case SFSLOGIN_BAD: aarg->ntries++; signauthreq (); return ; #if 0 case SFSLOGIN_MORE: { sfscd_agentreq_arg arg; arg.aid = aid; arg.agentreq.set_type (AGENTCB_AUTHMORE); arg.agentreq.more->authinfo = sp->authinfo; arg.agentreq.more->seqno = seqno; arg.agentreq.more->challenge = *sres.resmore; cbase = cdc->call (SFSCDCBPROC_AGENTREQ, &arg, &ares, wrap (this, &userauth::aresult)); break; } case SFSLOGIN_ALLBAD: finish (0); break; #endif default: warn << "userauth: bad status in loginres!\n"; fail (); return; } delete lres; lres = NULL; delete aarg; aarg = NULL; ares = NULL; spawn (); } void rexsess::dologin (clnt_stat err) { if (err) { warn << "dologin: " << err << "\n"; aarg->ntries++; signauthreq (); } else if (!ares->authenticate) { warn << "dologin: no certificate\n"; aarg->ntries++; signauthreq (); } else { sfs_loginarg larg; larg.seqno = 1; larg.certificate = *ares->certificate; if (!lres) lres = New sfs_loginres; sfsclnt->call (SFSPROC_LOGIN, &larg, lres, wrap (this, &rexsess::loggedin)); } } void rexsess::signauthreq () { assert (aarg->authinfo.type == SFS_AUTHINFO); authmeth *k = gmgr.lookup_by_index (aarg->ntries); if (!k) { warn ("exhausted all possible keys\n"); fail (); return ; } if (!ares) ares = New refcounted (true); k->authwarn (aarg); k->authinit (aarg, ares, wrap (this, &rexsess::dologin, RPC_SUCCESS)); } void rexsess::connected (ptr sc, str err) { if (!sc) { warn << path << ": FAILED (" << err << ")\n"; fail (); return; } sessconn = sc; sessclnt = aclnt::alloc (sc->x, rexd_prog_1); sfsclnt = aclnt::alloc (sc->x, sfs_program_1); // sessclnt->seteofcb (wrap (this, &rexsess::eof)); if (!aarg) aarg = New sfsagent_authinit_arg (); aarg->ntries = 0; aarg->requestor = "rex"; aarg->seqno = 1; aarg->authinfo.type = SFS_AUTHINFO; aarg->authinfo.service = SFS_REX; aarg->authinfo.name = sc->servinfo->get_hostname (); aarg->authinfo.hostid = sc->hostid; aarg->authinfo.sessid = sc->sessid; signauthreq (); } void rex_connect (str path, str pathfrom, bool forwardagent, cb_rex::ptr cb) { if (!pathfrom) pathfrom = myname (); if (rexsess *sp = sesstab[path]) { warn << "rexsess: hash lookup for " << path << " succeeded from " << pathfrom << "\n"; if (forwardagent) sp->forwardagent = true; sp->succeed (cb); } else vNew rexsess (path, pathfrom, forwardagent, cb); } static void print_rexsess (vec *psv, rexsess *sp) { rex_sessentry se; se.to = sp->path; se.created_from = sp->rexconnect_origin; se.agentforwarded = sp->agentforwarded; psv->push_back (se); } void list_rexsess (svccb *sbp) { vec sv; sesstab.traverse (wrap (print_rexsess, &sv)); rex_sessvec rsv; rsv.setsize (sv.size ()); for (size_t i = 0; i < sv.size (); i++) rsv[i] = sv[i]; sbp->replyref (rsv); } bool kill_rexsess (str path) { vec sv; sesstab.traverse (wrap (print_rexsess, &sv)); int found = 0; str foundstr; for (size_t i = 0; i < sv.size (); i++) if (sv[i].to.len () >= path.len () && !memcmp (sv[i].to, path, path.len ())) { found++; foundstr = sv[i].to; } if (found == 0) { warn ("%s does not specify a valid rex session to remove.\n", path.cstr ()); return false; } else if (found >= 2) { warn ("%s does not specify a unique rex session to remove.\n" "Please use a longer prefix\n", path.cstr ()); return false; } else if (rexsess *sp = sesstab[foundstr]) { warn ("removing rexsession connected to %s.\n", foundstr.cstr ()); sp->abort (); delete sp; return true; } return false; /* make GCC 3.0.4 happy with this unreachable code */ }