/*
 * setup.c  -  Setup netboot module
 *
 * Copyright (C) 1998-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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 of the License, or
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: setup.c,v 1.25 2007/01/06 18:31:39 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>
#include "privlib.h"



/*
 * Load header file for /etc/passwd access
 */
#if defined(HAVE_PWD_H)
# include <pwd.h>
#endif



/*
 * Define path of temporary files
 */
#if defined(HAVE_PATHS_H)
# include <paths.h>
#endif
#ifdef _PATH_VARTMP
# define TMPDIR _PATH_VARTMP
#else
# ifdef _PATH_TMP
#  define TMPDIR _PATH_TMP
# else
#  ifdef P_tmpdir
#   define TMPDIR P_tmpdir
#  else
#   define TMPDIR "/tmp"
#  endif
# endif
#endif



/*
 * Load support for locales
 */
#if defined(HAVE_LOCALE_H)
# include <locale.h>
# if defined(HAVE_LANGINFO_H) && defined(HAVE_NL_LANGINFO)
#  include <langinfo.h>
# endif
#endif



/*
 * Default environment variable names
 */
#ifndef ENV_CONFIG
# define ENV_CONFIG		"NETBOOT_CONFIG"
#endif

#ifndef ENV_PWD
# define ENV_PWD		"PWD"
#endif

#ifndef ENV_HOME
# define ENV_HOME		"HOME"
#endif

#ifndef ENV_LOCALE
# define ENV_LOCALE		"NETBOOT_LOCALE"
#endif



/*
 * Default file and path names
 */
#ifndef DEFAULT_CONFIG
# define DEFAULT_CONFIG		"//netboot.config"
#endif
#ifndef DEFAULT_DATABASE
# define DEFAULT_DATABASE	"//netboot.db"
#endif
#ifndef DEFAULT_DIR
# define DEFAULT_DIR		nbhomedir
#endif
#ifndef DEFAULT_TEMPDIR
# define DEFAULT_TEMPDIR	TMPDIR
#endif



/*
 * Variables private to this module
 */
static char *conflibdir   = NULL;	/* libdir name from config */
static char *confdatadir  = NULL;	/* datadir name from config */
static char *confhomedir  = NULL;	/* homedir name from config */
static char *conftempdir  = NULL;	/* temporary file dir from config */
static char *confdbname   = NULL;	/* database file name from config */



/*
 * Global variables exported by this module
 */
char *progname = NULL;			/* name of program */
char *configname = NULL;		/* name of configuration file */
char *nblogname = NULL;			/* name of logging file */
char *nblibdir = NULL;			/* netboot libdir */
char *nbdatadir = NULL;			/* netboot datadir */
char *nbhomedir = NULL;			/* netboot home directory */
char *nbtempdir = NULL;			/* temporary file directory */
char *sysdbname = NULL;			/* system database name */
char *userhomedir = NULL;		/* user home directory */
char *curworkdir = NULL;		/* current working directory */
char *nblocale = NULL;			/* current locale name */
char *nbcharname = NULL;		/* current character set name */
int nbcharset = CHARSET_UNKNOWN;	/* current character set definition */



/*
 * Definition of character sets supported by netboot. Alias names have been
 * taken from the GNU recode program.
 */
static struct {
	char *name;
	int   charset;
} charset_list[] = {
	{ "ascii",		CHARSET_ASCII },
	{ "ebcdic",		CHARSET_EBCDIC },
	{ "utf8",		CHARSET_UTF8 },
	{ "latin1",		CHARSET_LATIN1 },
	{ "latin9",		CHARSET_LATIN9 },
	{ "ANSI_X3.4-1968",	CHARSET_ASCII },
	{ "US-ASCII",		CHARSET_ASCII },
	{ "ISO646-US",		CHARSET_ASCII },
	{ "646",		CHARSET_ASCII },
	{ "EBCDIC",		CHARSET_EBCDIC },
	{ "EBCDIC-US",		CHARSET_EBCDIC },
	{ "ISO-8859-1",		CHARSET_LATIN1 },
	{ "ISO8859-1",		CHARSET_LATIN1 },
	{ "ISO-8859-15",	CHARSET_LATIN9 },
	{ "ISO8859-15",		CHARSET_LATIN9 },
	{ "UTF-8",		CHARSET_UTF8 },
	{ "IBM037",		CHARSET_EBCDIC },
	{ "CP037",		CHARSET_EBCDIC },
	{ "IBM819",		CHARSET_LATIN1 },
	{ "CP819",		CHARSET_LATIN1 },
	{ NULL,			CHARSET_UNKNOWN }
};



