/*
 * ImapProxy - a caching IMAP proxy daemon
 * Copyright (C) 2002 Steven Van Acker
 * 
 * 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.
 *
 */


#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>

#include <splitstring.h>
#include <network.h>
#include <output.h>
#include <configfile.h>
#include <defines.h>

/* check the header for more info on these vars */
unsigned long config_remote_address = DEFAULT_REMOTE_ADDRESS,config_local_address = DEFAULT_LOCAL_ADDRESS;
unsigned int  config_remote_port = DEFAULT_REMOTE_PORT, config_local_port = DEFAULT_LOCAL_PORT;
int config_enable_debug = DEFAULT_ENABLE_DEBUG;
int config_log_facility = DEFAULT_LOG_FACILITY;
int config_enable_logging = DEFAULT_ENABLE_LOGGING;
int config_client_timeout = DEFAULT_CLIENT_TIMEOUT;
int config_server_timeout = DEFAULT_SERVER_TIMEOUT;
int config_keepalive = DEFAULT_KEEPALIVE;
int config_max_reuse = DEFAULT_MAX_REUSE;
int config_stats_frequency = DEFAULT_STATS_FREQUENCY;

struct access_list_t *config_accesslist = NULL;
unsigned int config_access_count = 0;

/* check_and_test_* :
 * 	parameters:
 * 		var = string containing the variable name
 * 		val = string containing the variable value
 * 	return :
 * 		0 on success
 * 		-1 on error
 * 		1 if this is not the right var name
 */

int check_and_set_debug(char *var,char *val)
{
    if(strcasecmp(var,"debug")) return 1;

    if(!strcasecmp(val,"on"))
	config_enable_debug = 1;
    else if(!strcasecmp(val,"off"))
	config_enable_debug = 0;
    else
    {
	debug("check_and_set_debug(\"%s\",\"%s\"): \"%s\" is not a valid value for \"%s\". Possible values are : \"on\" and \"off\"\n",var,val,val,var);
	return -1;
    }
    
    return 0;
}

int check_and_set_logging(char *var,char *val)
{
    if(strcasecmp(var,"logging")) return 1;
    
    if(!strcasecmp(val,"on"))
        config_enable_logging = 1;
    else if(!strcasecmp(val,"off"))
        config_enable_logging = 0;
    else
    {
        debug("check_and_set_logging(\"%s\",\"%s\"): \"%s\" is not a valid value for \"%s\". Possible values are : \"on\" and \"off\"\n",var,val,val,var);
        return -1;
    }
    
    return 0;
}

int check_and_set_log_facility(char *var,char *val)
{
#ifdef SOLARIS /* Solaris */
    char *values[] = { 	"LOG_AUTH", "LOG_CRON", "LOG_DAEMON", "LOG_KERN", 
			"LOG_LOCAL0", "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", 
			"LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", "LOG_LPR", "LOG_MAIL", "LOG_NEWS", 
			"LOG_SYSLOG", "LOG_USER", "LOG_UUCP" };
    int intvalues[] = {	LOG_AUTH, LOG_CRON, LOG_DAEMON, LOG_KERN, 
			LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, 
			LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_NEWS, 
			LOG_SYSLOG, LOG_USER, LOG_UUCP };
#else
    char *values[] = { 	"LOG_AUTH", "LOG_AUTHPRIV", "LOG_CRON", "LOG_DAEMON", "LOG_KERN", 
			"LOG_LOCAL0", "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", 
			"LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", "LOG_LPR", "LOG_MAIL", "LOG_NEWS", 
			"LOG_SYSLOG", "LOG_USER", "LOG_UUCP" };
    int intvalues[] = {	LOG_AUTH, LOG_AUTHPRIV, LOG_CRON, LOG_DAEMON, LOG_KERN, 
			LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, 
			LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_NEWS, 
			LOG_SYSLOG, LOG_USER, LOG_UUCP };
#endif /* Solaris */

    int i = 0;
    
    if(strcasecmp(var,"log_facility")) return 1;

    for(i = 0;i < sizeof(values)/sizeof(char *);i++)
    {
	if(!strcasecmp(val,values[i]))
	{
	    config_log_facility = intvalues[i];
	    return 0;
	}
    }
   
    debug("check_and_set_log_facility(\"%s\",\"%s\"): \"%s\" is not a valid value for \"%s\". Please specify a valid logfacility (man 3 syslog)\n",var,val,val,var);
    
    return -1;
}

