/* $Id: authdb.C,v 1.20 2002/11/21 18:54:56 max Exp $ */

/*
 *
 * Copyright (C) 2001 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 "authdb.h"
#include "qhash.h"
#include "rxx.h"
#include <grp.h>

static str
aekey (const sfsauth_dbrec &ae)
{
  static rxx knrx ("^[^:]*:[^:]*");
  str astr = authdbrec2str (&ae);
  if (!astr)
    return NULL;
  if (!knrx.search (astr))
    panic << "missing colon: " << astr << "\n";
  return knrx[0];
}

#define SEARCH(compare)				\
  reset ();					\
  while (next ())				\
    if (compare)				\
      return true;				\
    return false;

bool 
authcursor::validate ()
{
  bool pe = false, ret = true, flag = true;
  reset ();
  while (flag) {
    flag = next (&pe);
    if (pe)
      ret = false;
  }
  return ret;
}

bool
authcursor::find_user_name (str name)
{
  SEARCH (ae.type == SFSAUTH_USER && ae.userinfo->name == name);
}

bool
authcursor::find_user_pubkey (const sfspub &pk)
{
  SEARCH (ae.type == SFSAUTH_USER && 
	  (pk == ae.userinfo->pubkey || pk == ae.userinfo->srvprivkey));
}

bool
authcursor::find_user_uid (u_int32_t id)
{
  SEARCH (ae.type == SFSAUTH_USER && ae.userinfo->id == id);
}

bool
authcursor::find_group_name (str name)
{
  SEARCH (ae.type == SFSAUTH_GROUP && ae.groupinfo->name == name);
}

bool
authcursor::find_group_gid (u_int32_t id)
{
  SEARCH (ae.type == SFSAUTH_GROUP && ae.groupinfo->id == id);
}

void
authcursor::getgroups (vec<u_int32_t> *groups, str user)
{
  reset ();
  while (next ()) 
    if (ae.type == SFSAUTH_GROUP) {
      u_int n = ae.groupinfo->members.size ();
      for (u_int i = 0; i < n; i++) {
	sfs_idname np = ae.groupinfo->members[i];
	if (np == user) {
	  groups->push_back (ae.groupinfo->id);
	  break;
	}
      }
    }
}

struct authcursor_etc_group : public authcursor {
  static authcursor_etc_group *lastcursor;

  authcursor_etc_group () {}
  ~authcursor_etc_group () { if (lastcursor == this) lastcursor = NULL; }

  bool setae (struct group *gr);
  void reset () { setgrent (); lastcursor = this; }
  bool next (bool *pep = NULL) 
  { assert (lastcursor == this); return setae (getgrent ()); }

  bool find_user_name (str name) { return false; }
  bool find_user_pubkey (const sfspub &pk) { return false; }
  bool find_user_uid (u_int32_t uid) { return false; }

  bool find_group_name (str name) { return setae (getgrnam (name)); }
  bool find_group_gid (u_int32_t gid) { return setae (getgrgid (gid)); }
};

authcursor_etc_group *authcursor_etc_group::lastcursor;

bool
authcursor_etc_group::setae (struct group *gr)
{
  if (!gr) {
    ae.set_type (SFSAUTH_ERROR);
    return false;
  }
  ae.set_type (SFSAUTH_GROUP);
  ae.groupinfo->name = gr->gr_name;
  ae.groupinfo->id = gr->gr_gid;
  ae.groupinfo->vers = 0;
  ae.groupinfo->owners.setsize (0);
  ae.groupinfo->audit = "";

  int i;
  for (i = 0; gr->gr_mem[i]; i++)
    ;
  ae.groupinfo->members.setsize (i);
  while (i-- > 0)
    ae.groupinfo->members[i] = gr->gr_mem[i];
  return true;
}

ptr<authcursor>
authdb_etc_group::open (bool writable, mode_t perm, bool wait)
{
  if (writable)
    return NULL;
  return New refcounted<authcursor_etc_group> ();
}

authcursor_file_append::~authcursor_file_append ()
{
  str tmppath = path << ".tmp";
  if (wfd >= 0) {
    close (wfd);
    if (lf->ok ())
      unlink (tmppath);
  }
}

ptr<authcursor_file_append>
authcursor_file_append::alloc (str path, mode_t perm, ref<lockfile> lf)
{
  str tmppath = path << ".tmp";
  int fd = open (tmppath, O_CREAT|O_WRONLY|O_TRUNC, perm);
  if (fd < 0) {
    warn << tmppath << ": " << strerror (errno) << "\n";
    return NULL;
  }
  return New refcounted<authcursor_file_append> (lf, path, fd);
}

bool
authcursor_file_append::update (bool create)
{
  if (wfd < 0)
    return false;
  str astr = authdbrec2str (&ae);
  if (!astr)
    return false;
  str k = aekey (ae);
  if (keys[k]) {
    warn << "duplicate: " << astr << "\n";
    return false;
  }
  keys.insert (k);
  suio_print (&buf, astr);
  buf.print ("\n", 1);
  if (buf.resid () >= 8192)
    buf.output (wfd);
  return true;
}

bool
authcursor_file_append::commit ()
{
  if (wfd < 0)
    return false;
  if (buf.resid () && buf.output (wfd) <= 0
      || fsync (wfd) < 0) {
    warn ("authcursor_file_append::commit: %s: %m\n", path.cstr ());
    return false;
  }
  assert (!buf.resid ());
  close (wfd);
  wfd = -1;
  str tmppath = path << ".tmp";
  if (lf->ok ()) {
    if (rename (tmppath, path) < 0) {
      warn ("authcursor_file_append::commit: %s: rename: %m\n",
	    tmppath.cstr ());
      unlink (tmppath);
      return false;
    }
    return true;
  }
  else {
    warn ("authcursor_file_append::commit: %s: lost the lock\n",
	  path.cstr ());
    unlink (tmppath);
    return false;
  }
}

struct authcursor_file : public authcursor {
  const str path;
  ptr<lockfile> lf;
  mode_t perm;
  int fd;
  suio buf;
  int lineno;
  bool error;

  authcursor_file (str p, mode_t pm, ptr<lockfile> l)
    : path (p), lf (l), perm (pm), fd (-1), error (false) { reset (); }
  ~authcursor_file ();
  void reset ();
  bool next (bool *pep = NULL);
  bool update (bool create = false);
  bool commit () { return !error; }
};

authcursor_file::~authcursor_file ()
{
  close (fd);
}

void
authcursor_file::reset ()
{
  if (fd >= 0)
    close (fd);
  fd = open (path, O_RDONLY);
  if (fd < 0) {
    if (errno != ENOENT)
      warn ("%s: %m\n", path.cstr ());
  }
  else 
    lseek (fd, 0, SEEK_SET);
  buf.clear ();
  lineno = 1;
}

bool
authcursor_file::next (bool *pep)
{
  if (pep) 
    *pep = false;
  if (fd < 0)
    return false;
  bool flush = false;
  str line;
  for (;;) {
    while (!(line = suio_getline (&buf))) {
      if (buf.resid () > 0x20000) {
	buf.clear ();
	flush = true;
	continue;
      }
      int n = buf.input (fd);
      if (n > 0)
	continue;
      if (n < 0)
	warn << path << ": " << strerror (errno) << "\n";
      else if (buf.resid ()) {
	warn << path << ": " << lineno << ": incomplete last line\n";
	if (pep) *pep = true;
      }
      return false;
    }
    lineno++;
    if (flush) {
      warn << path << ": " << lineno-1 << ": line too long\n";
      if (pep) *pep = true;
    }
    else if (!str2authdbrec (&ae, line)) {
      warn << path << ": " << lineno-1 << ": syntax error\n";
      if (pep) *pep = true;
    } else
      return true;
  }
}

bool
authcursor_file::update (bool create)
{
  if (!lf) {
    error = true;
    return false;
  }
  ptr<authcursor_file_append> c
    = authcursor_file_append::alloc (path, perm, lf);
  if (!c) {
    error = true;
    return false;
  }
  sfsauth_dbrec dbr = ae;
  str k = aekey (dbr);
  if (!k) {
    error = true;
    return false;
  }
  errno = 0;
  bool found = false;
  bool empty = true;
  for (reset (); next ();) {
    empty = false;
    str kk = aekey (ae);
    if (k == kk) {
      c->ae = dbr;
      found = true;
    } else
      c->ae = ae;
    if (!c->update ()) {
      error = true;
      return false;
    }
  }
  if (!found) {
    c->ae = dbr;
    if (!c->update ()) {
      error = true;
      return false;
    }
  }
  if ((errno && (!create || !empty)) || !c->commit ()) {
    warn << path << ": " << strerror (errno) << "\n";
    return false;
  }
  return true;
}

ptr<authcursor>
authdb_file::open (bool writable, mode_t perm, bool wait)
{
  ptr<lockfile> lf;
  if (writable && !(lf = lockfile::alloc (path << ".lock", wait)))
    return NULL;
  return New refcounted<authcursor_file> (path, perm, lf);
}

ptr<authcursor>
authdb_file::trunc (mode_t perm, bool wait)
{
  ptr<lockfile> lf = lockfile::alloc (path << ".lock", wait);
  if (!lf)
    return NULL;
  return authcursor_file_append::alloc (path, perm, lf);
}