/*
 * Definition of general section in configuration file
 */
static struct paramdef general_params[] = {
	{ "libdir",	par_string,	NULL,	{&conflibdir}},
	{ "datadir",	par_string,	NULL,	{&confdatadir}},
	{ "homedir",	par_string,	NULL,	{&confhomedir}},
	{ "tempdir",	par_string,	NULL,	{&conftempdir}},
	{ "sysdb",	par_string,	NULL,	{&confdbname}},
	{ NULL,		par_null,	NULL,	{NULL}}
};

static struct sectdef general_sects[] = {
	{ "general",	general_params,	NULL,	NULL },
	{ NULL,		NULL,		NULL,	NULL }
};



/*
 * Assign a string, optionally setting a default value.
 */
static inline void assignstr __F((dest, src, defstr),
				char **dest AND
				char **src AND
				const char *defstr)
{
  /* Assign source or default string to destination pointer */
  if (*dest == NULL) {
	if (*src != NULL) {
		*dest = *src;
		*src = NULL;
	} else if (defstr != NULL)
		copystr(dest, defstr);
  }

  /* Release the source string if necessary */
  if (*src != NULL) {
	free(*src);
	*src = NULL;
  }
}



/*
 * Make a file or directory name absolute
 */
static void abspath __F((pathname, defaultname, curdir, libdir),
				char **pathname AND
				char *defaultname AND
				const char *curdir AND
				const char *libdir)
{
  char *cp = *pathname;

  /* Check if we have a path name */
  if (cp != NULL && *cp == '\0')
	cp = NULL;

  /* Normalize a relative path */
  if (cp != NULL) {
	char *newname;
	size_t len;

	if (*cp != '/') {
		len = strlen(cp) + strlen(curdir) + 2;
		newname = (char *)nbmalloc(len);
		sprintf(newname, "%s/%s", curdir, cp);
		cp = newname;
	} else if (!strncmp(cp, "//", 2)) {
		if (libdir == NULL) {
			struct cmdopt *op;
			char c;

			/* Find pathname option in common option table */
			op = nblib_find_opt(*pathname);
			assert(op != NULL);

			/*
			 * This routine gets called after reading all command
			 * line parameters. Actually, this error is a usage
			 * error, and for those (like every error resulting
			 * in an EXIT_USAGE nbexit code) we don't honor the quiet
			 * flag, and we don't write into the log file.
			 */
			c = (op->shortopt & 0x00FF);
			fprintf(stderr, "%s: \'//\' not allowed with ",
								progname);
			if (op->longopt != NULL)
				fprintf(stderr, "--%s %s", op->longopt,
						(c != '\0' ?  "or " : ""));
			if (c != '\0')
				fprintf(stderr, "-%c", c);
			fprintf(stderr, "option\n");
			nbexit(EXIT_USAGE);
		}
		if ((len = strlen(cp)) == 2) {
			newname = NULL;
			copystr(&newname, libdir);
		} else {
			len += strlen(libdir) + 1;
			newname = (char *)nbmalloc(len);
			sprintf(newname, "%s%s", libdir, cp + 1);
		}
		cp = newname;
	}
  }

  /* Set return value */
  if (cp != *pathname) {
	free(*pathname);
	*pathname = cp;
  }
  if (*pathname == NULL) {
	/*
	 * Do not use copystr() here. Setting a default name is just for
	 * temporary use. We detect, if the default name has been set by
	 * comparing the pointer against the address of the default name.
	 */
	*pathname = defaultname;
  }
}



/*
 * Clean directory path: remove any trailing slash
 */
static void cleandir __F((pathname), char *pathname)
{
  size_t len;

  if (pathname != NULL) {
	len = strlen(pathname);
	if (len > 1 && pathname[len - 1] == '/' &&
	    (len != 2 || pathname[0] != '/'))
		pathname[len - 1] = '\0';
  }
}



/*
 * Get the current working directory
 */
