/* $Id: sfsconst.C,v 1.45 2002/11/28 20:38:32 dm Exp $ */

/*
 *
 * Copyright (C) 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 "sfsmisc.h"
#include "amisc.h"
#include "parseopt.h"
#include <pwd.h>
#include <grp.h>

#ifndef SFSUSER
# define SFSUSER "sfs"
#endif /* !SFSUSER */

u_int32_t sfs_release = SFS_RELEASE;
u_int16_t sfs_port = SFS_PORT;
uid_t sfs_uid;
gid_t sfs_gid;
uid_t nobody_uid = (uid_t) -1;
gid_t nobody_gid = (gid_t) -1;
gid_t sfs_resvgid_start;	// First reserved gid
u_int sfs_resvgid_count;	// Number of reserved gid's
#ifdef MAINTAINER
bool runinplace;
#endif /* MAINTAINER */
str sfsdir = SFSDIR;
str sfssockdir = SFSDIR "/sockets";
str sfsdevdb;
const char *etc1dir = ETCDIR;
const char *etc2dir = DATADIR;
const char *etc3dir = NULL;
const char *sfsroot = "/sfs";
const char *sfs_logfile = "/var/sfs/sign_log";
const char *sfs_authd_syslog_priority = "local4.info";
u_int sfs_dlogsize = 1024;
u_int sfs_mindlogsize = 768;
u_int sfs_maxdlogsize = 9000;
u_int sfs_rsasize = 1280;
u_int sfs_minrsasize = 768;
u_int sfs_maxrsasize = 9000;
u_int sfs_pwdcost = 11;
u_int sfs_hashcost = 0;
u_int sfs_maxhashcost = 22;

static bool const_set;

#ifdef MAINTAINER
# define fwarn warn
#else /* !MAINTAINER */
# define fwarn fatal
#endif /* !MAINTAINER */

static void
idlookup (str uid, str gid)
{
  if (!uid)
    uid = "sfs";
  if (!gid)
    gid = uid;

  bool uidok = convertint (uid, &sfs_uid);
  struct passwd *pw = uidok ? getpwuid (sfs_uid) : getpwnam (uid.cstr ());
  bool gidok = convertint (gid, &sfs_gid);
  struct group *gr = gidok ? getgrgid (sfs_gid) : getgrnam (gid.cstr ());

  if (!uidok) {
    if (!pw)
      fatal << "Could not find user " << uid << "\n";
    sfs_uid = pw->pw_uid;
  }
  if (!gidok) {
    if (!gr)
      fatal << "Could not find group " << gid << "\n";
    sfs_gid = gr->gr_gid;
  }
  if (gr && gr->gr_mem[0])
    fwarn << "Group " << gid << " must not have any members\n";
  if (pw && gr && (gid_t) pw->pw_gid != (gid_t) gr->gr_gid)
    fwarn << "User " << uid << " must have login group " << gid << ".\n";

  endpwent ();
  endgrent ();
}

static void
resvgidset (str lowstr, str highstr)
{
  if (!lowstr || !highstr)
    return;

  gid_t end;

  if (!convertint (lowstr, &sfs_resvgid_start))
    fatal << "Could not interpret resvgid value " 
	  << lowstr << " as a number.\n";
  if (!convertint (highstr, &end))
    fatal << "Could not interpret resvgid value " 
	  << highstr << " as a number.\n";

  if (sfs_resvgid_start > end)
    fatal << "Starting value of resvgid range is greater than end value.\n";

  sfs_resvgid_count = end - sfs_resvgid_start + 1;
}