int check_and_set_client_timeout(char *var,char *val)
{
    int i;
    if(strcasecmp(var,"client_timeout")) return 1;

    i = strtol(val,NULL,10);

    if(i <= 0)
    {
	debug("check_and_set_client_timeout(\"%s\",\"%s\"): \"%s\" is not a valid value for \"%s\". Please use a number > 0\n",var,val,val,var);
	return -1;
    }

    config_client_timeout = i;
    
    return 0;
}

int check_and_set_server_timeout(char *var,char *val)
{
    int i;
    if(strcasecmp(var,"server_timeout")) return 1;

    i = strtol(val,NULL,10);

    if(i <= 0)
    {
	debug("check_and_set_server_timeout(\"%s\",\"%s\"): \"%s\" is not a valid value for \"%s\". Please use a number > 0\n",var,val,val,var);
	return -1;
    }

    config_server_timeout = i;
    
    return 0;
}

int check_and_set_keepalive(char *var,char *val)
{
    int i;
    if(strcasecmp(var,"keepalive")) return 1;

    i = strtol(val,NULL,10);

    if(i <= 0)
    {
	debug("check_and_set_keepalive(\"%s\",\"%s\"): \"%s\" is not a valid value for \"%s\". Please use a number > 0\n",var,val,val,var);
	return -1;
    }

    config_keepalive = i;
    
    return 0;
}

int check_and_set_remote_address(char *var,char *val)
{
    unsigned long ip = 0;
    
    if(strcasecmp(var,"remote_address"))
    {
	/* no match, so not our problem. */
	return 1;
    }

    if((ip = my_resolve(val)) == 0)
    {
	debug("check_and_set_remote_address(\"%s\",\"%s\"): Could not resolve \"%s\".\n",var,val,val);
	return -1;
    }

    config_remote_address = ip;
    
    return 0;
}

int check_and_set_local_address(char *var,char *val)
{
    unsigned long ip = 0;
    
    if(strcasecmp(var,"local_address"))
	return 1;
    
    if(!strcmp(val,"*"))
    {
	config_local_address = INADDR_ANY;
	return 0;
    }
    
    if((ip = my_resolve(val)) == 0)
    {
	debug("check_and_set_local_address(\"%s\",\"%s\"): Could not resolve \"%s\".\n",var,val,val);
	return -1;
    }

    config_local_address = ip;
    
    return 0;
}

int check_and_set_remote_port(char *var,char *val)
{
    if(strcasecmp(var,"remote_port"))
	return 1;

    config_remote_port = atoi(val);
    
    return 0;
}

int check_and_set_local_port(char *var,char *val)
{
    if(strcasecmp(var,"local_port"))
	return 1;

    config_local_port = atoi(val);
    
    return 0;
}

int check_and_set_accept_deny(char *var,char *val)
{
    int action = 0;
    unsigned long ip,mask;
    char *p;

    /* what action ? */
    if(!strcasecmp(var,"accept"))
	action = CONFIG_ACCESS_LIST_ACCEPT;
    else if(!strcasecmp(var,"deny"))
	action = CONFIG_ACCESS_LIST_BLOCK;
    else
	return 1;

    /* search for netmask */
    if(!(p = strchr(val,'/')))
    {
	p = "255.255.255.255";
    }
    else
    {
	*p++ = '\0';
    }

    /* what netmask notation ? */
    if(!strchr(p,'.'))
    {
	/* netmask in /n notation */
	int n = 32 - atoi(p);

	mask = htonl(0xffffffff << n);
    }
    else
    {
	/* netmask in ip notation */
	mask = my_resolve(p);
    }

    /* resolve ip part */
    if(!(ip = my_resolve(val)))
    {
	debug("check_and_set_accept_deny(\"\",\"\"): Could not resolve \"%s\".\n",var,val,val);
	return -1;
    }
   
    /* add to accesslist */
    if(!(config_accesslist = (struct access_list_t *)realloc(config_accesslist,sizeof(struct access_list_t)*(config_access_count+1))))
    {
	debug("check_and_set_accept_deny(\"\",\"\"): Out of memory.\n",var,val);
	return -1;
    }
	
    config_accesslist[config_access_count].ip = ip;
    config_accesslist[config_access_count].mask = mask;
    config_accesslist[config_access_count].action = action;
    
    config_access_count++;
    
    return 0;
}