static char *getcurdir __F_NOARGS
{
  char *buf = NULL;

  /*
   * First try to read the PWD environment variable
   */
#ifdef HAVE_GETENV
  if (buf == NULL) {
	struct stat dotstat, pwdstat;
	char *cp;

	if ((cp = getenv(ENV_PWD)) != NULL && *cp == '/' &&
	    stat(cp, &pwdstat) == 0 &&
	    stat(".", &dotstat) == 0 &&
	    dotstat.st_ino == pwdstat.st_ino &&
	    dotstat.st_dev == pwdstat.st_dev)
		copystr(&buf, cp);
  }
#endif

  /*
   * Try the use of getcwd(). For this we need to allocate the memory
   * buffer ourselves.
   */
#ifdef HAVE_GETCWD
  if (buf == NULL) {
	size_t s = MAXPATHLEN;
	char *cp;

	while (TRUE) {
		cp = (char *)nbmalloc(s);
		if (getcwd(cp, s) != NULL)
			break;
		free(cp);
#ifdef ERANGE
		if (errno != ERANGE) {
			cp = NULL;
			break;
		}
		s *= 2;
#else
		cp = NULL;
		break;
#endif
	}
	if (cp != NULL && (strlen(cp) + 1) < s)
		buf = (char *)nbrealloc(cp, (strlen(cp) + 1));
	else
		buf = cp;
  }
#endif

  /*
   * Try using getwd(). We also have to allocate the memory ourselves
   * in this case.
   */
#if defined(HAVE_GETWD) && !defined(HAVE_GETCWD) && defined(PATH_MAX)
  if (buf == NULL) {
	har *cp;

	cp = (char *)nbmalloc(PATH_MAX);
	if (getcwd(cp) == NULL) {
		free(cp);
		cp = NULL;
	}
	if (cp != NULL && (strlen(cp) + 1) < PATH_MAX)
		buf = (char *)nbrealloc(cp, (strlen(cp) + 1));
	else
		buf = cp;
  }
#endif

  /*
   * The name of the current working directory has to be absolute. We have
   * to print this error directly onto the console because the logging sub-
   * system has not been initialized yet.
   */
  if (buf == NULL || *buf == '\0' || *buf != '/') {
	prnerr("unable to determine name of working directory");
	nbexit(EXIT_INTERNAL);
  }
  return(buf);
}



/*
 * Get the name of the user's home directory
 */
static char *gethomedir __F_NOARGS
{
  char *ret = NULL;

#ifdef HAVE_PWD_H
  /* Check for home directory in /etc/passwd */
  if (ret == NULL) {
	struct passwd *pd;

	pd = getpwuid(getuid());
	if (pd != NULL && pd->pw_dir != NULL)
		copystr(&ret, pd->pw_dir);
  }
#endif

#ifdef HAVE_GETENV
  /* Check for home directory in environment */
  if (ret == NULL) {
	char *cp;

	if ((cp = getenv(ENV_HOME)) != NULL)
		copystr(&ret, cp);
  }
#endif

  /* If all fails, assume home directory as current directory */
  if (ret == NULL)
	copystr(&ret, curworkdir);

  return(ret);
}



/*
 * Initialize program name
 */
static void init_progname __F((argc, argv), int argc AND char **argv)
{
  char *cp;

  if (argc > 0 && argv[0] != NULL) {
	if ((cp = strrchr(argv[0], '/')) == NULL)
		cp = argv[0];
	else
		++cp;
	if (!strncmp(cp, "lt-", 3))
		cp += 3;
	copystr(&progname, cp);
  } else {
	/* This should never happen */
	copystr(&progname, "netboot");
  }
}



/*
 * Initialize locale data
 */
