/*
 * Copyright (c) 2004-2009, Luiz Otavio O Souza <loos.br@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

static const char rcsid[] = "$Id: config.c 112 2009-03-15 17:30:28Z loos-br $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include "config.h"
#include "return.h"
#include "msn-proxy.h"

#ifndef MAX_BUF
#define MAX_BUF		1024
#endif

#define ISSPACE(c)	((c) == 0x20 || (c) == 0x09)
#define ISNOTEOL(c)	((c) != 0x0a && (c) != 0x0d)
#define ISCOMMENT(c)	((c) == 0x23)
#define ISNOTEQUAL(c)	((c) != 0x3d)

typedef struct {
    const char	*key;
    int		(*func)();
} __cmd;

const __cmd _cmd[] = {
    { "default_ns_host",	set_string },
    { "default_ns_port",	set_string },
    { "listen_host",		set_string },
    { "listen_port",		set_string },
    { "backlog",		set_int },
    { "port_min",		set_int },
    { "port_max",		set_int },
    { "max_clients",		set_int },
    { "max_ctl_clients",	set_int },
    { "timeout_listen",		set_timeout },
    { "timeout_ctl_read",	set_timeout },
    { "timeout_client_read",	set_timeout },
    { "timeout_client_write",	set_timeout },
    { "timeout_server_read",	set_timeout },
    { "timeout_server_write",	set_timeout },
    { (char *)0,		config_no_op }
};

int config_default(config_ *config) {

	memset(config, 0, sizeof(*config));

    /* default config file path */
    if (str_copys(&config->file, (unsigned char *)DEFAULT_CONFIG_PATH) == 0 ||
    str_cats(&config->file, (unsigned char *)DEFAULT_CONFIG_FILE) == 0) {

    	die_nomem();
    }

    /* default M$ notification server */
    if (str_copys(&config->default_ns_host, (unsigned char *)DEFAULT_NS_HOST) == 0 ||
	str_copys(&config->default_ns_port, (unsigned char *)DEFAULT_NS_PORT) == 0 ||
	str_copys(&config->listen_host, (unsigned char *)LISTEN_HOST) == 0 ||
	str_copys(&config->listen_port, (unsigned char *)LISTEN_PORT) == 0) {

	die_nomem();
    }

    /* defaults */
    config->backlog	    = 10;
    config->port_min	    = 25000;
    config->port_max	    = 30000;
    config->max_clients	    = 10;
    config->max_ctl_clients = 10;

    /* timeouts */
    config->timeout_listen.tv_sec	= 180;
    config->timeout_ctl_read.tv_sec	= 5;
    config->timeout_client_read.tv_sec  = 600;
    config->timeout_client_write.tv_sec = 60;
    config->timeout_server_read.tv_sec  = 600;
    config->timeout_server_write.tv_sec = 60;

    return(0);
}

int parse_config(config_ *config) {
 int			i;
 string			buf = {0};
 string			tmp = {0};
 string			key = {0};
 string			val = {0};
 FILE			*fp;
 log_			*log = &config->log;

    /* open config file */
    log->info("info: reading config file [%s]\n", &config->file);
    fp = fopen((char *)config->file.s, READONLY);
    if (fp == (FILE *)0) {
	log->debug("debug: cannot open config file [%s]\n", &config->file);
	return(RFAIL);
    }

    /* alloc buf */
    if (str_ready(&buf, MAX_BUF) == 0)
	die_nomem();

    tmp.a = buf.a;

    while (fgets((char *)buf.s, buf.a, fp)) {

	tmp.s = buf.s;
	tmp.len = buf.len = str_len(&buf);

	/* skip spaces at begin */
	while (tmp.len > 0 && ISSPACE(*tmp.s)) {
	    ++tmp.s; --tmp.len;
	}

	/* skip comment lines */
	if (tmp.len > 0 && ISCOMMENT(*tmp.s)) {
	    continue;
	}

	/* remove cr/lf from buf */
	if (tmp.len > 0) {
	    str_strip(&tmp);
	}

	/* get key */
	key.s = tmp.s;
	while (tmp.len > 0 && ISNOTEQUAL(*tmp.s)) {
	    ++tmp.s; --tmp.len;
	}

	if ((key.len = tmp.s - key.s) < 0) {
	    key.len = 0;
	}

	/* skip equal */
	if (tmp.len > 0) {
	    *tmp.s++ = 0;
	    --tmp.len;
	}

	/* get value */
	val.s = tmp.s;
	while (tmp.len > 0 && ISNOTEOL(*tmp.s)) {
	    ++tmp.s; --tmp.len;
	}

	if ((val.len = tmp.s - val.s) < 0) {
	    val.len = 0;
	}

	if (tmp.len > 0) {
	    *tmp.s++ = 0;
	    --tmp.len;
	}

	i = 0;
	while (key.len > 0) {

	    /* invalid command */
	    if (_cmd[i].key == (char *)0) {
		log->debug("debug: invalid config parameter: [%s]\n", &key);
		str_free(&buf);
		fclose(fp);
		return(RFAIL);
	    }

	    if (strcasecmp(_cmd[i].key, (char *)key.s) == 0) {

		if (_cmd[i].func(config, &key, &val) == -1) {
		    str_free(&buf);
		    fclose(fp);
		    return(RFAIL);
		}
		break;
	    }

	    ++i;
	}
    }

    /* close and return */
    str_free(&buf);
    fclose(fp);
    return(ROK);
}

