/*
    nss_cfgfile.c - Configuration file handling
    Copyright (C) 2000  Petr Vandrovec
    Copyright (C) 2003  Patrick Pollet

    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
    (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

    Revision history:

	1.00  2003, Jan 16		Patrick Pollet  <patrick.pollet@insa-lyon.fr>
		Initial revision, stronly inspired by ncpfs/lib/cfgfile.c


 */

#include "config.h"


#include "private/libncp-lock.h"


#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/syslog.h>

#include "nss_cfgfile.h"
#include "nss_ncp.h"
// temporary define (waiting for a better Makefile)
#define GLOBALCFGFILE "/etc/ncpfs.conf"
#ifndef GLOBALCFGFILE
#error "GLOBALCFGFILE must be defined"
#endif


// #define DEBUG 1

static struct nss_ncp_conf*
alloc_nss_ncp_conf(void)
{
	struct nss_ncp_conf * conf;

	conf = malloc(sizeof(*conf));
	if (!conf)
		return NULL;
	return conf;
}


void
free_nss_ncp_conf(struct nss_ncp_conf *conf)
{
#define FREEFIELD(x) do if (conf->x) { free(conf->x); conf->x = NULL; } while (0);
	if (conf) {
		FREEFIELD(server);
		FREEFIELD(startCtx);
		FREEFIELD(ctrlGroup);
		FREEFIELD(defShell);
	}
#undef FREEFIELD
}


struct cfgFile {
		ncpt_mutex_t	mutex;
		FILE*		file;
};

static struct cfgFile*
cfgOpenFile(const char* path,
	    int writeRequired)
{
	struct cfgFile* cfg;

	cfg = malloc(sizeof(*cfg));
	if (cfg) {
		cfg->file = fopen(path, writeRequired ? "r+" : "r");
		if (cfg->file) {
			ncpt_mutex_init(&cfg->mutex);
		} else {
			free(cfg);
			cfg = NULL;
		}
	}
	return cfg;
}

static void
cfgClose(struct cfgFile* cfg)
{
	ncpt_mutex_lock(&cfg->mutex);
	fclose(cfg->file);
	ncpt_mutex_destroy(&cfg->mutex);
	free(cfg);
}


struct check {
	const char *option;	/* configuration option */
	int         mandatory;	/* can be empty or null */
	int         found;	/* set to TRUE if found in cfg file */
	char      **char_ptr;	/* where to store string value */
	int        *int_ptr;	/* where to store integer value */
	const char *defValue;	/* default value */
};


#ifdef DEBUG
void
printResults(const char *infos, const struct check *results)
{
	const struct check *ptr;

	printf ("%s\n", infos);
	for (ptr = results; ptr->option; ptr++) {
		if (ptr->int_ptr)
			printf ("option=%s mandatory=%d found=%d value=%d defvalue=%s\n",
				ptr->option, ptr->mandatory, ptr->found, *ptr->int_ptr, ptr->defValue);
		else
			printf ("option=%s mandatory=%d found=%d value=%s defvalue=%s\n",
				ptr->option, ptr->mandatory, ptr->found, *ptr->char_ptr, ptr->defValue);
	}

}

void
printConf(const char *infos, const struct nss_ncp_conf *conf)
{
	printf ("%s\n", infos);
	printf ("debug=%d useTree=%d server=%s startCtx=%s ctrlGroup=%s defGid=%u defShell=%s fallbackUid=%u fallbackGid=%u "
		"doPassword=%d doGroup=%d doShadow=%d\n",
		conf->debug, conf->useTree, conf->server, conf->startCtx,
		conf->ctrlGroup, conf->defGid, conf->defShell, conf->fallbackUid, conf->fallbackGid,
		conf->doPassword, conf->doGroup, conf->doShadow);
}
#endif


static int
process_line(const char *cptr, struct check *ptr)
{

	const char* sptr; // start of real value
	const char* eptr; // end of real value

	char ec;
	char cc;

	while (*cptr && isspace(*cptr))
		cptr++;
	if (*cptr != '=' && *cptr != ':')
		return 1;		//no equal sign found
	cptr++;

	while (*cptr && isspace(*cptr))
		cptr++;

	// space are allowed in value only if surrounded by " or ' (eg a NDS control group)
	if (*cptr == '"' || *cptr == '\'')
		ec = *cptr++;
	else
		ec = 0;
	sptr = cptr;
	eptr = cptr;
	while ((cc = *cptr++) != 0) {
		if (cc == '\n')
			break;
		if (!ec && isspace(cc))
			break;
		if (cc == ec)
			break;
		eptr++;
	}
	if (eptr != sptr) {
		if (ptr->int_ptr) {
			char* errPtr; // err

			*ptr->int_ptr = strtoul(sptr, &errPtr, 0);
			ptr->found = errPtr == eptr;
		} else {
			char *v = malloc(eptr - sptr + 1);
			if (v) {
				memcpy(v, sptr, eptr - sptr);
				v[eptr - sptr] = 0;
				*ptr->char_ptr = v;
				ptr->found = TRUE;
			} else
				return 1;
		}
	}
	return 0;
}

