/* $Id: authserv.C,v 1.55 2001/08/27 03:09:20 ericp Exp $ */

/*
 *
 * Copyright (C) 1999 Michael Kaminsky (kaminsky@lcs.mit.edu)
 * Copyright (C) 1998, 1999 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 "authserv.h"
#include "parseopt.h"
#include "rxx.h"

sfssrp_parms srpglobal;
ptr<rabin_priv> authsrvkey;
sfs_servinfo servinfo;
sfs_hash hostid;
bool accepts_changes = true;
bool accepts_reg     = false;
str sfsauthcachedir;

// all in seconds
int xfer_timeout = 60;
int cache_expire = 7 * 24 * 60 * 60;
int cache_recheck = 60;

void
unixaccept (ptr<axprt_unix> x, const authunix_parms *aup)
{
  if (x)
    vNew authclient (x, aup);
}

ptr<axprt_stream>
cloneaccept (int fd)
{
  if (fd < 0)
    fatal ("EOF from authserv\n");
  tcp_nodelay (fd);
  ref<axprt_crypt> x = axprt_crypt::alloc (fd);
  vNew authclient (x);
  return x;
}

static void
parseconfig (str cf)
{
  parseargs pa (cf);
  bool errors = false;
  bool srpglobalset = false;
  int line;
  vec<str> av;

  static rxx prefixrx ("^-prefix=([\\w/]+)$");
  static rxx mapallrx ("^-mapall=(\\w+|[\\d,]+)$"); // XXX
  static rxx pubrx ("^-pub=(.+)$");
  size_t ro_cnt = 0, reg_cnt = 0;

  while (pa.getline (&av, &line)) {
    if (!strcasecmp (av[0], "hostname")) {
      if (av.size () != 2) {
	errors = true;
	warn << cf << ":" << line << ": usage: hostname name\n";
      }
      else if (servinfo.host.hostname != "") {
	errors = true;
	warn << cf << ":" << line << ": hostname already specified\n";
      }
      else
	servinfo.host.hostname = av[1];
    }
    else if (!strcasecmp (av[0], "keyfile")) {
      if (authsrvkey) {
	  errors = true;
	  warn << cf << ":" << line << ": keyfile already specified\n";
      }
      else if (av.size () == 2) {
	str keyfile (av[1]);
	if (keyfile[0] != '/')
	  keyfile = strbuf ("%s/", etc1dir) << keyfile;
	str key = file2wstr (keyfile);
	if (!key) {
	  errors = true;
	  warn << keyfile << ": " << strerror (errno) << "\n";
	  warn << cf << ":" << line << ": could not read keyfile\n";
	}
	else if (!(authsrvkey = import_rabin_priv (key, NULL))) {
	  errors = true;
	  warn << cf << ":" << line << ": could not decode keyfile\n";
	}
      }
      else {
	errors = true;
	warn << cf << ":" << line << ": usage: keyfile path\n";
      }
    }
    else if (!strcasecmp (av[0], "userfile")) {
      bool r = false, reg = false;
      str pre, mapall, pp;
      if (av.size () >= 2) {
	for (size_t i = 1; i < av.size () - 1; i++ ) {
	  prefixrx.search (av[i]);
	  mapallrx.search (av[i]);
	  pubrx.search (av[i]);
	  if (!strcasecmp (av[i], "-ro")) {
	    r = true; ro_cnt++;
	  }
	  else if (!strcasecmp (av[i], "-reg")) {
	    reg = true; reg_cnt++;
	    accepts_reg     = true;
	    accepts_changes = true;
	  }
	  else if (prefixrx.success ())
	    pre = prefixrx[1];
	  else if (mapallrx.success ())
	    mapall = mapallrx[1];
	  else if (pubrx.success ())
	    pp  = pubrx[1];
	  else {
	    errors = true;
	    warn << cf << ":" << line << ": unknown userfile option: "
		 << av[i] << "\n";
	  }
	}
	if (r && reg) {
	  errors = true;
	  warn << cf << ":" << line << ": conflicting userfile option";
	}
      } else { /* not enough arguments */
	errors = true;
	warn << cf << ":" << line << ": usage: must specify file";
      }
      if (av[av.size () - 1][0] == '-') {
	errors = true;
	warn << cf << ":" << line << ": must specify non-option file";
      }
      if (!errors) {
	str path = av[av.size () - 1];
	if (path[0] != '/')
	  path = strbuf ("%s/", etc1dir) << path;
	userfiles.push_back(userfile (path, pp, r, pre, mapall, reg));
      }
    }
    else if (!strcasecmp (av[0], "srpfile")) {
      if (srpglobalset) {
	errors = true;
	warn << cf << ":" << line << ": srpfile already specified\n";
      }
      else if (av.size () == 2) {
	str key = file2wstr (av[1]);
	if (!key) {
	  errors = true;
	  warn << av[1] << ": " << strerror (errno) << "\n";
	  warn << cf << ":" << line << ": could not read srpfile\n";
	}
	else if (!import_srp_params (key, &srpglobal.N, &srpglobal.g)) {
	  errors = true;
	  warn << cf << ":" << line << ": could not decode srpfile\n";
	} else srpglobalset = true;
      }
      else {
	errors = true;
	warn << cf << ":" << line << ": usage: srpfile path\n";
      }
    }
    else if (!strcasecmp (av[0], "denyfile")) {
      if (denyfile) {
	  errors = true;
	  warn << cf << ":" << line << ": denyfile already specified\n";
      }
      else if (av.size () == 2) {
	if (!file2str (av[1])) {
	  errors = true;
	  warn << av[1] << ": " << strerror (errno) << "\n";
	  warn << cf << ":" << line << ": could not read denyfile\n";
	}
	else {
	  denyfile = av[1];
	  if (denyfile[0] != '/')
	    denyfile = strbuf ("%s/", etc1dir) << denyfile;
	}
      }
      else {
	errors = true;
	warn << cf << ":" << line << ": usage: denyfile path\n";
      }
    }
    else if (!strcasecmp (av[0], "xfertimeout")) {
      if (!convertint (av[1], &xfer_timeout) || (xfer_timeout < 0)) {
	errors = true;
	warn << cf << ":" << line << ": usage: XferTimeout seconds\n";
      }
    }
    else if (!strcasecmp (av[0], "cacheexpire")) {
      if (!convertint (av[1], &cache_expire)
	  || (cache_expire < 0)) {
	errors = true;
	warn << cf << ":" << line << ": usage: CacheExpire <time (secs)>\n";
      }      
    }
    else if (!strcasecmp (av[0], "cacherecheck")) {
      if (!convertint (av[1], &cache_recheck)
	  || (cache_recheck < 0)) {
	errors = true;
	warn << cf << ":" << line << ": usage: CacheRecheck <time (secs)>\n";
      }
    }
    else {
      errors = true;
      warn << cf << ":" << line << ": Unknown directive '"
	   << av[0] << "'\n";
    }
  }

  if (reg_cnt > 1) {
    errors = true;
    warn << "can only specify a single userfile for registrations\n";
  }

  if (!errors && !authsrvkey) {
    str keyfile = sfsconst_etcfile ("sfs_host_key");
    if (!keyfile) {
      errors = true;
      warn << "cannot locate default file sfs_host_key\n";
    }
    else {
      str key = file2wstr (keyfile);
      if (!key) {
	errors = true;
	warn << keyfile << ": " << strerror (errno) << "\n";
      }
      else if (!(authsrvkey = import_rabin_priv (key, NULL))) {
	errors = true;
	warn << "could not decode " << keyfile << "\n";
      }
    }
  }

  if (cache_recheck > cache_expire) {
    errors = true;
    warn << "cache recheck time must be <= cache expire time";
  }
    
  if (errors)
    fatal << "errors in configuration file\n";

  /* check to see if we have anything */
  if (ro_cnt == userfiles.size ())
    accepts_changes = false;
    
  /* check for default user file */
  if (userfiles.empty ()) {
    str uf = strbuf ("%s/%s", etc1dir, "sfs_users");
    str ufpub = strbuf ("%s/%s", etc1dir, "sfs_users.pub");
    userfiles.push_back (userfile (uf, ufpub, false, NULL, NULL, true));
    accepts_reg = true;
    accepts_changes = true;
  }

  /* check for default deny file */
  if (!denyfile)
    denyfile = sfsconst_etcfile ("sfs_deny");

  /* check for default srp file */
  if (!srpglobalset) {
    str keyfile = sfsconst_etcfile ("sfs_srp_parms");
    if (keyfile) {
      str key = file2wstr (keyfile);
      if (!key) {
	fatal << keyfile << ": " << strerror (errno) << "\n";
      }
      else if (!import_srp_params (key, &srpglobal.N, &srpglobal.g)) {
	errors = true;
	fatal << "could not decode " << keyfile << "\n";
      }
    }
    else
      warn << "srpfile unspecified and default not found..." 
	   << "not serving SRP parms.\n";
  }

  servinfo.release = SFS_RELEASE;
  servinfo.host.type = SFS_HOSTINFO;
  if (servinfo.host.hostname == "" && !(servinfo.host.hostname = myname ()))
    fatal << "could not figure out my host name\n";
  servinfo.host.pubkey = authsrvkey->n;
  if (!sfs_mkhostid (&hostid, servinfo.host))
    fatal << "could not marshal my own hostinfo\n";
  servinfo.prog = SFSAUTH_PROGRAM;
  servinfo.prog = SFSAUTH_VERSION;
}