int config_no_op(config_ *config, string *key, string *val) {
    return(0);
}

int set_string(config_ *config, string *key, string *val) {

    if (!val || !val->s)
	return(-1);

    if (strcasecmp((char *)key->s, "default_ns_host") == 0) {
	if (str_copy(&config->default_ns_host, val->s, val->len) == 0)
	    die_nomem();
    } else if (strcasecmp((char *)key->s, "default_ns_port") == 0) {
	if (str_copy(&config->default_ns_port, val->s, val->len) == 0)
	    die_nomem();
    } else if (strcasecmp((char *)key->s, "listen_host") == 0) {
	if (str_copy(&config->listen_host, val->s, val->len) == 0)
	    die_nomem();
    } else if (strcasecmp((char *)key->s, "listen_port") == 0) {
	if (str_copy(&config->listen_port, val->s, val->len) == 0)
	    die_nomem();
    }

    return(0);
}

int set_int(config_ *config, string *key, string *val) {
 unsigned int		rtrn = 0;

    if (!val || !val->s)
	return(-1);

    rtrn = (unsigned int)strtol((char *)val->s, (char **)0, 10);

    if (strcasecmp((char *)key->s, "max_clients") == 0)
	config->max_clients = rtrn;
    else if (strcasecmp((char *)key->s, "max_ctl_clients") == 0)
	config->max_ctl_clients = rtrn;
    else if (strcasecmp((char *)key->s, "port_min") == 0)
	config->port_min = rtrn;
    else if (strcasecmp((char *)key->s, "port_max") == 0)
	config->port_max = rtrn;
    else if (strcasecmp((char *)key->s, "backlog") == 0)
	config->backlog = rtrn;

    return(0);
}

int set_timeout(config_ *config, string *key, string *val) {
 unsigned int	secs;

    if (!val || !val->s)
	return(-1);

    secs = (unsigned int)strtol((char *)val->s, (char **)0, 10);

    if (strcasecmp((char *)key->s, "timeout_listen") == 0)
	config->timeout_listen.tv_sec = secs;
    else if (strcasecmp((char *)key->s, "timeout_ctl_read") == 0)
	config->timeout_ctl_read.tv_sec = secs;
    else if (strcasecmp((char *)key->s, "timeout_client_read") == 0)
	config->timeout_client_read.tv_sec = secs;
    else if (strcasecmp((char *)key->s, "timeout_client_write") == 0)
	config->timeout_client_write.tv_sec = secs;
    else if (strcasecmp((char *)key->s, "timeout_server_read") == 0)
	config->timeout_server_read.tv_sec = secs;
    else if (strcasecmp((char *)key->s, "timeout_server_write") == 0)
	config->timeout_server_write.tv_sec = secs;

    return(0);
}

int config_set_path(string *file, char *path) {
	/* Deallocate previous memory */
	str_free(file);
	str_zero(file);
    if (str_cats(file, (unsigned char *)path) == 0)
	die_nomem();

    if ( file->len > 0 && *(file->s + file->len - 1) != '/' ) {
	if (str_cats(file, (unsigned char *)"/") == 0)
	    die_nomem();
    }

    if (str_cats(file, (unsigned char *)DEFAULT_CONFIG_FILE) == 0)
	die_nomem();

    return(0);
}

void print_config(config_ *config) {
 log_			*log = &config->log;
 defaults_		*defaults = &config->defaults;

    log->debug("msn-proxy release     : %S [%S]\n", "msn-proxy-"
						    MSNPROXY_VERSION,
						    MSNPROXY_RELEASE);
    log->debug("config file           : %s\n", &config->file);
    log->debug("default ns host       : %s\n", &config->default_ns_host);
    log->debug("default ns port       : %s\n", &config->default_ns_port);
    log->debug("listen host           : %s\n", &config->listen_host);
    log->debug("listen port           : %s\n", &config->listen_port);
    log->debug("backlog               : %d\n", config->backlog);
    log->debug("max clients           : %d\n", config->max_clients);
    log->debug("max ctl clients       : %d\n", config->max_ctl_clients);
    log->debug("port range begin      : %d\n", config->port_min);
    log->debug("port range end        : %d\n", config->port_max);
    log->debug("ctl read timeout      : %d\n", config->timeout_ctl_read.tv_sec);
    log->debug("client read timeout   : %d\n", config->timeout_client_read.tv_sec);
    log->debug("client write timeout  : %d\n", config->timeout_client_write.tv_sec);
    log->debug("server read timeout   : %d\n", config->timeout_server_read.tv_sec);
    log->debug("server write timeout  : %d\n", config->timeout_server_write.tv_sec);
    log->debug("client connect timeout: %d\n", config->timeout_listen.tv_sec);
    log->debug("\n");
    log->debug("ACLs\n");
    log->debug("connect               : %S\n",
				(defaults->connect == YES) ? "ALLOW" : "DENY");
    log->debug("log messages          : %S\n",
				(defaults->save_msg == YES) ? "YES" : "NO");
    log->debug("save_contacts         : %S\n",
				(defaults->save_contacts == YES) ? "YES" : "NO");
    log->debug("\n");
}