int check_and_set_max_reuse(char *var,char *val)
{
    if(strcasecmp(var,"max_reuse"))
	return 1;
    
    config_max_reuse = atoi(val);

    if(config_max_reuse < 0)
    {
	debug("check_and_set_max_reuse(\"\",\"\"): Invalid value (Should be >= 0).\n",var,val);
    }

    return 0;
}

int check_and_set_stats_frequency(char *var,char *val)
{
    if(strcasecmp(var,"stats_frequency"))
	return 1;
    
    config_stats_frequency = atoi(val);

    if(config_stats_frequency < 0)
    {
	debug("check_and_set_stats_frequency(\"\",\"\"): Invalid value (Should be >= 0).\n",var,val);
    }

    return 0;
}

ConfigFunction Functions[] = 
{
    {check_and_set_debug},
    {check_and_set_logging},
    {check_and_set_log_facility},
    {check_and_set_client_timeout},
    {check_and_set_server_timeout},
    {check_and_set_keepalive},
    {check_and_set_remote_address},
    {check_and_set_local_address},
    {check_and_set_remote_port},
    {check_and_set_local_port},
    {check_and_set_accept_deny},
    {check_and_set_max_reuse},
    {check_and_set_stats_frequency}
};


/* read the configfile 
 * return 0 on success
 * -1 on error
 */

int config_file_read(char *file)
{
    FILE *fd;
    char buf[1024];
    int linecount = 0;
    int i = 0,r = 0;
    
    Split_string ss;
    
    if(!(fd = fopen(file,"r")))
    {
	debug("config_file_read(\"%s\"): Could not open configfile (%m).\n",file);
	return -1;
    }
  
    /* this function may be called on SIGHUP,
     * so make sure we don't leak
     */
    if(config_accesslist)
    {
	free(config_accesslist);
	config_accesslist = NULL;
    }

    config_access_count = 0;
    
    while(fgets(buf,sizeof(buf),fd))
    {
	char *p;
	
	if((p = strchr(buf,'\r'))) *p = '\0';
	if((p = strchr(buf,'\n'))) *p = '\0';
	
	/* need no comments either */
	if((p = strchr(buf,'#'))) *p = '\0';
	
	
	linecount++;
	
	init_split_string(&ss);
	/* split string in pieces */
	if(make_split_string_strip_crlf(buf,&ss," \t"))
	{
	    debug("config_file_read(\"%s\"): Error parsing config file (line %d).\n",file,linecount);
	    return -1;
	}

	if(split_string_count_parts(&ss) == 0)
	{
	    goto next_line;
	}
	if(split_string_count_parts(&ss) != 2)
	{
	    debug("config_file_read(\"%s\"): Line \"%s\" is not a variable/value pair (line %d).\n",file,buf,linecount);
	    return -1;
	}

	for(i = 0;i < sizeof(Functions)/sizeof(ConfigFunction); i++)
	{
	    r = (Functions[i].f)(split_string_get(&ss,0),split_string_get(&ss,1));
	   
	    if(r < 0)
	    {
		debug("config_file_read(\"%s\"): configuration error on line %d (\"%s\").\n",file,linecount,buf);
		return -1;
	    }
	    else if(r == 0)
	    {
		goto next_line;
	    }
	}

	/* if we get here, it means that no function matched any config token
	 */

	debug("config_file_read(\"%s\"): unknown configuration variable \"%s\" on line %d (\"%s\").\n",file,split_string_get(&ss,0),linecount,buf);
	return -1;
	
next_line:
	
	delete_split_string(&ss);
    }

    fclose(fd);
    return 0;
}