static int
fix_conf(struct check *results)
{
	struct check* ptr;

	for (ptr = results; ptr->option; ptr++) {
		if (!ptr->found) {
			if (ptr->mandatory) {
				traceForce(0, LOG_ERR, "ncp_nss aborting :missing mandatory information '%s=' in config file %s", ptr->option, GLOBALCFGFILE);
				return 1;
			}
			if (ptr->int_ptr) {
				*ptr->int_ptr = strtoul(ptr->defValue, NULL, 0);
			} else {
				char *v = strdup(ptr->defValue);
				if (v)
					*ptr->char_ptr = v;
				else
					return 1;
			}
		}
	}
	return 0;
}


static struct nss_ncp_conf *
read_conf_file(const char * mySection, struct cfgFile *cfg)
{
	struct nss_ncp_conf *conf;

	conf = alloc_nss_ncp_conf();
	if (!conf)
		return NULL;
	{
		struct check check_confs[] = {
		/*option	mandati	found	char_ptr		int_ptr			defValue */
		{"debug",	FALSE,	FALSE,	NULL,			&conf->debug,		"0"},
		{"useTree",	FALSE,	FALSE,	NULL,			&conf->useTree,		"0"},
		{"server",	TRUE,	FALSE,	&conf->server,		NULL,			""},
		{"startCtx",	FALSE,	FALSE,	&conf->startCtx,	NULL,			""},
		{"ctrlGroup",	FALSE,	FALSE,	&conf->ctrlGroup,	NULL,			""},
		{"defGid",	FALSE,	FALSE,	NULL,			&conf->defGid,		"100"},
		{"defShell",	FALSE,	FALSE,	&conf->defShell,	NULL,			"/bin/bash"},
		{"fallbackUid",	FALSE,	FALSE,	NULL,			&conf->fallbackUid,	"-1"},
		{"fallbackGid",	FALSE,	FALSE,	NULL,			&conf->fallbackGid,	"-1"},
		{"doPasswd",	FALSE,	FALSE,	NULL,			&conf->doPassword,	"0"},
		{"doGroup",	FALSE,	FALSE,	NULL,			&conf->doGroup,		"0"},
		{"doShadow",	FALSE,	FALSE,	NULL,			&conf->doShadow,	"0"},
		{NULL,		FALSE,	FALSE,	NULL,			NULL,			NULL}
		};

		char cfgline[16384];
		size_t seclen = strlen(mySection);

#ifdef DEBUG
// DO NOT DO IT A a second call (using test_ncp_nss -2) coredump !!!
//		printResults("before reading CFG", check_confs);
#endif
		ncpt_mutex_lock(&cfg->mutex);
		//rewind(cfg->file);
		while (fgets(cfgline, sizeof(cfgline)-1, cfg->file)) {
		char* cptr = cfgline;
		struct check* ptr;

		while (*cptr && isspace(*cptr))
			cptr++;
			if (*cptr != '[')
				continue;
			if (strncasecmp(++cptr, mySection, seclen))
				continue;
			if (cptr[seclen] != ']')
				continue;
			while (fgets(cfgline, sizeof(cfgline) - 1, cfg->file)) {
				cptr = cfgline;

				while (*cptr && isspace(*cptr))
					cptr++;
				if (!*cptr)
					continue;	//empty line
				if (*cptr == '[')	// start of another section
					goto ssend;
				for (ptr = check_confs; ptr->option; ptr++) {
					size_t keylen = strlen(ptr->option);
					if (!strncasecmp(cptr, ptr->option, keylen)) {
						cptr += keylen;
						process_line(cptr, ptr);
					}
				}
			}
		}

ssend:
#ifdef DEBUG
		printResults("after reading CFG no error", check_confs);
#endif
#ifdef DEBUG
		printConf("before fixing ", conf);
#endif

		if (!fix_conf (check_confs)) {	// fill in missing values with default,
#ifdef DEBUG
			printConf("after fixing ", conf);
#endif
			ncpt_mutex_unlock(&cfg->mutex);
			return conf;
		}

#ifdef DEBUG
		printResults("after reading CFG error", check_confs);
#endif
		ncpt_mutex_unlock(&cfg->mutex);
		free_nss_ncp_conf(conf);
		return NULL;
	}

}

struct nss_ncp_conf*
parse_conf(void)
{
	struct cfgFile *cfg;
	struct nss_ncp_conf *conf;
#ifdef DEBUG
	printf("entering parse_conf\n");
#endif
	cfg = cfgOpenFile(GLOBALCFGFILE, FALSE);
	if (!cfg)
		return NULL;
	conf = read_conf_file(NSS_SECTION, cfg);
	cfgClose(cfg);
#ifdef DEBUG
	if (conf)
		printConf("final value ", conf);
#endif

	return conf;
}