static void init_locale __F_NOARGS
{
  char *locale_name = NULL;
  char *charset_name = NULL;
  char *env = NULL;
  char *cp;
  int idx;

#ifdef HAVE_GETENV

  /* Check if user wants us to override the system locale */
  env = getenv(ENV_LOCALE);

#endif

#ifndef HAVE_SETLOCALE

  /* If we don't have any locale support, we will always use the C locale */
  copystr(&locale_name, (env == NULL ? "C" : env));

#else

  /* Set the required locale, or C if locale is not initialized */
  cp = setlocale(LC_ALL, (env == NULL ? "" : env));
  if (cp == NULL) {
	if (env == NULL)
		cp = setlocale(LC_ALL, "C");
	if (cp == NULL) {
		prnerr("unable to set current locale");
		nbexit(EXIT_LOCALE);
	}
  }
  copystr(&locale_name, cp);

  /* Some implementations return a locale name for every component of LC_ALL */
  if ((cp = strchr(locale_name, ' ')) != NULL) {
	char *ctype = cp;

	*ctype++ = '\0';
	while (*ctype && *ctype == ' ')
		ctype++;
	if (*ctype) {
		if ((cp = strchr(ctype, ' ')) != NULL)
			*cp = '\0';
		cp = NULL;
		copystr(&cp, ctype);
		free(locale_name);
		locale_name = cp;
	}
  }

#endif

  /* Extract character set name from locale name */
  assert(locale_name != NULL);
  if ((cp = strchr(locale_name, '@')) != NULL)
	*cp = '\0';
  else if ((cp = strchr(locale_name, '+')) != NULL)
	*cp = '\0';
  else if ((cp = strchr(locale_name, ',')) != NULL)
	*cp = '\0';
  if ((cp = strchr(locale_name, '.')) != NULL) {
	*cp++ = '\0';
	copystr(&charset_name, cp);
  }

  /* Determine the character set index value */
  idx = -1;
  if (charset_name != NULL) {
	for (idx = 0; charset_list[idx].name != NULL; idx++)
		if (!strcmp(charset_name, charset_list[idx].name))
			break;
#if defined(HAVE_NL_LANGINFO) && defined(CODESET)
	if (charset_list[idx].charset == CHARSET_UNKNOWN) {
		/*
		 * If a character set has been specified, it can be
		 * an alias for something else, so we use nl_langinfo()
		 * here to determine the aliased name.
		 */
		cp = nl_langinfo(CODESET);
		copystr(&charset_name, cp);
		for (idx = 0; charset_list[idx].name != NULL; idx++)
			if (!strcmp(cp, charset_list[idx].name))
				break;
	}
#endif
  } else {
#if defined(HAVE_NL_LANGINFO) && defined(CODESET)
	cp = nl_langinfo(CODESET);
#else
# if EBCDIC
	cp = "ebcdic";
# else
	cp = "ascii";
# endif
#endif
	copystr(&charset_name, cp);
	for (idx = 0; charset_list[idx].name != NULL; idx++)
		if (!strcmp(cp, charset_list[idx].name))
			break;
  }

  /* Setup global values */
  assert(charset_name != NULL);
  nbcharset = (idx < 0 ? CHARSET_UNKNOWN : charset_list[idx].charset);
  nbcharname = charset_name;
  nblocale = locale_name;
}



/*
 * Setup netboot module program
 */
void nbsetup __F((argc, argv, opts, sects),
				int argc AND
				char **argv AND
				struct cmdopt *opts AND
				struct sectdef *sects)
{
  struct sectdef *sectbuf = NULL;
  int sectnum, gensectnum, dbsectnum;
  char *homedir = NULL;

  /* Initialize program name */
  init_progname(argc, argv);

  /* Initialize locale */
  init_locale();

  /* Initialize signal handling */
  nblib_init_signal();

  /* Determine the name of the current working directory */
  curworkdir = getcurdir();
  cleandir(curworkdir);

  /* Determine name of user's home directory */
  userhomedir = gethomedir();
  cleandir(userhomedir);
  if (userhomedir == NULL || *userhomedir != '/') {
	prnerr("invalid user home directory");
	nbexit(EXIT_INTERNAL);
  }

  /* Process command line options */
  nblib_parse_opt(argc, argv, opts);

  /* Cleanup directory names from command line */
  cleandir(nbhomedir);
  cleandir(nblibdir);
  cleandir(nbdatadir);
  cleandir(nbtempdir);

  /*
   * If name of config file not given on command line, check if it is given
   * as an environment variable. Otherwise set a default.
   */
  if (configname == NULL) {
#ifdef HAVE_GETENV
	char *cp;

	if ((cp = getenv(ENV_CONFIG)) != NULL)
		copystr(&configname, cp);
	else
#endif
		copystr(&configname, DEFAULT_CONFIG);
  }

  /*
   * Make some command line paths and file names absolute. Setting any
   * defaults here is just for use by the configuration file reading
   * routine (which requires some paths set to absolute names). Those
   * default values get reset to NULL after reading the configuration
   * file, and before the configuration file values get examined.
   */
  abspath(&nbhomedir, userhomedir, curworkdir, NULL);
  abspath(&nblibdir, curworkdir, nbhomedir, NULL);
  abspath(&nbdatadir, NULL, nbhomedir, NULL);
  abspath(&nbtempdir, NULL, nbhomedir, nblibdir);
  abspath(&configname, NULL, nbhomedir, nblibdir);
  abspath(&nblogname, NULL, curworkdir, NULL);

  /* Initialize logging */
  nblib_init_log();
  if (nblogname != NULL && verbose < LOGLEVEL_NOTICE)
	verbose = LOGLEVEL_NOTICE;

