/* $Id: sfsclient.C,v 1.15 2001/06/28 04:47:23 dm Exp $ */ /* * * 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 "sfsclient.h" #include "axprt_crypt.h" #include "sfscd_prot.h" #include "nfstrans.h" #include "crypt.h" #define SFSPREF ".SFS " #define SFSFH SFSPREF "FH" time_t mount_idletime = 300; static str srvarg2str (const sfsserverargs &a) { if (a.ma->carg.civers >= 5) return a.ma->carg.ci5->sname; int port = sfs_port; struct sockaddr_in sin; socklen_t sinlen = sizeof (sin); bzero (&sin, sizeof (sin)); if (!getpeername (a.fd, (sockaddr *) &sin, &sinlen)) port = ntohs (sin.sin_port); strbuf path; if (port != sfs_port) path << port << "@"; path << a.ma->carg.ci4->name << ":" << armor32 (a.ma->carg.ci4->hostid.base (), sfs_hash::nelm); return path; } sfsserver::sfsserver (const sfsserverargs &a) : lock_flag (true), condemn_flag (false), destroyed (false), recon_backoff (1), recon_tmo (NULL), ns (a.ns), prog (*a.p), path (srvarg2str (a)), carg (a.ma->carg), hinfo (a.ma->cres->servinfo.host), fsinfo (NULL), lastuse (timenow) { refcount_inc (); authinfo.type = SFS_AUTHINFO; authinfo.service = carg.civers <= 4 ? carg.ci4->service : carg.ci5->service; bool pathok = sfs_parsepath (path, &authinfo.name, &authinfo.hostid); assert (pathok); prog.pathtab.insert (this); prog.idleq.insert_tail (this); waitq.push_back (wrap (this, &sfsserver::retrootfh, a.cb)); if (a.fd >= 0) { setfd (a.fd); // Yuck... can't call virtual function from supertype constructor delaycb (0, wrap (this, &sfsserver::crypt, *a.ma->cres, wrap (mkref (this), &sfsserver::getsessid))); } else reconnect_0 (); ns->setcb (wrap (this, &sfsserver::getnfscall)); } sfsserver::~sfsserver () { if (fsinfo) fsinfo_free (fsinfo); prog.pathtab.remove (this); prog.idleq.remove (this); } void sfsserver::setfd (int fd) { assert (fd >= 0); x = axprt_crypt::alloc (fd); // XXX - shouldn't always be crypt sfsc = aclnt::alloc (x, sfs_program_1); sfscbs = asrv::alloc (x, sfs_program_1, wrap (this, &sfsserver::sfsdispatch)); } void sfsserver::touch () { lastuse = timenow; prog.idleq.remove (this); prog.idleq.insert_tail (this); } void sfsserver::reconnect () { if (!locked ()) { lock (); assert (!recon_tmo); if (x) { sfsc = NULL; sfscbs = NULL; close (x->reclaim ()); x = NULL; } flushstate (); // prog.cdc->call (SFSCDCBPROC_HIDEFS, &path, NULL, aclnt_cb_null); reconnect_0 (); } } void sfsserver::reconnect_0 () { recon_tmo = NULL; str hname; u_int16_t port; if (!sfs_parsepath (path, &hname, NULL, &port)) panic << path << ": cannot parse\n"; tcpconnect (hname, port, wrap (mkref (this), &sfsserver::reconnect_1), false); } void sfsserver::reconnect_1 (int fd) { if (condemned ()) { close (fd); unlock (); } else if (fd < 0) { close (fd); connection_failure (); } else { setfd (fd); ref cres = New refcounted; sfsc->call (SFSPROC_CONNECT, &carg, cres, wrap (mkref (this), &sfsserver::reconnect_2, cres)); } } void sfsserver::reconnect_2 (ref cres, clnt_stat err) { if (condemned ()) unlock (); else if (err) connection_failure (); else if (cres->status == SFS_REDIRECT) connection_failure (true); else if (cres->status) connection_failure (); else if (cres->reply->servinfo.host.pubkey != hinfo.pubkey || cres->reply->servinfo.host.hostname != hinfo.hostname || !sfs_ckpath (path, cres->reply->servinfo.host)) { warn << path << ": server has changed public key\n"; connection_failure (true); } else crypt (*cres->reply, wrap (mkref (this), &sfsserver::getsessid)); } void sfsserver::getsessid (const sfs_hash *sessid) { if (condemned ()) unlock (); else if (!sessid) connection_failure (); else { authinfo.sessid = *sessid; sfs_fsinfo *fsi = fsinfo_alloc (); sfsc->call (SFSPROC_GETFSINFO, NULL, fsi, wrap (mkref (this), &sfsserver::getfsinfo, fsi), NULL, NULL, fsinfo_marshall ()); } } void sfsserver::getfsinfo (sfs_fsinfo *fsi, clnt_stat err) { nfs_fh3 fh (rootfh); if (condemned ()) unlock (); else if (err || !fsi) connection_failure (); else if (!setrootfh (fsi) || (fsinfo && fh.data != rootfh.data)) connection_failure (true); else { recon_backoff = 1; if (fsinfo) { warn << "Reconnected to " << path << "\n"; prog.cdc->call (SFSCDCBPROC_SHOWFS, &path, NULL, aclnt_cb_null); fsinfo_free (fsinfo); } fsinfo = fsi; initstate (); unlock (); return; } fsinfo_free (fsi); } void sfsserver::connection_failure (bool permanent) { if (!fsinfo) { // XXX - doesn't work - what if GETFSINFO RPC itself fails condemn (); unlock (); destroy (); } else if (permanent) { warn << "Must remount " << path << "\n"; condemn (); prog.cdc->call (SFSCDCBPROC_DELFS, &path, NULL, aclnt_cb_null); unlock (); } else { if (recon_backoff < 64) recon_backoff <<= 1; prog.cdc->call (SFSCDCBPROC_HIDEFS, &path, NULL, aclnt_cb_null); recon_tmo = delaycb (recon_backoff, 0, wrap (mkref (this), &sfsserver::reconnect_0)); } } void sfsserver::unlock () { assert (lock_flag); lock_flag = false; vec q; q.swap (waitq); while (!q.empty ()) (*q.pop_front ()) (); } void sfsserver::sfsdispatch (svccb *sbp) { if (!sbp) { if (!condemned ()) { warn << "EOF from " << path << "\n"; reconnect (); } return; } switch (sbp->proc ()) { case SFSCBPROC_NULL: sbp->reply (NULL); break; default: sbp->reject (PROC_UNAVAIL); break; } } void sfsserver::retrootfh (fhcb cb) { nfs_fh3 fh (rootfh); if (condemned ()) (*cb) (NULL); else if (!ns->encodefh (fh)) { (*cb) (NULL); destroy (); } else (*cb) (&fh); } void sfsserver::condemn () { if (!condemn_flag) { condemn_flag = true; if (recon_tmo) { timecb_remove (recon_tmo); recon_tmo = NULL; unlock (); } flushstate (); vec q; q.swap (waitq); while (!q.empty ()) (*q.pop_front ()) (); } } void sfsserver::destroy () { assert (!destroyed); destroyed = true; condemn(); refcount_dec (); } void sfsserver::getnfscall (nfscall *nc) { if (condemned ()) { nc->error (NFS3ERR_STALE); return; } if (locked () || !x || x->ateof ()) { waitq.push_back (wrap (this, &sfsserver::getnfscall, nc)); return; } if (nc->proc () == NFSPROC3_NULL) { nc->reply (NULL); return; } if (nc->proc () != NFSPROC3_GETATTR || nc->template getarg ()->data != rootfh.data) { touch (); if (!authok (nc)) return; } if (!prog.intercept (this, nc)) dispatch (nc); } void sfsserver::crypt (sfs_connectok cres, sfsserver::crypt_cb cb) { sfs_hash h; (*cb) (&h); } sfsprog::sfsprog (ref cdx, sfsprog::allocfn_t f, bool ncl) : newserver (f), needclose (ncl), x (cdx), cdc (aclnt::alloc (x, sfscdcb_program_1)), cds (asrv::alloc (x, sfscd_program_1, wrap (this, &sfsprog::cddispatch))), ns (New refcounted), nd (New refcounted (New refcounted (ns))), linkserv (nd->servalloc ()), idletmo (NULL) { linkserv->setcb (wrap (this, &sfsprog::linkdispatch)); } void sfsprog::cddispatch (svccb *sbp) { if (!sbp) fatal ("EOF from sfscd\n"); switch (sbp->proc ()) { case SFSCDPROC_NULL: sbp->reply (NULL); break; case SFSCDPROC_INIT: sfs_suidserv (sbp->template getarg ()->name, wrap (this, &sfsprog::ctlaccept)); sbp->reply (NULL); break; case SFSCDPROC_MOUNT: { sfscd_mountarg *arg = sbp->template getarg (); ref nns = nd->servalloc (); if (needclose) nns = close_simulate (nns); int fd = arg->cres ? x->recvfd () : -1; newserver (this, nns, fd, arg, wrap (this, &sfsprog::mountcb, sbp)); tmosched (); break; } case SFSCDPROC_UNMOUNT: if (sfsserver *s = pathtab[*sbp->template getarg ()]) s->destroy (); sbp->reply (NULL); break; case SFSCDPROC_FLUSHAUTH: { sfs_aid aid = *sbp->template getarg (); for (sfsserver *s = pathtab.first (); s; s = pathtab.next (s)) s->authclear (aid); sbp->reply (NULL); break; } case SFSCDPROC_CONDEMN: if (sfsserver *s = pathtab[*sbp->template getarg ()]) s->condemn (); sbp->reply (NULL); break; default: sbp->reject (PROC_UNAVAIL); break; } } void sfsprog::tmosched (bool expired) { if (expired) idletmo = NULL; sfsserver *si; while ((si = idleq.first)) { if (si->lastuse + mount_idletime > timenow) { if (!idletmo) idletmo = delaycb (si->lastuse + mount_idletime - timenow, wrap (this, &sfsprog::tmosched, true)); return; } if (!si->locked () && !si->condemned ()) cdc->call (SFSCDCBPROC_IDLE, &si->path, NULL, aclnt_cb_null); si->touch (); } } void sfsprog::mountcb (svccb *sbp, const nfs_fh3 *fhp) { sfscd_mountres res (NFS_OK); if (fhp) { res.reply->mntflags = NMOPT_NFS3; res.reply->fh = fhp->data; x->sendfd (ns->getfd (), false); } else res.set_err (EIO); sbp->reply (&res); } void sfsprog::ctlaccept (ptr x, const authunix_parms *aup) { if (x && !x->ateof ()) vNew sfsctl (x, aup, this); } static void mklnkfattr (fattr3exp *f, const nfs_fh3 *fh) { bzero (f, sizeof (*f)); f->type = NF3LNK; f->mode = 0444; f->nlink = 1; f->gid = sfs_gid; f->used = 0; f->fileid = 1; f->size = 2 * fh->data.size (); } void sfsprog::linkdispatch (nfscall *nc) { static const char hexchars[] = "0123456789abcdef"; switch (nc->proc ()) { case NFSPROC3_GETATTR: { nfs_fh3 *arg = nc->template getarg (); getattr3res res (NFS3_OK); mklnkfattr (res.attributes.addr (), arg); nc->reply (&res); break; } case NFSPROC3_READLINK: { nfs_fh3 *arg = nc->template getarg (); readlink3res res (NFS3_OK); res.resok->symlink_attributes.set_present (true); mklnkfattr (res.resok->symlink_attributes.attributes.addr (), arg); mstr m (2 * arg->data.size ()); for (size_t i = 0; i < arg->data.size (); i++) { u_char b = arg->data[i]; m[2*i] = hexchars[b>>4]; m[2*i+1] = hexchars[b&0xf]; } res.resok->data = m; nc->reply (&res); break; } default: nc->error (NFS3ERR_ACCES); break; } } bool sfsprog::intercept (sfsserver *s, nfscall *nc) { switch (nc->proc ()) { case NFSPROC3_SETATTR: { setattr3args *sar = nc->template getarg (); sattr3 &sa = sar->new_attributes; if (sa.mode.set || sa.size.set || sa.atime.set || sa.mtime.set || !sa.uid.set || !sa.gid.set || *sa.uid.val != (u_int32_t) -2) return false; if (sfsctl *sc = ctltab (nc->getaid (), *sa.gid.val)) sc->fip = New refcounted (s, sar->object); nc->error (NFS3ERR_PERM); return true; } case NFSPROC3_LOOKUP: { diropargs3 *arg = nc->template getarg (); if (strncmp (arg->name, SFSPREF, sizeof (SFSPREF) - 1)) return false; lookup3res res (NFS3_OK); res.resok->obj_attributes.set_present (true); if (arg->name == SFSFH) { res.resok->object = arg->dir; mklnkfattr (res.resok->obj_attributes.attributes.addr (), &arg->dir); linkserv->encodefh (res.resok->object); nc->reply (&res, xdr_lookup3res); return true; } return false; } default: return false; break; } } /* We have no idea what type aup.aup_gids is (could be gid_t, * u_int32_t, etc.) Rather than autoconf it, just use templates to * work around the problem. */ template inline void domalloc (T *&tp, size_t glen) { tp = static_cast (xmalloc (glen)); } sfsprog::sfsctl::sfsctl (ref x, const authunix_parms *naup, sfsprog *p) : prog (p), aid (aup2aid (naup)), pid (0) { const int glen = naup->aup_len * sizeof (naup->aup_gids[0]); aup = *naup; aup.aup_machname = xstrdup (naup->aup_machname); domalloc (aup.aup_gids, glen); memcpy (aup.aup_gids, naup->aup_gids, glen); s = asrv::alloc (x, sfsctl_prog_1, wrap (this, &sfsctl::dispatch)); prog->ctltab.insert (this); } sfsprog::sfsctl::~sfsctl () { prog->ctltab.remove (this); xfree (aup.aup_machname); xfree (aup.aup_gids); } void sfsprog::sfsctl::setpid (int32_t npid) { prog->ctltab.remove (this); pid = npid; prog->ctltab.insert (this); } static void sfsctl_err (svccb *sbp, nfsstat3 err) { switch (sbp->proc ()) { case SFSCTL_LOOKUP: sbp->replyref (lookup3res (nfsstat3 (err))); break; default: sbp->replyref (err); } } inline nfsstat3 clnt2nfs (clnt_stat err) { switch (err) { case RPC_SUCCESS: return NFS3_OK; case RPC_CANTSEND: case RPC_CANTRECV: return NFS3ERR_JUKEBOX; case RPC_AUTHERROR: return NFS3ERR_ACCES; default: return NFS3ERR_IO; } } static void idnames_cb (svccb *sbp, sfs_idnames *resp, clnt_stat err) { sfsctl_getidnames_res res (clnt2nfs (err)); if (!res.status) *res.names = *resp; sbp->reply (&res); delete resp; } static void idnums_cb (svccb *sbp, sfs_idnums *resp, clnt_stat err) { sfsctl_getidnums_res res (clnt2nfs (err)); if (!res.status) *res.nums = *resp; sbp->reply (&res); delete resp; } static void getcred_cb (svccb *sbp, sfsauth_cred *resp, clnt_stat err) { sfsctl_getcred_res res (clnt2nfs (err)); if (!res.status) *res.cred = *resp; sbp->reply (&res); delete resp; } static void lookup_cb (svccb *sbp, lookup3res *resp, clnt_stat err) { if (err) resp->set_status (clnt2nfs (err)); sbp->reply (resp); delete resp; } void sfsprog::sfsctl::dispatch (svccb *sbp) { if (!sbp) { delete this; return; } switch (sbp->proc ()) { case SFSCTL_NULL: sbp->reply (NULL); return; case SFSCTL_SETPID: setpid (*sbp->template getarg ()); sbp->reply (NULL); return; } sfsserver *si = prog->pathtab[*sbp->template getarg ()]; if (!si) { sfsctl_err (sbp, NFS3ERR_STALE); return; } if (!si->sfsc) { sfsctl_err (sbp, NFS3ERR_JUKEBOX); return; } AUTH *auth = si->authof (aid); switch (sbp->proc ()) { case SFSCTL_GETFH: { sfsctl_getfh_res res; if (fip && fip->fspath == si->path) *res.fh = fip->fh; else res.set_status (NFS3ERR_STALE); fip = NULL; sbp->reply (&res); break; } case SFSCTL_GETIDNAMES: { sfsctl_getidnames_arg *argp = sbp->template getarg (); sfs_idnames *resp = New sfs_idnames; si->sfsc->call (SFSPROC_IDNAMES, &argp->nums, resp, wrap (idnames_cb, sbp, resp), auth); break; } case SFSCTL_GETIDNUMS: { sfsctl_getidnums_arg *argp = sbp->template getarg (); sfs_idnums *resp = New sfs_idnums; si->sfsc->call (SFSPROC_IDNUMS, &argp->names, resp, wrap (idnums_cb, sbp, resp), auth); break; } case SFSCTL_GETCRED: { sfsauth_cred *resp = New sfsauth_cred; si->sfsc->call (SFSPROC_GETCRED, NULL, resp, wrap (getcred_cb, sbp, resp), auth); break; } case SFSCTL_LOOKUP: { sfsctl_lookup_arg *argp = sbp->template getarg (); lookup3res *resp = New lookup3res; si->sfsc->call (NFSPROC3_LOOKUP, &argp->arg, resp, wrap (lookup_cb, sbp, resp), auth); break; } default: sbp->reject (PROC_UNAVAIL); break; } }