/* $Id: sfskeymgr.C,v 1.45 2002/12/12 18:52:26 max Exp $ */ /* * * Copyright (C) 2002 Maxwell Krohn (max@cs.nyu.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 "sfskey.h" #include #include #include #include "sfskeymgr.h" #include "rxx.h" static rxx dot ("\\."); static rxx pound ("#"); static rxx comma (","); static void kmlcb (str err, ptr c, ptr k) { assert (false); } static void kmlcb2 (str err, ptr k) { assert (false); } static bool get_yesno (const str &prompt) { for (int i = 0; i < 5; i++) { str r = getline (prompt, NULL); if (!r || r.len () == 0) return false; const char *cp = r.cstr (); if (*cp == 'Y' || *cp == 'y') return true; if (*cp == 'N' || *cp == 'n') return false; } return false; } static str dir_split (const str &s, str *d = NULL) { if (d) *d = NULL; assert (s && s.len ()); const char *cp1, *cp2; cp2 = NULL; cp1 = s.cstr (); while ((cp2 = strchr (cp1, '/'))) cp1 = cp2 + 1; u_int len = s.len () - (cp1 - s.cstr ()); if (len) { if (d) *d = substr (s.cstr (), 0, s.len () - len); return substr (cp1, 0, len); } return s; } str sfskeystore::defkey2 () { init (false); if (!dotsfs) return NULL; return strbuf (dotsfs << "/identity"); } str sfskeystore::defkey2_readlink () { init (false); char buf[1024]; str nln; str ln = defkey2 (); if (!ln) return NULL; struct stat sb; for (int i = 0; i < 32; i++) { if (lstat (ln.cstr (), &sb) < 0) return NULL; if (!S_ISLNK (sb.st_mode)) return ln; int rc = readlink (ln.cstr (), buf, 1024); if (rc <= 0) return NULL; nln = str (buf, rc); if (nln.cstr () [0] != '/') { str dir; dir_split (ln, &dir); if (!dir) return NULL; strbuf sb (dir); if (dir.cstr () [dir.len () - 1] != '/') sb << "/"; sb << nln; nln = sb; } ln = nln; } return NULL; } int sfskeyinfo_proac::cmp (sfskeyinfo *k) const { int d; if (k->kt != SFSKI_PROAC) return -1; sfskeyinfo_proac *s = reinterpret_cast (k); if ((d = version - s->version)) return d; if ((d = s->host_priority - host_priority)) return d; if ((d = strcmp (host.cstr (), s->host.cstr ()))) return d; return (privk_version - s->privk_version); } sfskeymgr::sfskeymgr (const str &u, u_int32_t o) : g (0), N (0), fetching (false), fflerr (false), g_opts (o) { setuser (u); ks = New sfskeystore (user, g_opts); } void sfskeymgr::setuser (str u) { sfs_aid aid; if ((uid = getuid ()) < 0) fatal << "Cannot find user ID\n"; if (uid != 0 && u && u != myusername ()) fatal << "Can only use -u flag as root\n"; user = u; if (uid == 0) { if (!user) { if ((aid = myaid ())) { g_opts |= KM_NOHM; uid = aid; } struct passwd *pw = NULL; if (!(pw = getpwuid (uid))) fatal << "Cannot find pwent for uid " << uid << "\n"; user = pw->pw_name; } else { g_opts |= KM_NOHM; } } else if (!user && !(user = myusername ())) { fatal << "Cannot lookup my username\n"; } } bool sfskeymgr::getsrp (const str &filename, ptr sc) { if (g > 0) return true; if (filename) { str s = file2str (filename); if (!s) warn << "Cannot read SRP file: " << filename << "\n"; else if (import_srp_params (s, &N, &g)) return true; } ptr c = NULL; if (sc) c = aclnt::alloc (sc->x, sfsauth_prog_2); if (get_srp_params (c, &N, &g)) return true; return false; } void sfskeymgr::update (sfskey *nk, ptr ok, const str &path, u_int32_t l_opts, km_update_cb cb) { ptr sc = NULL; user_host_t uh; if (!get_userhost (path, &uh)) warn << path << ": cannot parse\n"; ustate *st = New ustate (nk, ok, sc, uh.hostname, uh.user, cb, l_opts); login (path, wrap (this, &sfskeymgr::u_gotlogin, st), wrap (&kmlcb2), ok, l_opts); } void sfskeymgr::u_gotlogin (ustate *st, str err, ptr sc, ptr k) { if (err) { done (st, err); return; } st->setcon (sc); st->ok = k; update (st); } void sfskeymgr::update (sfskey *nk, ptr ok, ptr scon, const str &path, const str &user, u_int32_t u_opts, km_update_cb cb) { ustate *st = New ustate (nk, ok, scon, path, user, cb, u_opts); update (st); } void sfskeymgr::update (ustate *st) { if (!(st->u_opts & KM_NOSRP)) getcertinfo (st); else getuinfo (st); } void sfskeymgr::getcertinfo (ustate *st) { sfsauth2_query_arg aqa; aqa.type = SFSAUTH_CERTINFO; aqa.key.set_type (SFSAUTH_DBKEY_NULL); st->c->call (SFSAUTH2_QUERY, &aqa, &st->aqr, wrap (this, &sfskeymgr::gotcertinfo, st)); } void sfskeymgr::gotcertinfo (ustate *st, clnt_stat err) { if (err) { done (st, cse2str (err)); return; } if (st->aqr.type == SFSAUTH_ERROR) { done (st, *st->aqr.errmsg); return; } sfsauth_certinfores certres = *(st->aqr.certinfo); if (certres.name.len () <= 0) { done (st, "Could not get server's name/realm"); return; } if (certres.info.status == SFSAUTH_CERT_REALM) warnx << st->hostname << ": authserver is in realm " << certres.name << "\n"; st->realm = certres.name; getuinfo (st); } void sfskeymgr::getuinfo (ustate *st) { sfsauth2_query_arg aqa; aqa.type = SFSAUTH_USER; if (st->ok) { aqa.key.set_type (SFSAUTH_DBKEY_PUBKEY); if (!st->ok->export_pubkey (aqa.key.key)) { done (st, "could not export public key"); return; } } else if (st->user) { aqa.key.set_type (SFSAUTH_DBKEY_NAME); *aqa.key.name = st->user; } else { done (st, "must provide either an oldkey or a user name"); return; } st->c->call (SFSAUTH2_QUERY, &aqa, &st->aqr, wrap (this, &sfskeymgr::gotuinfo, st)); } void sfskeymgr::gotuinfo (ustate *st, clnt_stat err) { if (err) { done (st, cse2str (err)); return; } str rc; if ((rc = check_uinfo (st->aqr))) { done (st, rc); return; } if (st->ok && st->aqr.userinfo->vers == 0) { done (st, "No account found for user"); return; } if (!st->ok && st->aqr.userinfo->vers > 0 && !(st->u_opts & KM_REREG)) { done (st, "Account exists; use -f flag to force reregister"); return; } if (!setup_uinfo (st)) return; sign_updatereq (st); } str sfskeymgr::check_uinfo (const sfsauth2_query_res &aqr) { if (aqr.type == SFSAUTH_ERROR) { return *(aqr.errmsg); } if (aqr.type != SFSAUTH_USER) { return ("Unexpected server response"); } return NULL; } void sfskeymgr::doupdate (ustate *st) { st->c->call (SFSAUTH2_UPDATE, &st->aua, &st->aur, wrap (this, &sfskeymgr::gotres, st), st->scon->auth); } bool sfskeymgr::setup_uinfo (ustate *st) { // Needed for proactive signatures if (!(st->u_opts & KM_DLT) && (st->u_opts & KM_CHNGK)) { st->nk->key->set_hostname (st->scon->servinfo->mkpath ()); st->nk->key->set_username (st->user); } sfsauth_userinfo uinfo = *st->aqr.userinfo; if (!(st->u_opts & KM_NOPK)) { if (!st->nk->key->export_pubkey (&uinfo.pubkey)) { done (st, "cannot export new public key"); return false; } } if ((st->u_opts & (KM_SKH | KM_DLT))) { if (!st->nk->key->export_keyhalf (&uinfo.srvprivkey, &st->delta)) { done (st, "cannot export server keyhalf"); return false; } } else { uinfo.srvprivkey.set_type (SFSAUTH_KEYHALF_NONE); } if (!(st->u_opts & KM_NOSRP)) { if (!st->nk->pwd) { done (st, "Cannot use SRP without a password"); return false; } srp_client srpc; if (!(uinfo.pwauth = srpc.create (N, g, st->nk->pwd, st->realm, st->nk->cost, 0))) { done (st, "could not create SRP info"); return false; } if (!(st->u_opts & KM_NOESK)) { str epk; str2wstr (epk); if (!st->nk->key->export_privkey (&epk, &srpc.eksb)) { done (st, "could not encrypt private key"); return false; } uinfo.privkey = epk; } warnx << st->hostname << ": New SRP key: " << st->user << "@" << st->realm << "/" << N.nbits () << "\n"; } uinfo.vers ++; st->uinfo = uinfo; return true; } void sfskeymgr::sign_updatereq (ustate *st) { sfsauth2_update_arg &ua = st->aua; u_int32_t opts = 0; if (st->u_opts & KM_KPSRP) opts |= SFSUP_KPSRP; if (st->u_opts & KM_KPESK) opts |= SFSUP_KPESK; if (st->u_opts & KM_KPPK) opts |= SFSUP_KPPK; ua.req.opts = opts; ua.req.type = SFS_UPDATEREQ; ua.req.authid = st->scon->authid; ua.req.rec.set_type (SFSAUTH_USER); *ua.req.rec.userinfo = st->uinfo; sfsauth2_sigreq sr; sr.set_type (SFS_UPDATEREQ); *sr.updatereq = ua.req; if (st->ok) st->sigs++; if (!st->delta) st->sigs++; if (st->ok) { ua.authsig.alloc (); st->ok->sign (sr, st->scon->authinfo, wrap (this, &sfskeymgr::gotsig, st, ua.authsig)); } if (!st->delta) { ua.newsig.alloc (); st->nk->key->sign (sr, st->scon->authinfo, wrap (this, &sfskeymgr::gotsig, st, ua.newsig)); } } void sfskeymgr::gotsig (ustate *st, sfs_sig2 *target, str err, ptr sig) { if (err) st->sigerr = err; if (sig) *target = *sig; if (!--st->sigs) if (st->sigerr) done (st, st->sigerr); else doupdate (st); } void sfskeymgr::gotres (ustate *st, clnt_stat err) { if (err) { done (st, cse2str (err), false); } else if (!st->aur.ok) { done (st, *st->aur.errmsg); } else { done (st, NULL); } } void sfskeymgr::done (ustate *st, str err, bool noconf) { (*st->cb) (err, noconf); delete st; return; } void sfskeymgr::done (lstate *st, str err) { if (st->fetching) fetching = false; blocked_fetch_t *bf; while (fqueue.size ()) if ((bf = fqueue.pop_front ())) { dologin (bf->st, bf->ki); delete bf; } if (st->opts & KM_PKONLY) { (*st->cb2) (err, st->pkey); } else { (*st->cb) (err, st->scon, st->key); } delete st; return; } ptr sfskeymgr::getsrpcon (sfskeyinfo *ki) { if (!ki->remote) return NULL; user_host_t uh; if (!get_userhost (ki->fn (), &uh)) return NULL; key_con_t **k = keycontab[uh.hash]; if (k) return (*k)->con; return NULL; } static void login_errcb (km_login_cb c1, km_login_cb2 c2, u_int32_t opts, str err) { if (opts & KM_PKONLY) (*c2) (err, NULL); else (*c1) (err, NULL, NULL); } void sfskeymgr::login (const str &hostname, km_login_cb cb, km_login_cb2 cb2, ptr key, u_int32_t opts) { if (!hostname) { login_errcb (cb, cb2, opts, "No hostname given"); return; } user_host_t uh; if (!get_userhost (hostname, &uh)) { login_errcb (cb, cb2, opts, strbuf (hostname << ": malformed hostname")); return; } key_con_t **k = keycontab[uh.hash]; if (k) { if (opts & KM_PKONLY) (*cb2) (NULL, (*k)->key); else (*cb) (NULL, (*k)->con, (*k)->key); return; } if (!uh.sfspath) { str err = strbuf () << uh.hostname << ": full SFS path or SRP connection needed"; login_errcb (cb, cb2, opts, err); return; } lstate *ls = New lstate (uh, key, cb, cb2, opts); sfs_connect_path (uh.sfspath, SFS_AUTHSERV, wrap (this, &sfskeymgr::gotcon, ls), true, !(uh.sfspath == "-")); } void sfskeymgr::gotcon (lstate *ls, ptr sc, str err) { if (!sc || err) { done (ls, err); return; } ls->scon = sc; ls->c = aclnt::alloc (sc->x, sfs_program_1); if (ls->uh.sfspath == "-" && (ls->opts & KM_UNX)) { unixlogin (ls); } else if (!ls->key || (ls->opts & KM_PKONLY)) { getpubkey (ls); } else { sfs_dologin (sc, ls->key, 0, wrap (this, &sfskeymgr::gotlogin, ls)); } } void sfskeymgr::getpubkey (lstate *ls) { ref c (aclnt::alloc (ls->scon->x, sfsauth_prog_2)); sfsauth2_query_arg aqa; aqa.type = SFSAUTH_USER; aqa.key.set_type (SFSAUTH_DBKEY_NAME); *aqa.key.name = ls->uh.user; c->call (SFSAUTH2_QUERY, &aqa, &ls->aqr, wrap (this, &sfskeymgr::gotpubkey, ls)); } void sfskeymgr::gotpubkey (lstate *ls, clnt_stat err) { if (err) { done (ls, cse2str (err)); return ; } str rc; if ((rc = check_uinfo (ls->aqr))) { done (ls, rc); return ; } sfskeyinfo *ki = NULL; ptr pub = sfscrypt.alloc (ls->aqr.userinfo->pubkey); if (!pub) { done (ls, "No such public key on server."); return; } if (ls->opts & KM_PKONLY) { ls->pkey = pub; done (ls, NULL); return; } if (!(ki = ks->getkeys (pub))) { done (ls, "cannot find public key"); return ; } dologin (ls, ki); } void sfskeymgr::dologin (lstate *ls, sfskeyinfo *ki) { str err; while (ki && ki->bad) ki = ki->next; if (!ki) { done (ls, "No suitable keys found"); } ptr priv = ki->privk; if (!priv) { if (fetching && !ls->fetching) { fqueue.push_back (New blocked_fetch_t (ls, ki)); return ; } fetching = true; ls->fetching = true; sfskey *k = fetch (ki, &err); if (err) { delete k; done (ls, err); return ; } priv = k->key; delete k; } if (!priv) { done (ls, "Internal key tables are corrupted"); return; } ls->key = priv; sfs_dologin (ls->scon, priv, 0, wrap (this, &sfskeymgr::gotlogin_r, ls, ki)); } void sfskeymgr::gotlogin_r (lstate *ls, sfskeyinfo *ki, str err) { if (!err) { ki->privk = ls->key; gotlogin (ls, NULL); return ; } ki->bad = true; while (ki && ki->bad) ki = ki->next; if (ki) warn << "Retryable error: "; else warn << "Final retry failed: "; warn << err << "\n"; if (err && ki) { dologin (ls, ki); return; } done (ls, "All suitable keys failed"); return ; } void sfskeymgr::gotlogin (lstate *ls, str err) { if (err) { done (ls, err); return ; } insertcon (ls->uh, ls->scon, ls->key); done (ls, NULL); } void sfskeymgr::unixlogin (lstate *ls, int ntries) { if (ntries == 0) { ls->arg.set_type (SFS_UNIXPWAUTH); ls->arg.pwauth->req.type = SFS_SIGNED_AUTHREQ; ls->arg.pwauth->req.authid = ls->scon->authid; ls->arg.pwauth->req.user = user; } sfs_loginarg larg; if (uid) ls->arg.pwauth->password = getpwd (" UNIX password: "); else ls->arg.pwauth->password = ""; ls->arg.pwauth->req.seqno = ntries; larg.seqno = ntries; if (!xdr2bytes (larg.certificate, ls->arg)) { done (ls, "Cannot marshal login arguments"); return ; } ls->c->call (SFSPROC_LOGIN, &larg, &ls->res, wrap (this, &sfskeymgr::gotunixlogin, ls, ntries)); } void sfskeymgr::gotunixlogin (lstate *ls, int ntries, clnt_stat err) { if (err) { done (ls, cse2str (err)); return; } if (ls->res.status == SFSLOGIN_OK) { ls->scon->auth = authuint_create (*ls->res.authno); gotlogin (ls, NULL); return; } if (!uid) { done (ls, "Root login rejected by authserver"); return; } if (ntries == 2) { done (ls, "Too login failures."); return; } unixlogin (ls, ntries + 1); } sfskey * sfskeystore::fetch (sfskeyinfo *ki, sfskeymgr *km, str *err, str *pwd, u_int32_t l_opts) { assert (ki && err && pwd); str keyname = ki->afn (); sfskey *k = New sfskey (); if (!ki->remote) k->pwd = *pwd; ptr scon; bool changerealm; bool *bp = (l_opts & KM_REALM) ? &changerealm : NULL; str rc = sfskeyfetch (k, keyname, &scon, NULL, false, bp); if (!rc && scon && bp && *bp && !(l_opts & KM_FRC) && !get_yesno ("Accept changes to SRP realm? [y/N] ")) rc = "Changes to SRP realm not accepted"; if (rc) { if (err) *err = rc; return NULL; } if (!(g_opts & (KM_NODCHK | KM_NOHM))) { str dfk = defkey2_readlink (); if (dfk) { dfk = dir_split (dfk); if (ki->fn () == dfk) ki->defkey = true; } } *pwd = k->pwd; if (scon) { if (!km->insert (keyname, scon, k->key, k, err)) return NULL; ki->rkt = k->key->is_proac () ? SFSKI_PROAC : SFSKI_STD; } return k; } bool sfskeymgr::bump_privk_version (sfskeyinfo *ki, u_int32_t l_opts) { bool ret = false; if (ki->remote) { if (l_opts & (KM_NOSRP | KM_NOESK)) warn << ki->fn () << ": refusing to update; client share will be lost\n"; else ret = true; } else { if (ki->bump_privk_version ()) ret = ks->check_local (ki, l_opts); } return ret; } bool sfskeymgr::insert (const str &kn, ptr con, ptr key, sfskey *kw, str *err) { user_host_t uh; if (!get_userhost (kn, &uh)) { *err = strbuf (kn << ": invalid keyname"); return false; } insertcon (uh, con, key, kw); return true; } sfskeyinfo * sfskeystore::getkeys (ptr pub) { lsdir (); str hv = pub->get_pubkey_hash (); sfskeyinfo **k; sfskeyinfo_proac **s; if ((k = pktab_std[hv])) return *k; else if ((s = pktab_proac[hv])) return *s; return NULL; } void sfskeystore::prepend (ptr priv) { lsdir (); str hv = priv->get_pubkey_hash (); if (!priv->is_proac ()) { sfskeyinfo *ki = New sfskeyinfo (priv); pktab_std.insert (hv, ki); } else { sfskeyinfo_proac *ks = New sfskeyinfo_proac (priv); sfskeyinfo_proac **k = pktab_proac[hv]; if (k) ks->next = *k; pktab_proac.insert (hv, ks); } } void sfskeystore::hashit (ptr pub, sfskeyinfo *k_new) { str hv = pub->get_pubkey_hash (); if (k_new->kt == SFSKI_STD) { pktab_std.insert (hv, k_new); return; } else if (k_new->kt != SFSKI_PROAC) { return; } sfskeyinfo_proac *ks = reinterpret_cast (k_new); sfskeyinfo_proac **k = pktab_proac [hv]; if (!k) { pktab_proac.insert (hv, ks); return; } sfskeyinfo_proac *pp = NULL; for (sfskeyinfo_proac *p = *k; p; p = reinterpret_cast (p->next)) { if (ks->host_priority < p->host_priority || (ks->host_priority == p->host_priority && !(ks->host == p->host))) { ks->next = p; if (pp) pp->next = ks; else *k = ks; break; } else if (ks->host_priority == p->host_priority) { if (ks->privk_version > p->privk_version) { ks->next = p->next; if (pp) pp->next = ks; else *k = ks; } break; } else if (!p->next) { p->next = ks; break; } pp = p; } } void sfskeystore::init (bool needkeysdir) { if (!(g_opts & KM_NOHM)) { if (g_opts & KM_NOCRT) { if (needkeysdir) { agent_ckdir (true); } else if (!init_ck) { agent_ckdir (false); init_ck = true; } } else { agent_mkdir (); } dir = userkeysdir; } } bool sfskeystore::lsdir () { if (read) return true; if (g_opts & KM_NOHM) return true; init (true); if (!dir) fatal << "No keys directory found ($HOME/.sfs/authkeys/)\n"; DIR *dp; struct dirent *dip; dp = opendir (dir); if (!dp) fatal << dir << ": Cannot open directory.\n"; str dfk = defkey2_readlink (); if (dfk) dfk = dir_split (dfk); sfskeyinfo *k; while ((dip = readdir (dp))) { if (!strcmp (dip->d_name, ".") || !strcmp (dip->d_name, "..")) continue; k = sfskeyinfo::alloc (str (dip->d_name), dir); str file = file2str (k->afn ()); if (file) { ptr pub = sfscrypt.alloc_from_priv (file); if (pub) hashit (pub, k); } if (k) { if (dfk && k->fn () == dfk) k->defkey = true; k->exists = true; ls[k->kt].push_back (k); } } closedir (dp); read = true; return true; } sfskeyinfo * sfskeystore::generate (sfskey *k, u_int32_t l_opts) { lsdir (); str kn = k->keyname; str knp = kn; if (kn) { char *cp = strchr (k->keyname, '#'); if (cp) kn = substr (kn, 0, cp - k->keyname - 1); knp = strbuf (kn << "#"); } ptr proac = k->key; sfski_type kt = proac->is_proac () ? SFSKI_PROAC : SFSKI_STD; sfskeyinfo *ki = NULL; sfskeyinfo_proac *s = NULL; str path, hn; if (kt != SFSKI_PROAC || ((ki = getkeys (proac)) && ki->kt != SFSKI_PROAC) || !(path = proac->get_hostname ()) || !sfs_parsepath (path, &hn, NULL, NULL, NULL) || !hn) return generate (knp, kt, false); sfskeyinfo_proac *sret = NULL; int hpmax = 0; for ( ; ki ; ki = ki->next) { s = reinterpret_cast (ki); if (s->host_priority > hpmax) hpmax = s->host_priority; if (s->host == hn) { sret = New sfskeyinfo_proac (*s); sret->kn = kn; sret->privk_version ++; break; } } sfskeyinfo *ret = NULL; if (!sret) { if (s) { sret = New sfskeyinfo_proac (*s); sret->kn = kn; sret->host = hn; sret->privk_version = 1; sret->host_priority = hpmax + 1; ret = sret; } else { ret = generate (knp, kt, false); ret->set_hostname (hn); } } else { ret = sret; } if (!ret) return NULL; if (!check_local (ret, l_opts)) { warn << ret->afn () << ": refusing to overwite\n"; return NULL; } return ret; } sfskeyinfo * sfskeystore::generate (str raw, sfski_type xt, bool kcomplete) { lsdir (); bool exists = false; if (!raw) { str hn = myname (); // XXX - this should be more robust if (!hn) { warn << "Could not fetch local hostname\n"; return NULL; } raw = strbuf (user << "@" << hn); } else if (xt != SFSKI_NONE && raw.cstr () [raw.len () - 1] == '#') { raw = substr (raw, 0, raw.len () - 1); } sfskeyinfo *m = NULL; if (!(g_opts & KM_NOHM)) { const vec &k = ls [xt]; for (u_int i = 0; i < k.size (); i++) { if (k[i]->cmp (raw, kcomplete)) continue; if (kcomplete) { exists = true; break; } if (!m || k[i]->gcmp (*m) > 0) m = k[i]; } } sfskeyinfo *r = sfskeyinfo::alloc (raw, dir, xt, m); r->exists = exists; r->gen = true; return r; } sfskeyinfo * sfskeystore::search (const str &raw, bool kcomplete) { bool mmk = false; lsdir (); sfskeyinfo *p = NULL, *tp = NULL; for (int i = 0; i <= SFSKI_PROAC; i++) { tp = search (raw, sfski_type (i), kcomplete); if (p && tp) { warn << raw << ": multiple matching keys found\n"; mmk = true; } if (tp) p = tp; } if (mmk) warn << "using key: " << p->fn () << "\n"; return p; } sfskeyinfo * sfskeystore::search (const str &raw, sfski_type kt, bool kcomplete) { sfskeyinfo *m = NULL; if (g_opts & KM_NOHM) return m; const vec &k = ls [kt]; for (u_int i = 0; i < k.size (); i++) { if (k[i]->cmp (raw, kcomplete)) continue; if (!m || k[i]->cmp (m) > 0) m = k[i]; if (kcomplete && m) break; } return m; } bool sfskeyinfo_proac::set_hostname (const str &s) { // XXX - should be more robust if (s) { host = s; } else if (!(host = myname ())) { warn << "Cannot get my hostname\n"; return false; } return true; } sfskeyinfo * sfskeyinfo::alloc (const str &raw, str dir, sfski_type kt, sfskeyinfo *m) { int version = (m ? m->version + 1 : 1); if (m && !dir) dir = m->dir; switch (kt) { case SFSKI_STD: return New sfskeyinfo_std (raw, dir, version); case SFSKI_PROAC: { sfskeyinfo_proac *k = New sfskeyinfo_proac (raw, dir, version); if (!k->set_hostname ()) { delete k; k = NULL; } return k; } default: return New sfskeyinfo (raw, dir, kt, version); } } sfskeyinfo * sfskeyinfo::alloc (const str &raw, str dir) { assert (raw && raw.len ()); str file; if (!dir) file = dir_split (raw, &dir); else file = raw; vec kv1, kv2; int c = split (&kv1, pound, file, 2); if (c != 2) return New sfskeyinfo (file, dir); c = split (&kv2, comma, kv1[1], 3); sfskeyinfo *ret = NULL; if (c == 1) { int v; if (!convertint (kv2[0], &v)) ret = New sfskeyinfo (file, dir); else ret = New sfskeyinfo_std (kv1[0], dir, v); } else if (c == 3) { int pv, hp, sv; vec kv3; if (!convertint (kv2[0], &pv) || !convertint (kv2[2], &sv) || split (&kv3, dot, kv2[1], 2) != 2 || !convertint (kv3[0], &hp)) ret = New sfskeyinfo (file, dir); else ret = New sfskeyinfo_proac (kv1[0], dir, pv, kv3[1], hp, sv); } else { ret = New sfskeyinfo (file, dir); } return ret; } bool sfskeystore::check_local (sfskeyinfo *ki, u_int32_t l_opts) { int rc = access (ki->afn (), F_OK); if (rc < 0 && errno == ENOENT) { ki->exists = false; ki->gen = true; } else if (rc == 0) { ki->exists = true; if (l_opts & KM_FGEN) { if (!(l_opts & KM_FRC)) return false; rc = access (ki->afn (), W_OK); if (rc < 0) return false; } } else { return false; } return true; } sfskeyinfo * sfskeyinfo::alloc (str raw, sfskeystore *ks, u_int32_t l_opts) { sfskeyinfo *ret = NULL; sfski_type kt = (l_opts & KM_PROAC) ? SFSKI_PROAC : SFSKI_STD; if (!raw || raw == "#") { if (l_opts & KM_FGEN) return ks->generate (NULL, kt, false); str kn = ks->defkey2_readlink (); if (kn) { ret = sfskeyinfo::alloc (kn); if (!ks->check_local (ret, l_opts)) { warn << kn << ": refusing to overwrite\n"; return NULL; } } else if (l_opts & KM_GEN) { ret = ks->generate (NULL, kt, false); } return ret; } char *cp = strchr (raw.cstr (), '#'); char *cp2 = strchr (raw.cstr (), '/'); char *cp3 = strchr (raw.cstr (), '@'); if (!cp || cp2 || (l_opts & KM_NOSRC)) { if (iskeyremote (raw, l_opts & KM_PKONLY)) { if (cp3 && raw.len () && (raw.cstr () + raw.len () - 1 == cp3)) { warn << raw << ": illegal keyname\n"; return NULL; } if (cp3 && cp3 == raw.cstr ()) raw = strbuf () << ks->getuser () << raw; ret = New sfskeyinfo (raw); ret->remote = true; ret->exists = true; } else { if (!cp2) raw = strbuf () << "./" << raw; ret = sfskeyinfo::alloc (raw); if (!ks->check_local (ret, l_opts)) { warn << raw << ": refusing to overwrite\n"; return NULL; } } } else { bool kcomplete = (cp[1] != 0); if (!kcomplete) raw = substr (raw, 0, raw.len () - 1); if ((l_opts & KM_FGEN) || (!(ret = ks->search (raw, kcomplete)) && (l_opts & KM_GEN))) ret = ks->generate (raw, kt, kcomplete); } return ret; } sfskeyinfo * sfskeymgr::getkeyinfo (const str &keyname, u_int32_t l_opts) { return sfskeyinfo::alloc (keyname, ks, l_opts); } sfskeyinfo * sfskeymgr::getkeyinfo (sfskey *k, u_int32_t l_opts) { return ks->generate (k, l_opts); } sfskeyinfo * sfskeymgr::getkeyinfo_list (const str &keyname, u_int32_t l_opts) { sfskeyinfo *ki = getkeyinfo (keyname, l_opts); if (ki && ki->has_backup_keys ()) { sfskeyinfo *nki = ks->fill_list (ki); for (sfskeyinfo *p = nki; p; p = p->next) if (p->cmp (ki) == 0) return nki; } return ki; } sfskey * sfskeymgr::fetch (sfskeyinfo *ki, str *err, u_int32_t l_opts) { if (!ki->exists) { *err = strbuf (ki->afn () << ": cannot find key"); return NULL; } user_host_t uh; key_con_t **kct = NULL; sfskey *k = NULL; if (ki->remote && get_userhost (ki->afn (), &uh) && (kct = keycontab[uh.hash]) && (k = (*kct)->kw)) { return (k); } return ks->fetch (ki, this, err, &pwd, l_opts); } void sfskeymgr::fetchpub (sfskeyinfo *ki, km_login_cb2 cb) { str fn = ki->afn (); if (!ki->remote) { str raw = file2str (fn); if (!raw) { (*cb) (strbuf () << fn << ": cannot open file", NULL); return ; } ptr k = sfscrypt.alloc_from_priv (raw); if (!k) { (*cb) (strbuf () << fn << ": could not parse key", NULL); return; } (*cb) (NULL, k); return; } else { user_host_t uh; if (!get_userhost (fn, &uh)) (*cb) (strbuf () << fn << ": cannot parse user/host", NULL); if (uh.sfspath) login (fn, wrap (&kmlcb), cb, NULL, KM_PKONLY); else { str err; sfskey *k = fetch (ki, &err); if (!k) (*cb) (err, NULL); else (*cb) (NULL, k->key); } } } sfskeyinfo * sfskeystore::fill_list (sfskeyinfo *ki) { if (!ki->exists) return NULL; lsdir (); str k = file2str (ki->afn ()); ptr pub; if (k && (pub = sfscrypt.alloc_from_priv (k))) ki = getkeys (pub); return ki; } void sfskeymgr::fetch_all (sfskeyinfo *ki, cbkik cb) { str err; sfskey *k = NULL; for ( ; ki ; ki = ki->next) { if (!ki->exists || ki->bad || ki->flagged) continue; k = fetch (ki, &err); if (err) { warn << ki->afn () << ": " << err << "\n"; } else if (!k) { warn << ki->afn () << ": no key returned\n"; } else { ki->flagged = true; (*cb) (ki, k); } } } void sfskeymgr::fetch_from_list (sfskeyinfo *ki, cbk cb) { str err; sfskey *k = NULL; while (ki) { while (ki && (!ki->exists || ki->bad)) ki = ki->next; if (ki) { k = fetch (ki, &err); if (err) { fflerr = true; warn << ki->afn () << ": " << err << "\n"; k = NULL; } else if (!k) { fflerr = true; warn << ki->afn () << ": no key returned\n"; } else { break; } ki = ki->next; } } if (!k) { (*cb) (NULL); return ; } k->key->init (wrap (this, &sfskeymgr::initcb, ki, k, cb)); } void sfskeymgr::initcb (sfskeyinfo *ki, sfskey *k, cbk cb, str err) { if (!err) { if (fflerr) warn << ki->afn () << ": key initialize succeded\n"; (*cb) (k); return; } fflerr = true; warn << ki->afn () << ": key initialization failed:\n" << err << "\n"; ki = ki->next; fetch_from_list (ki, cb); } sfskey * sfskeymgr::fetch_or_gen (sfskeyinfo *ki, str *errp, u_int nbits, u_int cost, u_int32_t l_opts) { assert (errp); if (ki->exists) { sfskey *k = fetch (ki, errp); return k; } sfskey *k = New sfskey (); if (cost) k->cost = cost; bool labprompt; if (ki->keylabel) { labprompt = false; k->keyname = ki->keylabel; } else { labprompt = !(ki->is_proactive ()); k->keyname = ki->fn (); } strbuf prompt (ki->fn_std () << " (" << (l_opts & KM_PROAC ? "2-Schnorr" : "Rabin") << ")"); str rc = sfskeygen (k, nbits, prompt, labprompt, (l_opts & KM_NOPWD), (l_opts & KM_NOKBD), false, (l_opts & KM_PROAC) ? SFS_2SCHNORR : SFS_RABIN); if (rc) { *errp = rc; delete k; return NULL; } return k; } void sfskeymgr::insertcon (const user_host_t &uh, ptr con, ptr k, sfskey *kw) { key_con_t *kct = New key_con_t (con, k, kw); keycontab.insert (uh.hash, kct); keycontab.insert (strbuf (uh.user << "@" << con->servinfo->mkpath ()), kct); keycontab.insert (strbuf (uh.user << "@" << con->servinfo->get_hostname ()), kct); } ptr sfskeymgr::getservinfo () { if (servinfo) return servinfo; sfs_connect_path ("-", SFS_AUTHSERV, wrap (this, &sfskeymgr::si_gotcon), true, false); checkflag = true; while (checkflag) acheck (); return servinfo; } void sfskeymgr::si_gotcon (ptr s, str err) { if (err) warn << "Cannot connect to local authserver: " << err << "\n"; if (s) servinfo = s->servinfo; checkflag = false; } bool sfskeymgr::save (sfskey *k, sfskeyinfo *ki, u_int32_t l_opts) { str hn = k->key->get_hostname (); if (hn) { user_host_t uh; if (!get_userhost (hn, &uh)) { warn << ki->fn () << ": cannot set hostname\n"; return false; } ki->set_hostname (uh.hostname); } if (l_opts & KM_CHNGK) k->keyname = ki->fn (); return ks->save (k, ki, l_opts); } bool sfskeymgr::select (sfskeyinfo *ki, u_int32_t l_opts) { return ks->setlink (ki->afn (), l_opts); } bool sfskeystore::save (sfskey *k, sfskeyinfo *ki, u_int32_t l_opts) { str rc; bool ret = true; if (ki->gen && (!ki->exists || (l_opts & KM_FRC))) { str kn = ki->afn (); rc = sfskeysave (kn, k, !(l_opts & KM_FRC)); if (rc) { warn << rc << "\n"; ret = false; } else { warnx << "wrote key: " << kn << "\n"; if (!(l_opts & KM_NOLNK) && !(g_opts & KM_NOHM) && ki->setlink () && !setlink (kn, l_opts)) return false; } } return ret; } bool sfskeystore::setlink (const str &target, u_int32_t l_opts) { if (g_opts & KM_NOHM) return false; struct stat sb; str ln = defkey2 (); int rc = lstat (ln.cstr (), &sb); if (rc < 0 && errno != ENOENT) { warn << ln << ": cannot access file\n"; return false; } if (rc == 0) { if (!S_ISLNK (sb.st_mode)) { if (S_ISREG (sb.st_mode) && (l_opts & KM_FRCLNK)) { if (unlink (ln.cstr ()) < 0) { warn << ln << ": cannot delete\n"; return false; } if (!access (ln.cstr (), F_OK)) { warn << ln << ": delete failed\n"; return false; } } else { warn << ln << ": file exists and is not a symlink; not overwriting\n"; return false; } } else if (unlink (ln.cstr ()) < 0) { warn << ln << ": cannot overwrite\n"; return false; } } if (symlink (target.cstr (), ln.cstr ())) { warn << ln << ": " << strerror (errno); return false; } return true; } bool sfskeymgr::get_userhost (const str &h, user_host_t *uh) { if (!h || !h.len () || h == "-") { uh->user = user; uh->hostname = myname (); uh->sfspath = "-"; uh->hash = strbuf (user << "@-"); return true; } static rxx re ("^(([^@]*)@)?([^@%,]+)(%(\\d+))?(,([a-zA-Z0-9]+))?$"); if (!re.search (h)) return false; str u2 = re[2]; str hostname = re[3]; str ports = re[5]; str hostid = re[7]; int port = sfs_port; assert (hostname); if (ports && ports.len ()) if (!convertint (ports, &port)) return false; if (hostname == "-" && (port != sfs_port)) return false; if (u2 && u2.len ()) uh->user = u2; else uh->user = user; uh->hostname = hostname; if (hostid) { if (port != sfs_port) uh->sfspath = strbuf ("@" << hostname << "%" << port << "," << hostid); else uh->sfspath = strbuf ("@" << hostname << "," << hostid); } else { if (hostname == "-") uh->sfspath = hostname; else uh->sfspath = NULL; } if (port != sfs_port) uh->hash = strbuf (user << "@" << hostname << "%" << port); else uh->hash = strbuf (user << "@" << hostname); return true; } void sfskeymgr::add_keys (const vec &keys) { sfskeyinfo *ki; sfskey *k; str errstr; for (u_int i = 0; i < keys.size (); i++) { if (!(ki = getkeyinfo (keys[i]))) fatal << keys[i] << ": invalid keyname\n"; if (!(k = fetch (ki, &errstr))) fatal << keys[i] << ": could not fetch key:\n" << errstr << "\n"; add (k->key); } } void sfskeymgr::check_connect (const vec &servers, cbv cb) { ncb = servers.size (); for (u_int i = 0; i < servers.size (); i++) login (servers[i], wrap (this, &sfskeymgr::cc_cb, servers[i], cb), wrap (&kmlcb2)); } void sfskeymgr::cc_cb (str srv, cbv cb, str err, ptr con, ptr k) { if (err) { fatal << srv << ": " << err << "\n"; } else if (!con || !k) { fatal << srv << ": Cannot establish connection or login\n"; } if (--ncb == 0) (*cb) (); } bool sfskeymgr::add_con (ptr key, ptr *sc, str *hn) { if (!key->get_coninfo (sc, hn)) return false; str err; if (!insert (*hn, *sc, key, NULL, &err)) { warn << err << "\n"; return false; } return true; } str sfskeyinfo::afn () const { str ret; if (dir) { u_int dlen = dir.len (); const char *d = dir.cstr (); u_int i; for (i = dlen - 1; i >= 0 && d[i] == '/'; i--) ; ret = strbuf (substr (dir, 0, i+1) << "/" << fn ()); } else { ret = fn (); } return ret; }