  /* Setup temporary buffer for sections */
  sectnum = 0;
  if (sects != NULL) {
	while (sects[sectnum].name != NULL)
		sectnum++;
  }
  dbsectnum = 0;
  while (dbsects[dbsectnum].name != NULL)
	dbsectnum++;
  gensectnum = sizeof(general_sects) / sizeof(struct sectdef) - 1;
  sectbuf = (struct sectdef *)nbmalloc(sizeof(struct sectdef) *
				(sectnum + dbsectnum + gensectnum + 1));
  memcpy(&(sectbuf[0]), general_sects, sizeof(struct sectdef) * gensectnum);
  memcpy(&(sectbuf[gensectnum]), dbsects, sizeof(struct sectdef) * dbsectnum);
  if (sects != NULL)
	memcpy(&(sectbuf[gensectnum + dbsectnum]),
				sects, sizeof(struct sectdef) * sectnum);

  /* Read config file */
  if (!nblib_readconfig(sectbuf, configname)) {
	free(sectbuf);
	nbexit(-1);
  }
  free(sectbuf);

  /*
   * Restore the command line parameters which have not been set. These
   * have to be restored in the reverse order of the abspath() calls
   * above.
   */
  if (nblibdir == curworkdir)
	nblibdir = NULL;
  if (nbhomedir == userhomedir)
	nbhomedir = NULL;

  /* Cleanup directory names from configuration file */
  cleandir(confhomedir);
  cleandir(conflibdir);
  cleandir(confdatadir);
  cleandir(conftempdir);

  /*
   * Setup global variables if they have not been set on the command line. In
   * case nothing has been set at all, select some default value.
   */
  assignstr(&nbhomedir, &confhomedir, userhomedir);
  assignstr(&nblibdir, &conflibdir, DEFAULT_DIR);
  assignstr(&nbdatadir, &confdatadir, NULL);
  assignstr(&nbtempdir, &conftempdir, DEFAULT_TEMPDIR);
  assignstr(&sysdbname, &confdbname, DEFAULT_DATABASE);

  /* Check that the home directory is readable and writable */
  if (!strncmp(nbhomedir, "//", 2)) {
	prnerr("\'//\' not allowed in name of home directory");
	nbexit(EXIT_CONFIG);
  }
  copystr(&homedir, nbhomedir);
  checkaccess(&nbhomedir, curworkdir, ACCESS_DIR_RW);
#ifdef HAVE_MKDIR
  if (nbhomedir == NULL) {
	char *cp;

	setpath(&homedir, curworkdir);
	if (homedir != NULL) {
		if ((cp = strchr(homedir, ':')) != NULL)
			*cp = '\0';
		if (mkdir(homedir, 0755) != 0) {
			prnerr("unable to create directory %s", homedir);
			nbexit(EXIT_CONFIG);
		}
		if (nbhomedir != NULL)
			free(nbhomedir);
		nbhomedir = homedir;
	}
  }
#endif
  if (nbhomedir == NULL) {
	prnerr("unable to access home directory");
	nbexit(EXIT_CONFIG);
  }

  /* Check that the non-shared data directory exists and is at least readable */
  if (!strncmp(nblibdir, "//", 2)) {
	prnerr("\'//\' not allowed in name of \'libdir\' directory");
	nbexit(EXIT_CONFIG);
  }
  checkaccess(&nblibdir, nbhomedir, ACCESS_DIR_READ);
  if (nblibdir == NULL) {
	prnerr("unable to access \'libdir\' directory");
	nbexit(EXIT_CONFIG);
  }

  /* Check that the shared data directory exists - but it has not to exist */
  if (nbdatadir != NULL) {
	if (!strncmp(nbdatadir, "//", 2)) {
		prnerr("\'//\' not allowed in name of \'datadir\' directory");
		nbexit(EXIT_CONFIG);
	}
	checkaccess(&nbdatadir, nbhomedir, ACCESS_DIR_EXIST);
  }

  /* Check that the temp directory is readable and writable */
  checkaccess(&nbtempdir, nbhomedir, ACCESS_DIR_RW);
  if (nbtempdir == NULL) {
	prnerr("unable to access temporary directory");
	nbexit(EXIT_CONFIG);
  }

  /* Check if the current character set has been set */
  if (nbcharset == CHARSET_UNKNOWN)
	prnlog(LOGLEVEL_NORMAL, "Warning: unknown character set");
}