/* check if a certain IP is blocked.
 * returns 1 if blocked
 * 0 otherwise
 */
int check_if_blocked(unsigned long ip)
{
    int i = 0;
    struct access_list_t *current = NULL;
    
    for(i = 0; i < config_access_count; i++)
    {
	current = &config_accesslist[i];

	if((current->mask & ip) == (current->ip & current->mask))
	{
	    if(current->action == CONFIG_ACCESS_LIST_BLOCK)
		return 1;
	    return 0;
	}
    }

    /* if IP is not in the accesslist, then deny by default */

    return 1;
    
}

char *log_facility_to_str(int x)
{
#ifdef SOLARIS /* Solaris */
    char *values[] = { 	"LOG_AUTH", "LOG_CRON", "LOG_DAEMON", "LOG_KERN", 
			"LOG_LOCAL0", "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", 
			"LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", "LOG_LPR", "LOG_MAIL", "LOG_NEWS", 
			"LOG_SYSLOG", "LOG_USER", "LOG_UUCP" };
    int intvalues[] = {	LOG_AUTH, LOG_CRON, LOG_DAEMON, LOG_KERN, 
			LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, 
			LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_NEWS, 
			LOG_SYSLOG, LOG_USER, LOG_UUCP };
#else
    char *values[] = { 	"LOG_AUTH", "LOG_AUTHPRIV", "LOG_CRON", "LOG_DAEMON", "LOG_KERN", 
			"LOG_LOCAL0", "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", 
			"LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", "LOG_LPR", "LOG_MAIL", "LOG_NEWS", 
			"LOG_SYSLOG", "LOG_USER", "LOG_UUCP" };
    int intvalues[] = {	LOG_AUTH, LOG_AUTHPRIV, LOG_CRON, LOG_DAEMON, LOG_KERN, 
			LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, 
			LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_NEWS, 
			LOG_SYSLOG, LOG_USER, LOG_UUCP };
#endif /* Solaris */

    int i = 0;
    
    for(i = 0;i < sizeof(values)/sizeof(char *);i++)
    {
	if(intvalues[i] == x)
	{
	    return values[i];
	}
    }
   
    return NULL;
}

void configfile_print(char *conffile)
{
    int i;
    struct in_addr x;

    fprintf(stderr,"Configuration (%s):\n",conffile);
    x.s_addr = config_remote_address;
    fprintf(stderr,"\tremote address: %s\n",inet_ntoa(x));
    fprintf(stderr,"\tremote port: %d\n",config_remote_port);
    x.s_addr = config_local_address;
    fprintf(stderr,"\tlocal address: %s\n",inet_ntoa(x));
    fprintf(stderr,"\tlocal port: %d\n",config_local_port);
    fprintf(stderr,"\t%d access-rule(s):\n",config_access_count);

    for(i = 0;i < config_access_count ; i++)
    {
	fprintf(stderr,"\t\t%s ",config_accesslist[i].action == CONFIG_ACCESS_LIST_ACCEPT?"accept":"deny");
	x.s_addr = config_accesslist[i].ip;
	fprintf(stderr,"%s",inet_ntoa(x));
	x.s_addr = config_accesslist[i].mask;
	fprintf(stderr,"/%s\n",inet_ntoa(x));
    }
    
    fprintf(stderr,"\tlogging: %s\n",config_enable_logging?"ON":"OFF");
    fprintf(stderr,"\tlogging to: %s\n",log_facility_to_str(config_log_facility));
    fprintf(stderr,"\tdebug: %s\n",config_enable_debug?"ON":"OFF");
    fprintf(stderr,"\tclient_timeout: %d s\n",config_client_timeout);
    fprintf(stderr,"\tserver_timeout: %d s\n",config_server_timeout);
    fprintf(stderr,"\tkeepalive: %d s\n",config_keepalive);
    fprintf(stderr,"\tstats_frequency: %d s\n",config_stats_frequency);
    fprintf(stderr,"\tmax_reuse: %d times\n",config_max_reuse);
}


syntax highlighted by Code2HTML, v. 0.9.1