/* $Id: authdb.C,v 1.19 2001/01/13 19:46:11 dm Exp $ */ /* * * Copyright (C) 1999 David Mazieres (dm@uun.org) * Copyright (C) 1999 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 "authserv.h" #include "authdb.h" #include "rxx.h" #include "sha1.h" #include "serial.h" #include "sfsmisc.h" #include "ihash.h" #include "tempfile.h" vec userfiles; class authfile { str filename; int fd; int lineno; suio in; struct stat sb; void reset (); public: authfile () : fd (-1) {} ~authfile () { close (fd); } bool openfile (str path); bool checkfile (); void closefile () { reset (); } bool getentry (authentry *); }; str userfile::mkdbpath (str path) { char hname[sha1::hashsize]; sha1_hash (hname, path, path.len ()); return strbuf () << sfsauthcachedir << "/" << armor32 (hname, sizeof(hname)); } userfile::userfile (const str &p, const str &pp, bool r, const str &pre, const str &map, bool reg) : path (p), pub (pp), ro (r), prefix (pre), mapall (map), reg (reg), dbpath (ro ? mkdbpath (path) : path), xferpid (-1) { } inline str authentry2str (authentry *e) { strbuf b; b << e->keyname << ":" << e->pubkey << ":" << e->privs << ":"; if (e->srpinfo) { assert (!strchr (e->srpinfo, '\n')); b << e->srpinfo << ":"; if (e->privkey) { assert (!strchr (e->privkey, '\n')); b << e->privkey; } } else b << ":"; b << "\n"; return b; } void authfile::reset () { filename = NULL; if (fd >= 0) close (fd); fd = -1; lineno = 0; in.clear (); } bool authfile::openfile (str path) { reset (); if (lstat (path, &sb) < 0 || (fd = open (path, O_RDONLY)) < 0) { warn << path << ": " << strerror (errno) << "\n"; return false; } if (S_ISLNK (sb.st_mode)) { warn << path << ": cannot be a symbolic link\n"; reset (); return false; } filename = path; return true; } bool authfile::checkfile () { struct stat sb2; return lstat (filename, &sb2) >= 0 && stat_unchanged (&sb, &sb2); } /* Line format is: * keyname:key:privs:SRPinfo:privkey */ static rxx authrx ("^([\\w_/-]+):([^:]+):([^:]+):([^:]*)(:([^:]*))?$"); bool authfile::getentry (authentry *e) { str line; do { while (!(line = suio_getline (&in))) if (in.input (fd, 8192) <= 0) { if (in.resid ()) { warn << filename << ":" << lineno + 1 << ": incomplete last line\n"; in.clear (); } return false; } lineno++; authrx.search (line); if (!authrx.success ()) warn << filename << ":" << lineno << ": malformed line ignored\n"; } while (!authrx.success ()); e->keyname = authrx[1]; e->pubkey = authrx[2]; e->privs = authrx[3]; e->srpinfo = authrx[4]; e->privkey = authrx[6]; if (!e->privkey) e->privkey = ""; return true; } str userfile::remprefix (str keyname) { if (keyname && prefix) { if (keyname.len () < prefix.len () + 2 || memcmp (keyname, prefix, prefix.len ()) || keyname[prefix.len ()] != '/') return NULL; return substr (keyname, prefix.len () + 1); } return keyname; } sfsauth_stat userfile::update (authentry *e, bool add) { assert (!ro); str keyname; if (e) { keyname = remprefix (e->keyname); if (!keyname) return SFSAUTH_BADKEYNAME; if (e->srpinfo && (e->srpinfo.len () != strlen (e->srpinfo) || strchr (e->srpinfo, '\n') || !srp_server::sane (e->srpinfo))) { warn << "ignoring bad SRP data for user " << e->keyname << "\n"; e->srpinfo = NULL; } if (e->privkey && (!e->srpinfo || e->privkey.len () != strlen (e->privkey) || strchr (e->privkey, '\n'))) { warn << "ignoring bad private key data for user " << e->keyname << "\n"; e->privkey = NULL; } } authfile af; if (!af.openfile (dbpath)) return SFSAUTH_FAILED; str newdbpath (strbuf () << path << "#" << getpid () << "~"); tempfile newdb (newdbpath, 0400); str newpubpath; if (pub) newpubpath = strbuf () << pub << "#" << getpid () << "~"; tempfile newpub (newpubpath, 0444); sfsauth_stat ret = SFSAUTH_NOCHANGES; authentry ae; while (af.getentry (&ae)) { if (e) { if (keyname == ae.keyname) { ae = *e; if (ret == SFSAUTH_NOCHANGES) ret = SFSAUTH_OK; else if (ret == SFSAUTH_OK) warn << path << ": duplicate keyname " << keyname << "\n"; } else if (e->pubkey == ae.pubkey) ret = SFSAUTH_KEYEXISTS; } newdb << authentry2str (&ae); if (newpubpath) { ae.srpinfo = ae.privkey = ""; newpub << authentry2str (&ae); } } if (e && add && ret == SFSAUTH_NOCHANGES) { newdb << authentry2str (e); if (newpubpath) { ae = *e; ae.srpinfo = ae.privkey = ""; newpub << authentry2str (&ae); } ret = SFSAUTH_OK; } if (ret) if (e || ret != SFSAUTH_NOCHANGES) return ret; if (!af.checkfile ()) { warn ("%s: file modified during update\n", path.cstr ()); return SFSAUTH_FAILED; } if (!newdb.rename (path)) { warn ("%s: %m\n", path.cstr ()); return SFSAUTH_FAILED; } if (newpubpath && !newpub.rename (pub)) warn ("%s: %m\n", pub.cstr ()); return SFSAUTH_OK; } bool userfile::lookup (authentry *e) { str keyname = remprefix (e->keyname); authfile af; if (!af.openfile (dbpath)) return false; authentry ae; while (af.getentry (&ae)) { if (keyname && keyname != ae.keyname) continue; if (e->pubkey && e->pubkey != ae.pubkey) continue; *e = ae; if (mapall) e->privs = mapall; return true; } return false; } bool authlookup (authentry *e) { for (userfile *uf = userfiles.base (); uf < userfiles.lim (); uf++) if (uf->lookup (e)) return true; return false; } bool authadd (authentry *e) { for (userfile *uf = userfiles.base (); uf < userfiles.lim (); uf++) if (uf->reg) return !uf->update (e, true); return false; } bool authupdate (authentry *e) { for (userfile *uf = userfiles.base (); uf < userfiles.lim (); uf++) if (!uf->ro) switch (uf->update (e)) { case SFSAUTH_OK: return true; case SFSAUTH_NOCHANGES: break; default: return false; } return false; } void auth_cache_refresh () { static str xferpath; if (!xferpath) xferpath = fix_exec_path ("xfer"); static str timeoutarg; if (!timeoutarg) timeoutarg = strbuf ("-t%d", xfer_timeout); for (userfile *uf = userfiles.base (); uf < userfiles.lim (); uf++) { if (!uf->ro || uf->xferpid >= 0) continue; char *av[] = { "xfer", const_cast (timeoutarg.cstr ()), "--", const_cast (uf->path.cstr ()), const_cast (uf->dbpath.cstr ()), NULL }; if ((uf->xferpid = aspawn (xferpath, av)) >= 0) chldcb (uf->xferpid, wrap (uf, &userfile::xferdone)); } } void auth_cache_schedule () { auth_cache_refresh (); timecb (timenow + cache_recheck + rnd.getword () % 8, wrap (auth_cache_schedule)); } void authdbinit () { auth_cache_schedule (); for (userfile *uf = userfiles.base (); uf < userfiles.lim (); uf++) { if (uf->ro) { warn << "caching " << uf->path << " -> " << uf->dbpath << "\n"; continue; } if (access (uf->path, F_OK) < 0) { if (errno == ENOENT) { int fd = open (uf->path, O_RDWR|O_CREAT, 0600); if (fd < 0) warn ("%s: %m\n", uf->path.cstr ()); else { warn << "created " << uf->path << "\n"; close (fd); } } else warn ("%s: %m\n", uf->path.cstr ()); } if (uf->pub) uf->update (NULL); } }