static bool
parseconfig (const char *dir, const char *file)
{
  str cf;
  if (!file)
    return false;
  if (file[0] == '/')
    cf = file;
  else if (!dir)
    return false;
  else
    cf = strbuf ("%s/%s", dir, file);

  if (access (cf, F_OK) < 0) {
    if (errno != ENOENT)
      warn << cf << ": " << strerror (errno) << "\n";
    return false;
  }

  str uid, gid, nuid, ngid, resvgidlow, resvgidhigh;
  bool dirset = false;

  bool errors = false;
  parseargs pa (cf);
  int line;
  vec<str> av;
  while (pa.getline (&av, &line)) {
    if (!strcasecmp (av[0], "sfsuser")) {
      if (uid) {
	errors = true;
	warn << cf << ":" << line << ": Duplicate sfsuser directive\n";
      }
      else if (av.size () == 2)
	uid = gid = av[1];
      else if (av.size () == 3) {
	uid = av[1];
	gid = av[2];
      }
      else {
	errors = true;
	warn << cf << ":" << line << ": usage: sfsuser user [group]\n";
      }
    }
    else if (!strcasecmp (av[0], "anonuser")) {
      if (nuid) {
	errors = true;
	warn << cf << ":" << line << ": Duplicate anonuser directive\n";
      }
      else if (av.size () == 2)
	nuid = av[1];
      else if (av.size () == 3) {
	nuid = av[1];
	ngid = av[2];
      }
      else {
	errors = true;
	warn << cf << ":" << line << ": usage: anonuser user [group]\n";
	continue;
      }

      gid_t g;
      if (ngid) {
	if (!convertint (ngid, &g)) {
	  if (struct group *gr = getgrnam (ngid))
	    g = gr->gr_gid;
	  else {
	    errors = true;
	    warn << cf << ":" << line << ": no group " << ngid << "\n";
	    ngid = NULL;
	  }
	}
      }

      uid_t u;
      if (!convertint (nuid, &u)) {
	struct passwd *pw = getpwnam (nuid);
	if (!pw) {
	  errors = true;
	  warn << cf << ":" << line << ": no user " << nuid << "\n";
	  continue;
	}
	nobody_uid = pw->pw_uid;
	if (ngid)
	  nobody_gid = g;
	else
	  nobody_gid = pw->pw_gid;
      }
      else if (ngid) {
	nobody_uid = u;
	nobody_gid = g;
      }
      else {
	errors = true;
	warn << cf << ":" << line << ": Must specify gid with numeric uid";
	continue;
      }
    }
    else if (!strcasecmp (av[0], "sfsdir")) {
      if (dirset) {
	errors = true;
	warn << cf << ":" << line << ": Duplicate sfsdir directive\n";
      }
      else if (av.size () != 2) {
	errors = true;
	warn << cf << ":" << line << ": usage: sfsdir directory\n";
      }
      else if (!runinplace) {
	sfsdir = av[1];
	sfssockdir = sfsdir << "/sockets";
      }
      dirset = true;
    }
    else if (!strcasecmp (av[0], "resvgids")) {
      if (resvgidhigh) {
	errors = true;
	warn << cf << ":" << line << ": Duplicate resvgids directive\n";
      }
      else if (av.size () != 3) {
	errors = true;
	warn << cf << ":" << line << ": usage: resvgids lower upper\n";
      }
      else {
	resvgidlow = av[1];
	resvgidhigh = av[2];
      }
    }
    else if (!strcasecmp (av[0], "rsasize")) {
      if (av.size () != 2 || !convertint (av[1], &sfs_rsasize)) {
	errors = true;
	warn << cf << ":" << line << ": usage: rsasize nbits\n";
      }
    } 
    else if (!strcasecmp (av[0], "dlogsize")) {
      if (av.size () != 2 || !convertint (av[1], &sfs_dlogsize)) {
	errors = true;
	warn << cf << ":" << line << ": usage: dlogsize nbits\n";
      }
    } 
    else if (!strcasecmp (av[0], "pwdcost")) {
      if (av.size () != 2 || !convertint (av[1], &sfs_pwdcost)) {
	errors = true;
	warn << cf << ":" << line << ": usage: pwdcost cost-param\n";
      }
      else if (sfs_pwdcost > 32) {
	errors = true;
	warn << cf << ":" << line << ": pwdcost must be well under 32\n";
      }
    }
    else if (!strcasecmp (av[0], "logpriority")) {
      if (av.size () != 2) {
	errors = true;
	warn << cf << ":" << line << ": usage: LogPriority facility.level\n";
      }
      else
	syslog_priority = av[1];
    }
    else
      warn << cf << ":" << line << ": Unknown directive '"
	   << av[0] << "'\n";
  }    

  if (errors)
    warn << "errors in " << cf << "\n";

  if (uid)
    idlookup (uid, gid);
  resvgidset (resvgidlow, resvgidhigh);
  return true;
}

/* If the directory specified by path does not exist, create it with
 * the given mode. If we fail for any reason, terminate with error.  */
void
mksfsdir (str path, mode_t mode, struct stat *sbp, uid_t uid)
{
  assert (path[0] == '/');

  mode_t m = umask (0);
  struct stat sb;
  if (stat (path, &sb) < 0) {
    if (errno != ENOENT || (mkdir (path, mode) < 0 && errno != EEXIST))
      fatal ("%s: %m\n", path.cstr ());
    if (chown (path, uid, sfs_gid) < 0) {
      int saved_errno = errno;
      rmdir (path);
      fatal ("chown (%s): %s\n", path.cstr (), strerror (saved_errno));
    }
    if (stat (path, &sb) < 0)
      fatal ("stat (%s): %m\n", path.cstr ());
  }
  umask (m);

  if (!S_ISDIR (sb.st_mode))
    fatal ("%s: not a directory\n", path.cstr ());
  if (sb.st_uid != uid)
    fwarn << path << ": owned by uid " << sb.st_uid
	  << ", should be uid " << uid << "\n";
  if (sb.st_gid != sfs_gid)
    fwarn << path << ": has gid " << sb.st_gid
	  << ", should be gid " << sfs_gid << "\n";
  if (sb.st_mode & 07777 & ~mode)
    fwarn ("%s: mode 0%o, should be 0%o\n",
	   path.cstr (), sb.st_mode & 07777, mode);

  if (sbp)
    *sbp = sb;
}

