/* $Id: authclnt.C,v 1.57 2002/12/01 02:45:25 dm Exp $ */ /* * * Copyright (C) 2002 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 "sfscrypt.h" #include "sfsschnorr.h" #include "sfsauthd.h" #include "rxx.h" #include "sfskeymisc.h" #if HAVE_GETSPNAM #include #endif /* HAVE_GETSPNAM */ extern "C" char *crypt (const char *, const char *); sprivk_tab_t sprivk_tab; static str hash_sprivk (const sfs_2schnorr_priv_xdr &k) { sfs_hash h; if (!sha1_hashxdr (&h, k)) return NULL; return str (h.base (), h.size ()); } bool sprivk_tab_t::is_valid (const str &hv) { assert (hv); bool ret; sprivk_t *s = keys[hv]; if (!s) ret = false; else ret = s->valid; return ret; } bool sprivk_tab_t::invalidate (const str &hv) { assert (hv); sprivk_t *s = keys[hv]; if (!s) return false; s->valid = false; release (hv, s); return true; } void sprivk_tab_t::bind (const str &hv) { assert (hv); sprivk_t *s = keys[hv]; if (s) s->refs++; else { nentries ++; keys.insert (hv); } } void sprivk_tab_t::release (const str &hv, sprivk_t *s) { assert (hv); if (!s) s = keys[hv]; if (s && --s->refs == 0) { nentries --; keys.remove (hv); } } bool validshell (const char *shell) { const char *s; setusershell (); while ((s = getusershell ())) if (!strcmp(s, shell)) { endusershell (); return true; } endusershell (); return false; } void authclnt::urecfree (urec_t *u) { utab.remove (u); ulist.remove (u); delete u; } authclnt::urec_t::~urec_t () { if (kh.type == SFSAUTH_KEYHALF_PRIV) for (u_int i = 0; i < kh.priv->size (); i++) sprivk_tab.release (hash_sprivk ((*kh.priv) [i])); } authclnt::urec_t::urec_t (u_int32_t a, sfs_authtype t, const sfsauth_dbrec &dbr) : authno (a), authtype (t) { if (dbr.type == SFSAUTH_USER) { uname = dbr.userinfo->name; kh = dbr.userinfo->srvprivkey; if (kh.type == SFSAUTH_KEYHALF_PRIV) for (u_int i = 0; i < kh.priv->size (); i++) sprivk_tab.bind (hash_sprivk ((*kh.priv) [i])); } } authclnt::authclnt (ref x, const authunix_parms *aup) : sfsserv (x), authsrv (asrv::alloc (x, sfsauth_prog_2, wrap (this, &authclnt::dispatch))), sfsauth_login_srp (aup && !aup->aup_uid) { if (aup) { uid.alloc (); *uid = aup->aup_uid; if (!client_name || !client_name.len ()) { if (!*uid) client_name = "LOCAL"; else client_name = strbuf ("LOCAL(uid=%d)", *uid); } } } authclnt::~authclnt () { ulist.traverse (wrap (this, &authclnt::urecfree)); } ptr authclnt::doconnect (const sfs_connectarg *ci, sfs_servinfo *si) { *si = myservinfo; return myprivkey; } inline str mkname (const dbfile *dbp, str name) { str r; if (dbp->prefix) r = dbp->prefix << "/" << name; else r = name; return name; } bool authclnt::setuser (sfsauth2_loginres *resp, const sfsauth_dbrec &ae, const dbfile *dbp) { assert (ae.type == SFSAUTH_USER); resp->set_status (SFSLOGIN_OK); resp->resok->creds.setsize (1); resp->resok->creds[0].set_type (SFS_UNIXCRED); str name = mkname (dbp, ae.userinfo->name); resp->resok->creds[0].unixcred->uid = dbp->uidmap->map (ae.userinfo->id); if (resp->resok->creds[0].unixcred->uid == badid) { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "uid out of range"; return false; } resp->resok->creds[0].unixcred->username = name; resp->resok->creds[0].unixcred->homedir = "/dev/null"; resp->resok->creds[0].unixcred->shell = "/dev/null"; if (dbp->allow_unix_pwd && name.len () == strlen (name)) { if (struct passwd *pw = getpwnam (ae.userinfo->name)) { resp->resok->creds[0].unixcred->homedir = pw->pw_dir; resp->resok->creds[0].unixcred->shell = pw->pw_shell; } } #if 0 /* XXX - what is this -dm? Seems like a huge security hole. */ else if (dbp->allow_userdir_shell) { // Allow remote users without a local account to specify a // shell and homedir resp->resok->creds[0].unixcred->homedir = USERDIR_HOMEDIR; resp->resok->creds[0].unixcred->shell = USERDIR_SHELL; } #endif resp->resok->creds[0].unixcred->gid = dbp->gidmap->map (ae.userinfo->gid); vec groups; findgroups (&groups, name); resp->resok->creds[0].unixcred->groups.setsize (groups.size ()); memcpy (resp->resok->creds[0].unixcred->groups.base (), groups.base (), groups.size () * sizeof (groups[0])); return true; } void setuser_pkhash (sfsauth2_loginres *resp, ptr vrfy) { str h; if (!(h = vrfy->get_pubkey_hash ())) { warn << "Error in sha1_hashxdr of user's public key\n"; return; } vec v; size_t n = resp->resok->creds.size (); sfsauth_cred *cp = resp->resok->creds.base (); for (size_t i = 0; i < n; i++) v.push_back (cp[i]); v.push_back (); v[n].set_type (SFS_PKCRED); *v[n].pkhash = armor32 (h); resp->resok->creds.setsize (n + 1); for (size_t i = 0; i < n + 1; i++) resp->resok->creds[i] = v[i]; } void authclnt::findgroups (vec *groups, str name) { groups->clear (); for (dbfile *dbp = dbfiles.base (); dbp < dbfiles.lim (); dbp++) { str suffix; if (!dbp->prefix) suffix = name; else if (dbp->prefix.len () + 1 < name.len () && name[dbp->prefix.len ()] == '/' && !memcmp (dbp->prefix.cstr (), name.cstr (), dbp->prefix.len ())) suffix = substr (name, dbp->prefix.len () + 1); else continue; ptr ac = dbp->db->open (); vec gv; ac->getgroups (&gv, suffix); while (!gv.empty ()) { u_int32_t gid = dbp->gidmap->map (gv.pop_front ()); if (gid != badid) groups->push_back (gid); } } } bool authclnt::checkreq (sfsauth2_loginres *resp, const sfsauth2_loginarg *lap, const sfs_authreq2 *reqp) { if (reqp->type != SFS_SIGNED_AUTHREQ && reqp->type != SFS_SIGNED_AUTHREQ_NOCRED) { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = strbuf ("bad sfs_authreq2 type %d\n", reqp->type); } else if (reqp->authid != lap->authid) { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "authid mismatch\n"; } else if (reqp->seqno != lap->arg.seqno) { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "sequence number mismatch\n"; } else return true; return false; } #if 0 ptr authclnt::getauthclnt () { /* XXX - Bit of a kludge; should really just override sfs_login */ static ptr c; static ptr ac; if (c) return c; int fds[2]; if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) < 0) fatal ("socketpair: %m\n"); c = aclnt::alloc (axprt_stream::alloc (fds[0]), sfsauth_prog_2); ac = New refcounted (axprt_crypt::alloc (fds[1]), (authunix_parms *) NULL, true); return c; } #endif void authclnt::dispatch (svccb *sbp) { if (!sbp) { delete this; return; } switch (sbp->proc ()) { case SFSAUTH2_NULL: sbp->reply (NULL); break; case SFSAUTH2_LOGIN: { sfsauth2_loginres res; sfsauth_login (&res, sbp->getarg (), sfsauth_login_srp); sbp->replyref (res); break; } case SFSAUTH2_QUERY: sfsauth_query (sbp); break; case SFSAUTH2_UPDATE: sfsauth_update (sbp); break; case SFSAUTH2_SIGN: sfsauth_sign (sbp); break; default: sbp->reject (PROC_UNAVAIL); break; } } void authclnt::sfsauth_sign (svccb *sbp) { sfsauth2_sign_arg *arg = sbp->getarg (); sfsauth2_sign_res res (true); u_int32_t authno = sbp->getaui (); sfsauth_dbrec db; bool restricted_sign = false; urec_t *ur = NULL; sfsauth_keyhalf *kh = NULL; sfs_idname uname; if (authno && (ur = utab[authno])) { kh = &ur->kh; uname = ur->uname; } if (!kh && arg->req.type == SFS_SIGNED_AUTHREQ && arg->req.authreq->type == SFS_SIGNED_AUTHREQ_NOCRED && arg->authinfo.service == SFS_AUTHSERV && authid == arg->req.authreq->authid) { sfsauth_dbkey key (SFSAUTH_DBKEY_NAME); if ((*key.name = arg->user) && get_user_cursor (NULL, NULL, &db, key)) { kh = &db.userinfo->srvprivkey; uname = db.userinfo->name; restricted_sign = true; } } if (!kh || kh->type != SFSAUTH_KEYHALF_PRIV) { res.set_ok (false); *res.errmsg = "No valid server private keyhalf for user"; sbp->replyref (res); return; } if (arg->presig.type != SFS_2SCHNORR) { res.set_ok (false); *res.errmsg = "Can only answer 2-Schnorr requests"; sbp->replyref (res); return; } res.sig->set_type (SFS_SCHNORR); int i = sfs_schnorr_pub::find (*kh, arg->pubkeyhash); if (i < 0) { res.set_ok (false); *res.errmsg = "No matching keyhalf found on server."; sbp->replyref (res); return; } const sfs_2schnorr_priv_xdr &spriv = (*kh->priv)[i]; if (ur && !sprivk_tab.is_valid (hash_sprivk (spriv))) { res.set_ok (false); *res.errmsg = "Server keyhalf is no longer valid."; sbp->replyref (res); return; } ref srv_key = New refcounted (spriv.p, spriv.q, spriv.g, spriv.y, spriv.x); str msg = sigreq2str (arg->req); if (!msg) { res.set_ok (false); *res.errmsg = "Cannot marshal signature request"; sbp->replyref (res); return ; } sfs_hash aid_tmp; if (arg->req.type != SFS_NULL && (!sha1_hashxdr (aid_tmp.base (), arg->authinfo) || !sigreq_authid_cmp (arg->req, aid_tmp))) { res.set_ok (false); *res.errmsg = "Incorrect authid in request"; sbp->replyref (res); return ; } if (!siglog (siglogline (*arg, uname))) { res.set_ok (false); *res.errmsg = "Refusing to sign: could not log signature"; sbp->replyref (res); return; } srv_key->endorse_signature (&res.sig->schnorr->r, &res.sig->schnorr->s, msg, arg->presig.schnorr->r); sbp->replyref (res); } str authclnt::siglogline (const sfsauth2_sign_arg &arg, const str &uname) { str req = xdr2str (arg); if (!req) return NULL; req = armor64 (req); str tm = single_char_sub (timestr (), ':', "."); strbuf line; line << "SIGN:" << uname << ":" << client_name << ":" << tm << ":" << req << "\n"; return line; } bool siglog (const str &line) { if (!line) return false; int n = write (logfd, line.cstr (), line.len ()); if (n < int (line.len ())) return false; return true; } str siglog_startup_msg () { strbuf msg; str tm = single_char_sub (timestr (), ':', "."); msg << "sfsauthd restarted: " << tm << "\n"; return msg; } void siglogv () { if (!siglog (siglog_startup_msg ())) fatal << "Cannot generate startup message for signature log\n"; } void authclnt::sfsauth_update (svccb *sbp) { const sfsauth_cred *cp = NULL; urec_t *ur = NULL; if (sbp->getaui () >= credtab.size () || !(cp = &credtab[sbp->getaui ()]) || cp->type != SFS_UNIXCRED || !(ur = utab[sbp->getaui ()]) || ur->authtype == SFS_NOAUTH) { sbp->reject (AUTH_REJECTEDCRED); return; } sfsauth2_update_res res (false); sfsauth_dbkey kname (SFSAUTH_DBKEY_NAME); *kname.name = cp->unixcred->username; bool oldsig = false; sfsauth_dbrec cdbr; dbfile *cdbp; if (!get_user_cursor (&cdbp, NULL, &cdbr, kname) || cp->unixcred->username != cdbr.userinfo->name || cp->unixcred->uid != cdbr.userinfo->id) { *res.errmsg = "could not load credential db record"; sbp->replyref (res); return; } sfsauth2_update_arg *argp = sbp->getarg (); if (argp->req.type != SFS_UPDATEREQ || (argp->req.rec.type != SFSAUTH_USER && argp->req.rec.type != SFSAUTH_GROUP)) { *res.errmsg = "invalid request"; sbp->replyref (res); return; } u_int32_t opts = argp->req.opts; if (argp->req.authid != authid) { *res.errmsg = "invalid authid"; sbp->replyref (res); return ; } static rxx adminrx ("(\\A|,)admin(\\z|,)"); bool admin = cdbp->allow_admin && adminrx.match (cdbr.userinfo->privs); str reqxdr = xdr2str (argp->req); if (argp->newsig) { str e; if (!sfscrypt.verify (argp->req.rec.userinfo->pubkey, *(argp->newsig), reqxdr, &e)) { *res.errmsg = str (strbuf ("new signature: " << e)); sbp->replyref (res); return; } } else if (!(opts & SFSUP_KPPK) && !admin) { *res.errmsg = "Missing signature with new public key."; sbp->replyref (res); return; } if (argp->authsig) { str e; if (!sfscrypt.verify (cdbr.userinfo->pubkey, *(argp->authsig), reqxdr, &e)) { *res.errmsg = str (strbuf ("old signature: " << e)); sbp->replyref (res); return; } else oldsig = true; } else if (!cdbp->allow_unix_pwd || ur->authtype != SFS_UNIXPWAUTH) { *res.errmsg = "digital signature required"; sbp->replyref (res); return; } else admin = false; dbfile *udbp; ptr uac; sfsauth_dbrec udbr; if (argp->req.rec.type == SFSAUTH_USER) { *kname.name = argp->req.rec.userinfo->name; if (!get_user_cursor (&udbp, &uac, &udbr, kname, true)) { *res.errmsg = "could not find or update user's record"; sbp->replyref (res); return; } if (!admin && (udbr.userinfo->name != cdbr.userinfo->name || udbr.userinfo->id != cdbr.userinfo->id)) { /* XXX - ignoring owner field for now */ *res.errmsg = "access denied"; sbp->replyref (res); return; } if (argp->req.rec.userinfo->vers < 1) { *res.errmsg = "version number of record must be greater than 0"; sbp->replyref (res); return; } if (argp->req.rec.userinfo->vers != udbr.userinfo->vers + 1) { *res.errmsg = "version mismatch"; sbp->replyref (res); return; } uac->ae.userinfo->vers = argp->req.rec.userinfo->vers; if (!(opts & SFSUP_KPPK)) uac->ae.userinfo->pubkey = argp->req.rec.userinfo->pubkey; if (!(opts & SFSUP_KPSRP)) uac->ae.userinfo->pwauth = argp->req.rec.userinfo->pwauth; if (!(opts & SFSUP_KPESK)) uac->ae.userinfo->privkey = argp->req.rec.userinfo->privkey; str err = update_srv_keyhalf (argp->req.rec.userinfo->srvprivkey, uac->ae.userinfo->srvprivkey, udbr.userinfo->srvprivkey, true, ur); if (err) { *res.errmsg = err; sbp->replyref (res); return; } strbuf sb; sb << "Last modified " << timestr () << " by " ; if (uid && !*uid) sb << "*superuser*"; else sb << cp->unixcred->username; sb << "@" << client_name; uac->ae.userinfo->audit = sb; if (admin) { u_int32_t gid = argp->req.rec.userinfo->gid; if (udbp->gidmap && gid != udbp->gidmap->map (uac->ae.userinfo->gid)) { gid = udbp->gidmap->unmap (gid); if (gid == badid || udbp->gidmap->map (gid) != argp->req.rec.userinfo->gid) { *res.errmsg = "bad gid"; sbp->replyref (res); return; } } uac->ae.userinfo->gid = gid; uac->ae.userinfo->privs = argp->req.rec.userinfo->privs; } if (!uac->update (cdbp->allow_create)) { *res.errmsg = "database refused update"; sbp->replyref (res); return; } res.set_ok (true); cdbp->mkpub (); } else { *res.errmsg = "group updates not implemented"; sbp->replyref (res); return; } sbp->replyref (res); } bool authclnt::get_user_cursor (dbfile **dbpp, ptr *acp, sfsauth_dbrec *dbrp, const sfsauth_dbkey &key, bool writable) { if (key.type != SFSAUTH_DBKEY_NAME && key.type != SFSAUTH_DBKEY_ID && key.type != SFSAUTH_DBKEY_PUBKEY) { if (dbrp) { dbrp->set_type (SFSAUTH_ERROR); *dbrp->errmsg = strbuf ("unsupported key type %d", key.type); } return false; } ptr pk; for (dbfile *dbp = dbfiles.base (); dbp < dbfiles.lim (); dbp++) { if (writable && !dbp->allow_update) continue; ptr ac = dbp->db->open (writable); if (!ac) continue; switch (key.type) { case SFSAUTH_DBKEY_NAME: { str name = dbp->strip_prefix (*key.name); if (!name || !ac->find_user_name (name)) { struct passwd *pw; if (dbp->allow_unix_pwd && (pw = getpwnam (name))) { sfsauth_dbrec rec (SFSAUTH_USER); rec.userinfo->name = *key.name; rec.userinfo->id = pw->pw_uid; rec.userinfo->vers = 0; rec.userinfo->gid = pw->pw_gid; ac->ae = rec; break; } continue; } break; } case SFSAUTH_DBKEY_ID: { u_int32_t id = dbp->uidmap ? dbp->uidmap->unmap (*key.id) : *key.id; if (id == badid || !ac->find_user_uid (id)) { struct passwd *pw; if (dbp->allow_unix_pwd && (pw = getpwuid (*key.id))) { sfsauth_dbrec rec (SFSAUTH_USER); if (dbp->prefix) rec.userinfo->name = dbp->prefix << "/" << pw->pw_name; else rec.userinfo->name = pw->pw_name; rec.userinfo->id = pw->pw_uid; rec.userinfo->vers = 0; rec.userinfo->gid = pw->pw_gid; ac->ae = rec; break; } continue; } break; } case SFSAUTH_DBKEY_PUBKEY: if (!pk) { if (!(pk = sfscrypt.alloc (*key.key))) { warn << "Cannot import user public key.\n"; return false; } } if (!(ac->find_user_pubkey (*pk) && *pk == ac->ae.userinfo->pubkey)) continue; break; default: panic ("unreachable\n"); } if (dbpp) *dbpp = dbp; if (acp) *acp = ac; if (dbrp) { *dbrp = ac->ae; dbrp->userinfo->pwauth = ""; dbrp->userinfo->privkey.setsize (0); if (dbp->prefix) dbrp->userinfo->name = dbp->prefix << "/" << dbrp->userinfo->name; if (dbp->uidmap) dbrp->userinfo->id = dbp->uidmap->map (dbrp->userinfo->id); if (dbrp->userinfo->id == badid) continue; if (dbp->gidmap) dbrp->userinfo->gid = dbp->gidmap->map (dbrp->userinfo->gid); } return true; } if (dbrp) { dbrp->set_type (SFSAUTH_ERROR); *dbrp->errmsg = "not found"; } return false; } void authclnt::query_user (svccb *sbp) { sfsauth2_query_arg *arg = sbp->getarg (); ptr ac; sfsauth2_query_res res; if (get_user_cursor (NULL, &ac, &res, arg->key) && sbp->getaui () < credtab.size ()) { const sfsauth_cred &c = credtab[sbp->getaui ()]; const urec_t *ur = utab[sbp->getaui ()]; if (ur && c.type == SFS_UNIXCRED && c.unixcred->username == res.userinfo->name && c.unixcred->uid == res.userinfo->id && ur->authtype == SFS_SRPAUTH) res.userinfo->privkey = ac->ae.userinfo->privkey; } if (res.type == SFSAUTH_USER && res.userinfo->srvprivkey.type == SFSAUTH_KEYHALF_PRIV) res.userinfo->srvprivkey.set_type (SFSAUTH_KEYHALF_FLAG); sbp->replyref (res); } void authclnt::query_srpparms (svccb *sbp) { sfsauth2_query_res res (SFSAUTH_SRPPARMS); if (!srpparms) { res.set_type (SFSAUTH_ERROR); *res.errmsg = "No SRP information available"; } else { res.srpparms->parms = srpparms; } sbp->replyref (res); } void authclnt::query_certinfo (svccb *sbp) { sfsauth2_query_res res (SFSAUTH_CERTINFO); if (sfsauthrealm.len () > 0) { res.certinfo->name = sfsauthrealm; res.certinfo->info.set_status (SFSAUTH_CERT_REALM); res.certinfo->info.certpaths->set (sfsauthcertpaths.base (), sfsauthcertpaths.size (), freemode::NOFREE); } else { res.certinfo->name = myservinfo.cr7->host.hostname; res.certinfo->info.set_status (SFSAUTH_CERT_SELF); } sbp->replyref (res); return ; } void authclnt::query_group (svccb *sbp) { sfsauth2_query_arg *arg = sbp->getarg (); sfsauth2_query_res res; if (arg->key.type != SFSAUTH_DBKEY_NAME && arg->key.type != SFSAUTH_DBKEY_ID) { res.set_type (SFSAUTH_ERROR); *res.errmsg = strbuf ("unsupported key type %d", arg->key.type); sbp->replyref (res); return; } for (dbfile *dbp = dbfiles.base (); dbp < dbfiles.lim (); dbp++) { ptr ac = dbp->db->open (); if (!ac) continue; switch (arg->key.type) { case SFSAUTH_DBKEY_NAME: { str name = dbp->strip_prefix (*arg->key.name); if (!name || !ac->find_group_name (name)) continue; break; } case SFSAUTH_DBKEY_ID: { u_int32_t id = dbp->uidmap ? dbp->uidmap->unmap (*arg->key.id) : *arg->key.id; if (id == badid || !ac->find_group_gid (id)) continue; break; } default: panic ("unreachable"); } res = ac->ae; assert (res.type == SFSAUTH_GROUP); sbp->replyref (res); return; } res.set_type (SFSAUTH_ERROR); *res.errmsg = "not found"; sbp->replyref (res); } void authclnt::sfsauth_query (svccb *sbp) { sfsauth2_query_arg *arg = sbp->getarg (); switch (arg->type) { case SFSAUTH_USER: query_user (sbp); break; case SFSAUTH_GROUP: query_group (sbp); break; case SFSAUTH_CERTINFO: query_certinfo (sbp); break; case SFSAUTH_SRPPARMS: query_srpparms (sbp); break; default: sfsauth2_query_res res; res.set_type (SFSAUTH_ERROR); *res.errmsg = strbuf ("unsupported query type %d", arg->type); sbp->replyref (res); break; } } void authclnt::sfs_login (svccb *sbp) { if (!authid_valid) { sbp->replyref (sfs_loginres (SFSLOGIN_ALLBAD)); return; } sfsauth2_loginarg la; la.arg = *sbp->getarg (); la.authid = authid; la.source = client_name << "!" << progname; sfsauth_dbrec dbr; sfsauth2_loginres lr; sfs_authtype authtype; sfsauth_login (&lr, &la, true, &dbr, &authtype); sfs_loginres res (lr.status); switch (lr.status) { case SFSLOGIN_OK: if (!seqstate.check (la.arg.seqno) || lr.resok->creds.size () < 1) res.set_status (SFSLOGIN_BAD); else { u_int32_t authno; authno = authalloc (lr.resok->creds.base (), lr.resok->creds.size ()); if (!authno) { warn << "ran out of authnos (or bad cred type)\n"; res.set_status (SFSLOGIN_BAD); } utab_insert (authno, authtype, dbr); *res.authno = authno; } break; case SFSLOGIN_MORE: *res.resmore = *lr.resmore; break; default: break; } sbp->replyref (res); } void authclnt::utab_insert (u_int32_t authno, sfs_authtype at, const sfsauth_dbrec &dbr) { urec_t *u = utab[authno]; if (u) urecfree (u); urec_t *ur = New urec_t (authno, at, dbr); utab.insert (ur); ulist.insert_head (ur); } void authclnt::sfs_logout (svccb *sbp) { u_int32_t authno = *sbp->getarg (); urec_t *u = utab[authno]; if (u) urecfree (u); sfsserv::sfs_logout (sbp); } inline bool sourceok (str source) { for (u_int i = 0; i < source.len (); i++) if (source[i] < 0x20 || source[i] >= 0x7f) return false; return true; } void authclnt::sfsauth_login (sfsauth2_loginres *resp, const sfsauth2_loginarg *lap, bool srp_ok, sfsauth_dbrec *dbp, sfs_authtype *atp) { sfs_autharg2 aa; sfsauth2_loginres &res = *resp; if (!sourceok (lap->source)) { res.set_status (SFSLOGIN_BAD); *res.errmsg = "invalid source in login reguest"; return; } if (!bytes2xdr (aa, lap->arg.certificate)) { res.set_status (SFSLOGIN_BAD); *res.errmsg = "cannot unmarshal certificate"; return; } if (atp) *atp = aa.type; str method; switch (aa.type) { case SFS_AUTHREQ: case SFS_AUTHREQ2: method = "public key"; authreq (&res, lap, &aa, dbp); break; case SFS_UNIXPWAUTH: method = "unix password"; unixpwauth (&res, lap, &aa, srp_ok, dbp); break; case SFS_SRPAUTH: method = "SRP password"; if (srp_ok) srpauth (&res, lap, &aa, dbp); else { res.set_status (SFSLOGIN_BAD); *res.errmsg = "SRP authentication of client to third party not allowed"; } break; default: res.set_status (SFSLOGIN_BAD); *res.errmsg = strbuf ("unknown login type %d", aa.type); break; } if (res.status == SFSLOGIN_OK && !res.resok->creds.size ()) res.set_status (SFSLOGIN_BAD); if (res.status == SFSLOGIN_OK) { if (res.resok->creds[0].type == SFS_UNIXCRED) warn << "accepted user " << res.resok->creds[0].unixcred->username << " from " << lap->source << " using " << method << "\n"; else if (res.resok->creds[0].type == SFS_PKCRED) warn << "accepted pubkey " << *res.resok->creds[0].pkhash << " from " << lap->source << " using " << method << "\n"; } } void authclnt::authreq (sfsauth2_loginres *resp, const sfsauth2_loginarg *lap, const sfs_autharg2 *aap, sfsauth_dbrec *dbrp) { ptr vrfy; str logname; sfs_msgtype mtype; str s; if (aap->type == SFS_AUTHREQ) { const sfs_pubkey &kp = aap->authreq1->usrkey; if (!(vrfy = sfscrypt.alloc (kp, SFS_VERIFY))) { *resp->errmsg = "Cannot load public Rabin key"; resp->set_status (SFSLOGIN_BAD); return ; } sfs_signed_authreq authreq; str msg; if (!vrfy->verify_r (aap->authreq1->signed_req, sizeof (authreq), msg) || !str2xdr (authreq, msg) || (authreq.type != SFS_SIGNED_AUTHREQ && authreq.type != SFS_SIGNED_AUTHREQ_NOCRED) || authreq.seqno != lap->arg.seqno || authreq.authid != lap->authid) { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "bad signature"; return; } mtype = authreq.type; if (authreq.usrinfo[0]) { if (memchr (authreq.usrinfo.base (), 0, authreq.usrinfo.size ())) logname = authreq.usrinfo.base (); else logname.setbuf (authreq.usrinfo.base (), authreq.usrinfo.size ()); } } else { str e; if (aap->sigauth->req.user.len ()) logname = aap->sigauth->req.user; mtype = aap->sigauth->req.type; str msg = xdr2str (aap->sigauth->req); resp->set_status (SFSLOGIN_BAD); if (!(vrfy = sfscrypt.alloc (aap->sigauth->key, SFS_VERIFY))) { *resp->errmsg = "cannot load public key"; return; } else if (!vrfy->verify (aap->sigauth->sig, msg, &e)) { *resp->errmsg = str (strbuf ("Bad login: " << e)); return; } if (!checkreq (resp, lap, &aap->sigauth->req)) return; } for (dbfile *dbp = dbfiles.base (); dbp < dbfiles.lim (); dbp++) { ptr ac = dbp->db->open (); // XXX - in long form for aiding in debugging if (!ac) continue; if (!ac->find_user_pubkey (*vrfy)) continue; if (ac->ae.type != SFSAUTH_USER) continue; if (logname) { if (dbp->prefix) { if (logname != dbp->prefix << "/" << ac->ae.userinfo->name) continue; } else { if (logname != ac->ae.userinfo->name) continue; } } if (*vrfy == ac->ae.userinfo->pubkey) { if (!setuser (resp, ac->ae, dbp)) continue; setuser_pkhash (resp, vrfy); } else if (mtype == SFS_SIGNED_AUTHREQ_NOCRED) { resp->set_status (SFSLOGIN_OK); resp->resok->creds.setsize (1); resp->resok->creds[0].set_type (SFS_NOCRED); } else { continue; } #if 0 str source = ""; if (lap->source.len ()) source = strbuf () << " from " << lap->source; str name = ac->ae.userinfo->name; if (dbp->prefix) name = dbp->prefix << "/" << name; warn << "signed login by " << name << source << "\n"; #endif if (dbrp) *dbrp = ac->ae; return; } if (mtype != SFS_SIGNED_AUTHREQ_NOCRED) { resp->set_status (SFSLOGIN_OK); setuser_pkhash (resp, vrfy); } else { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "bad login"; } } #define ROOT_SU(pw, ok) (!*(pw) && (ok) && uid && *uid == 0) void authclnt::unixpwauth (sfsauth2_loginres *resp, const sfsauth2_loginarg *lap, const sfs_autharg2 *aap, bool root_su_ok, sfsauth_dbrec *dbrp) { dbfile *dbp = dbfiles.base (); str name, source; ptr ac; bool flag = false; struct passwd *pe = getpwnam (aap->pwauth->req.user.cstr ()); const char *pw; if (!pe) goto badlogin; pw = aap->pwauth->password.cstr (); while (dbp < dbfiles.lim ()) { if (dbp->allow_unix_pwd) { flag = true; break; } dbp++; } if (!uid) goto badlogin; if (!flag) goto badlogin; #if HAVE_GETSPNAM if (struct spwd *spe = getspnam (aap->pwauth->req.user.cstr ())) { if (!ROOT_SU (pw, root_su_ok) && strcmp (spe->sp_pwdp, crypt (pw, spe->sp_pwdp))) goto badlogin; } else #endif /* HAVE_GETSPNAM */ if (!ROOT_SU (pw, root_su_ok) && strcmp (pe->pw_passwd, crypt (pw, pe->pw_passwd))) goto badlogin; ac = dbp->db->open (); if (!ac) goto badlogin; if (!ac->find_user_name (pe->pw_name)) { ac->ae.set_type (SFSAUTH_USER); ac->ae.userinfo->name = pe->pw_name; ac->ae.userinfo->id = pe->pw_uid; ac->ae.userinfo->vers = 0; ac->ae.userinfo->gid = pe->pw_gid; } if (!validshell (pe->pw_shell)) { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "bad shell"; goto badlogin; } if (!setuser (resp, ac->ae, dbp)) return; #if 0 source = ""; if (lap->source.len ()) source = strbuf () << " from " << lap->source; name = ac->ae.userinfo->name; if (dbp->prefix) name = dbp->prefix << "/" << name; warn << "password login by " << name << source << "\n"; #endif if (dbrp) *dbrp = ac->ae; return; badlogin: warn << "BAD unix password login attempt for " << aap->pwauth->req.user.cstr () << " from " << client_name << "\n"; if (resp->status != SFSLOGIN_BAD) resp->set_status (SFSLOGIN_BAD); if (!resp->errmsg->len ()) *resp->errmsg = "bad login"; } void authclnt::srpauth (sfsauth2_loginres *resp, const sfsauth2_loginarg *lap, const sfs_autharg2 *aap, sfsauth_dbrec *dbrp) { if (aap->srpauth->req.authid != lap->authid || (!sfsauth_login_srp && (!authid_valid || lap->authid != authid))) { srp_ac = NULL; resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "invalid or missing authid"; return; } if (aap->srpauth->msg.size () == 0) { sfsauth_dbkey kname (SFSAUTH_DBKEY_NAME); *kname.name = aap->srpauth->req.user; if (!get_user_cursor (&srp_dbp, &srp_ac, NULL, kname)) { srp_ac = NULL; resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "bad login"; return; } srp_seqno = lap->arg.seqno; srp_authid = lap->authid; resp->set_status (SFSLOGIN_MORE); switch (srp.init (resp->resmore.addr (), &aap->srpauth->msg, srp_authid, srp_ac->ae.userinfo->name, srp_ac->ae.userinfo->pwauth)) { case SRP_NEXT: return; default: srp_ac = NULL; resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "bad login"; return; } } if (!srp_ac || srp_authid != lap->authid || srp_seqno != lap->arg.seqno) { resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "unknown SRP session"; return; } srpmsg msg; switch (srp.next (&msg, &aap->srpauth->msg)) { case SRP_NEXT: resp->set_status (SFSLOGIN_MORE); *resp->resmore = msg; return; case SRP_LAST: if (setuser (resp, srp_ac->ae, srp_dbp)) resp->resok->resmore = msg; if (dbrp) *dbrp = srp_ac->ae; break; default: warn << "BAD SRP login attempt for " << mkname (srp_dbp, srp_ac->ae.userinfo->name) << " from " << client_name << "\n"; resp->set_status (SFSLOGIN_BAD); *resp->errmsg = "bad login"; break; } srp_ac = NULL; } str authclnt::update_srv_keyhalf (const sfsauth_keyhalf &updkh, sfsauth_keyhalf &newkh, const sfsauth_keyhalf &oldkh, bool canclear, urec_t *ur) { bool hasoldkh = false; const sfsauth_keyhalf_type &kht = updkh.type; if (kht == SFSAUTH_KEYHALF_NONE) return NULL; newkh.set_type (SFSAUTH_KEYHALF_PRIV); u_int okeys = 0; if (oldkh.type == SFSAUTH_KEYHALF_PRIV) okeys = oldkh.priv->size (); u_int nkeys; if (oldkh.type == SFSAUTH_KEYHALF_PRIV && okeys >= 1) { hasoldkh = true; if (kht == SFSAUTH_KEYHALF_DELTA) { nkeys = okeys; for (u_int i = 1; i < okeys; i++) (*newkh.priv)[i] = (*oldkh.priv)[i]; } else { nkeys = (okeys == SPRIVK_HISTORY_LEN) ? okeys : okeys + 1; newkh.priv->setsize (nkeys); for (u_int i = 1; i < nkeys; i++) (*newkh.priv)[i] = (*oldkh.priv)[i-1]; } } else { nkeys = 1; newkh.priv->setsize (1); } if (kht == SFSAUTH_KEYHALF_DELTA) { if (!hasoldkh) return "Cannot apply key delta: no key currently exists!"; (*newkh.priv)[0] = (*oldkh.priv)[0]; (*newkh.priv)[0].x += *updkh.delta; (*newkh.priv)[0].x %= (*newkh.priv)[0].q; sprivk_tab.invalidate (hash_sprivk ((*oldkh.priv)[0])); sprivk_tab.bind (hash_sprivk ((*newkh.priv)[0])); } else if (kht == SFSAUTH_KEYHALF_PRIV) { if (!canclear) return "Can only explicitly set server keyhalf on register or signed " "update."; if (nkeys == okeys) sprivk_tab.invalidate (hash_sprivk ((*oldkh.priv)[okeys - 1])); (*newkh.priv)[0] = (*updkh.priv)[0]; sprivk_tab.bind (hash_sprivk ((*newkh.priv)[0])); } ur->kh = newkh; return NULL; }