static void
usage ()
{
  warnx << "usage: " << progname << " [-f configfile]\n";
  exit (1);
}

int
main (int argc, char **argv)
{
  enum { minsize = 768 };

  str configfile;

  setprogname (argv[0]);

  int ch;
  while ((ch = getopt (argc, argv, "f:")) != -1)
    switch (ch) {
    case 'f':
      configfile = optarg;
      break;
    case '?':
    default:
      usage ();
    }

  argc -= optind;
  argv += optind;
  if (argc > 0)
    usage ();

  warn ("version %s, pid %d\n", VERSION, getpid ());

  sfsconst_init ();

  sfs_suidserv ("authserv", wrap (unixaccept));

  sfsauthcachedir = sfsdir << "/authdb";
  if (!configfile)
    configfile = sfsconst_etcfile_required ("sfsauthd_config");
  parseconfig (configfile);
  if (!runinplace)
    mksfsdir (sfsauthcachedir, 0755, NULL, 0);
  else {
    struct stat sb;
    sb.st_uid = 0;
    stat (sfsdir, &sb);
    mksfsdir (sfsauthcachedir, 0755, NULL, sb.st_uid);
  }

  random_init_file (sfsdir << "/random_seed");

  warn << "serving " << sfs_hostinfo2path (servinfo.host) << "\n";
  authdbinit ();

  if (!cloneserv (0, wrap (cloneaccept)))
    warn ("No sfssd detected, only accepting unix-domain clients\n");

  amain ();
}