void
sfsconst_init ()
{
  if (const_set)
    return;
  const_set = true;

  {
    char *p = safegetenv ("SFS_RELEASE");
    if (!p || !convertint (p, &sfs_release)) {
      str rel (strbuf () << "SFS_RELEASE=" << sfs_release);
      xputenv (rel);
    }
  }

#ifdef MAINTAINER
  if (char *p = safegetenv ("SFS_RUNINPLACE")) {
    runinplace = true;
    builddir = p;
    buildtmpdir = builddir << "/runinplace";
  }
  if (char *p = safegetenv ("SFS_ROOT"))
    if (*p == '/')
      sfsroot = p;
#endif /* MAINTAINER */
  sfsdevdb = strbuf ("%s/.devdb", sfsroot);

#ifdef MAINTAINER
  if (runinplace) {
    sfsdir = buildtmpdir;
    sfssockdir = sfsdir;
    etc3dir = etc1dir;
    etc1dir = sfsdir;
    etc2dir = xstrdup (str (builddir << "/etc"));
  }
  if (char *ps = safegetenv ("SFS_PORT"))
    if (int pv = atoi (ps))
      sfs_port = pv;
#endif /* MAINTAINER */

  str sfs_config = safegetenv ("SFS_CONFIG");
  if (sfs_config && sfs_config[0] == '/') {
    if (!parseconfig (NULL, sfs_config))
      fatal << sfs_config << ": " << strerror (errno) << "\n";
  }
  else {
    if (!parseconfig (etc3dir, sfs_config)) {
      parseconfig (etc3dir, "sfs_config");
      if (!parseconfig (etc2dir, sfs_config)) {
	parseconfig (etc2dir, "sfs_config");
	if (!parseconfig (etc1dir, sfs_config)) 
	  parseconfig (etc1dir, "sfs_config");
      }
    }
  }

  if (!sfs_uid)
    idlookup (NULL, NULL);

  if (char *p = getenv ("SFS_HASHCOST")) {
    sfs_hashcost = strtoi64 (p);
    if (sfs_hashcost > sfs_maxhashcost)
      sfs_hashcost = sfs_maxhashcost;
  }

  if (!getuid () && !runinplace) {
    mksfsdir (sfsdir, 0755);
    mksfsdir (sfssockdir, 0750);
  }
  else if (runinplace && access (sfsdir, 0) < 0) {
    struct stat sb;
    if (!stat (builddir, &sb)) {
      mode_t m = umask (0);
      if (!getuid ()) {
	if (pid_t pid = fork ())
	  waitpid (pid, NULL, 0);
	else {
	  umask (0);
	  setgid (sfs_gid);
	  setuid (sb.st_uid);
	  if (mkdir (sfsdir, 02770) >= 0)
	    chown (sfsdir, (uid_t) -1, sfs_gid);
	  _exit (0);
	}
      }
      else
	mkdir (sfsdir, 0777);
      umask (m);
    }
  }
}

str
sfsconst_etcfile (const char *name)
{
  str file;
  if (name[0] == '/') {
    file = name;
    if (!access (file, F_OK))
      return file;
    return NULL;
  }
  file = strbuf ("%s/%s", etc1dir, name);
  if (!access (file, F_OK))
    return file;
  if (errno != ENOENT)
    fatal << file << ": " << strerror (errno) << "\n";
  file = strbuf ("%s/%s", etc2dir, name);
  if (!access (file, F_OK))
    return file;
  if (errno != ENOENT)
    fatal << file << ": " << strerror (errno) << "\n";
  if (etc3dir) {
    file = strbuf ("%s/%s", etc3dir, name);
    if (!access (file, F_OK))
      return file;
    if (errno != ENOENT)
      fatal << file << ": " << strerror (errno) << "\n";
  }
  return NULL;
}

str
sfsconst_etcfile_required (const char *name)
{
  str file = sfsconst_etcfile (name);
  str e3 = etc3dir ? str (strbuf ("  %s/%s\n", etc3dir, name)) : str ("");
  if (!file)
    fatal ("Could not find '%s'.  Searched:\n  %s/%s\n  %s/%s\n",
	   name, etc1dir, name, etc2dir, name) << e3;
  return file;
}

str
sfshostname ()
{
  str name = safegetenv ("SFS_HOSTNAME");
  if (!name)
    name = myname ();
  if (!name)
    fatal ("could not figure out host's fully-qualified domain name\n");
  mstr m (name.len ());
  for (u_int i = 0; i < name.len (); i++)
    m[i] = tolower (name[i]);
  return m;
}
