/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: config.c,v 1.41 2002/11/08 23:49:30 khaytsus Exp $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stddef.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include "opennap.h"
#include "debug.h"
#ifdef WIN32
#include <io.h>
#endif

typedef enum
{
    VAR_TYPE_INT,
    VAR_TYPE_STR,
    VAR_TYPE_BOOL,
    VAR_TYPE_LIST
}
VAR_TYPE;

#define CF_ONCE	1		/* may only be set in config file or command line */
#define CF_HIDDEN 2		/* can't be queried by a client */
#define CF_OPTVAL 4     /* value is optional, may be empty */

struct config
{
    char   *name;
    VAR_TYPE type;
    unsigned long val;
    unsigned long def;		/* default value */
    unsigned int flags;
};

#define UL (unsigned long)

static struct config Vars[] = {
#ifndef ROUTING_ONLY
    {"allow_share", VAR_TYPE_BOOL, ON_ALLOW_SHARE, 1, 0},
    {"eject_after", VAR_TYPE_INT, UL & EjectAfter, 120, 0},
    {"eject_leeches", VAR_TYPE_BOOL, ON_EJECT_LEECHES, 1, 0},
    {"eject_no_channels_only", VAR_TYPE_BOOL, ON_EJECT_NO_CHANNELS_ONLY, 1, 0},
    {"eject_when_full", VAR_TYPE_BOOL, ON_EJECT_WHEN_FULL, 1, 0},
    {"eject_limit_files", VAR_TYPE_INT, UL & global.eject_limit_files, 0, 0},
    {"eject_limit_libsize", VAR_TYPE_INT, UL & global.eject_limit_libsize, 0, 0},
/*    {"eject_also_bans", VAR_TYPE_INT, UL & global.eject_also_bans, 0, 0}, */
    {"eject_ban_ttl", VAR_TYPE_INT, UL & global.eject_ban_ttl, 7200, 0},
    {"eject_grace_time", VAR_TYPE_INT, UL & global.eject_grace_time, 600, 0},
    {"leech_share", VAR_TYPE_BOOL, ON_LEECH_SHARE, 0, 0},
    {"alnum_nicks", VAR_TYPE_BOOL, ON_ALNUM_NICKS, 0, 0},
    {"ascii_filenames", VAR_TYPE_BOOL, ON_ASCII_FILENAMES, 0, 0},
    
    {"min_file_size", VAR_TYPE_INT, UL & global.min_file_size, 0, 0},
    {"max_file_size", VAR_TYPE_INT, UL & global.max_file_size, 0, 0},
    {"index_ignore_suffix", VAR_TYPE_BOOL, ON_IGNORE_SUFFIX, 1, 0},
    {"index_path_depth", VAR_TYPE_INT, UL & Index_Path_Depth, 2, 0},
    {"file_count_threshold", VAR_TYPE_INT, UL & File_Count_Threshold, 5000,
     0},
    {"max_results", VAR_TYPE_INT, UL & Max_Search_Results, 100, 0},
    {"max_searches_pending", VAR_TYPE_INT, UL & Max_Searches_Pending, 3, 0},
    
/*  {"max_new_users_per_minute", VAR_TYPE_INT, UL & global.max_new_users_per_minute, 0, 0}, */
    {"max_new_users_count", VAR_TYPE_INT, UL & global.max_new_users_count, 20, 0},
    {"max_new_users_time", VAR_TYPE_INT, UL & global.max_new_users_time, 10, 0},
    {"max_uploading", VAR_TYPE_INT, UL & global.max_uploading, 100, 0},
    {"max_block_pct", VAR_TYPE_INT, UL & global.max_block_pct, 0, 0},
    {"max_block_pct_ban_ttl", VAR_TYPE_INT, UL & global.max_block_pct_ban_ttl, 0, 0},
    {"max_whois_count", VAR_TYPE_INT, UL & global.max_whois_count, 0, 0},
    {"max_whois_time", VAR_TYPE_INT, UL & global.max_whois_time, 0, 0},
    {"max_whois_ban_ttl", VAR_TYPE_INT, UL & global.max_whois_ban_ttl, 0, 0},
    
    {"report_name", VAR_TYPE_STR, UL & global.report_name, 0, CF_OPTVAL},
    {"report_ip", VAR_TYPE_STR, UL & global.report_ip, 0, CF_OPTVAL},
    {"report_port", VAR_TYPE_INT, UL & global.report_port, 0, 0},
    {"stat_server_host", VAR_TYPE_STR, UL & global.stat_server,UL "stats.napigator.com", CF_OPTVAL},
    {"stat_server_pass", VAR_TYPE_STR, UL & global.stat_pass, 0, CF_HIDDEN | CF_OPTVAL},
    {"stat_server_port", VAR_TYPE_INT, UL & global.stat_server_port, 8890, 0},
    {"stat_server_user", VAR_TYPE_STR, UL & global.stat_user, 0, CF_HIDDEN | CF_OPTVAL},
    {"stats_port", VAR_TYPE_INT, UL & Stats_Port, 8889, CF_ONCE},
    {"no_share_level", VAR_TYPE_INT, UL & global.no_share_level, 4, 0},
#endif
    
    {"ibl_ttl", VAR_TYPE_INT, UL & global.ibl_ttl, 0, 0},
    {"default_ban_ttl", VAR_TYPE_INT, UL & global.default_ban_ttl, 17280000, 0},
    {"notify_mod_abuse", VAR_TYPE_BOOL, ON_NOTIFY_MOD_ABUSE, 1, 0},
    {"notify_mod_abuse_frequency", VAR_TYPE_INT, UL & global.notify_mod_abuse_frequency, 100, 0},
    
    {"notify_user_abuse", VAR_TYPE_BOOL, ON_NOTIFY_USER_ABUSE, 0, 0},
    {"notify_user_abuse_frequency", VAR_TYPE_INT, UL & global.notify_user_abuse_frequency, 1000, 0},
    
    {"evaluate_search_abuse_after_secs", VAR_TYPE_INT, UL & global.evaluate_search_abuse_after_secs, 120, 0},
    {"evaluate_search_abuse_after_tags", VAR_TYPE_INT, UL & global.evaluate_search_abuse_after_tags, 100, 0},
    {"max_searches_per_minute", VAR_TYPE_INT, UL & global.max_searches_per_minute, 2, 0},
    {"break_mx_queue", VAR_TYPE_BOOL, ON_BREAK_MX_QUEUE, 0, 0},
    {"level_to_set_flags", VAR_TYPE_INT, UL & global.level_to_set_flags,UL 2, 0},
    {"discipline_ignorers_ban_ttl", VAR_TYPE_INT, UL & global.discipline_ignorers_ban_ttl,UL 2592000, 0},
    {"notify_mod_block", VAR_TYPE_BOOL, ON_NOTIFY_MOD_BLOCK, 0, 0},
    {"notify_user_block", VAR_TYPE_BOOL, ON_NOTIFY_USER_BLOCK, 0, 0},
    {"log_channel", VAR_TYPE_BOOL, ON_LOG_CHANNEL, 1, 0},
    {"log_timefmt", VAR_TYPE_STR, UL & global.log_timefmt, UL "%H:%M:%S: ", CF_OPTVAL},
    {"log_blocked", VAR_TYPE_BOOL, ON_LOG_BLOCKED, 0, 0},
    {"log_ignore_abuse", VAR_TYPE_INT, UL & global.log_ignore_abuse, 1, 0},
    {"browse_nag", VAR_TYPE_BOOL, ON_BROWSE_NAG, 0, 0},
    /* Search cacheing settings from here on 500 cache entries should be sufficient for 
       a quick'n dirty proof of concept. */
    {"search_max_cache_entries", VAR_TYPE_INT, UL & global.search_max_cache_entries, 500, 0},
    
    {"no_mod_annoying", VAR_TYPE_BOOL, ON_NO_MOD_ANNOYING, 0, 0},
    {"max_tags_per_minute", VAR_TYPE_INT, UL & global.max_tags_per_minute, 2, 0},
    {"max_shared", VAR_TYPE_INT, UL & Max_Shared, 5000, 0},
    {"verbose_too_many", VAR_TYPE_BOOL, ON_VERBOSE_TOO_MANY, 1, 0},
    {"fix_xnap_path", VAR_TYPE_INT, UL & global.fix_xnap_path, 1, 0},
    {"notify_exceed_frequency", VAR_TYPE_INT, UL & global.notify_exceed_frequency, 10, 0},
    {"auto_link", VAR_TYPE_BOOL, ON_AUTO_LINK, 0, 0},
    {"auto_register", VAR_TYPE_BOOL, ON_AUTO_REGISTER, 0, 0},
    {"client_queue_length", VAR_TYPE_INT, UL & Client_Queue_Length, 102400,0},
    {"compression_level", VAR_TYPE_INT, UL & Compression_Level, 1, CF_ONCE},
    {"flood_commands", VAR_TYPE_INT, UL & Flood_Commands, 0, 0},
    {"flood_time", VAR_TYPE_INT, UL & Flood_Time, 0, 0},
    {"flood_eject", VAR_TYPE_INT, UL & Flood_Eject, 0, 0},
    {"flood_ban_ttl", VAR_TYPE_INT, UL & Flood_Ban_TTL, 86400, 0},
    {"ghost_kill", VAR_TYPE_INT, UL & global.ghost_kill, 1, 0},
    {"cloak_user_level", VAR_TYPE_INT, UL & global.cloak_user_level, 0, 0},
    {"clones_allow_level", VAR_TYPE_INT, UL & global.clones_allow_level, 3, 0},

    {"irc_channels", VAR_TYPE_BOOL, ON_IRC_CHANNELS, 1, 0},
    {"listen_address", VAR_TYPE_STR, UL & Listen_Addr, UL "0.0.0.0", CF_ONCE},
    {"log_mode", VAR_TYPE_BOOL, ON_LOGLEVEL_CHANGE, 0, 0},
    {"log_level", VAR_TYPE_INT, UL & Log_Level, /* 2047, 0}, */
        (LOG_LEVEL_SERVER | LOG_LEVEL_CLIENT | LOG_LEVEL_LOGIN | LOG_LEVEL_FILES | LOG_LEVEL_ERROR | LOG_LEVEL_SECURITY | LOG_LEVEL_STATS),
    0},
    
    {"log_stdout", VAR_TYPE_BOOL, ON_LOG_STDOUT, 1, 0},
    {"login_interval", VAR_TYPE_INT, UL & Login_Interval, 0, 0},
    {"login_timeout", VAR_TYPE_INT, UL & Login_Timeout, 60, 0},
    {"max_browse_result", VAR_TYPE_INT, UL & Max_Browse_Result, 500, 0},
    {"max_channel_length", VAR_TYPE_INT, UL & Max_Channel_Length, 32, 0},
    {"max_client_string", VAR_TYPE_INT, UL & Max_Client_String, 32, 0},
    {"max_clones", VAR_TYPE_INT, UL & Max_Clones, 0, 0},
    {"max_command_length", VAR_TYPE_INT, UL & Max_Command_Length, 2048, 0},
    {"max_connections", VAR_TYPE_INT, UL & Max_Connections, FD_SETSIZE, 0},
    {"max_hotlist", VAR_TYPE_INT, UL & Max_Hotlist, 32, 0},
    {"max_ignore", VAR_TYPE_INT, UL & Max_Ignore, 32, 0},
    {"max_nick_length", VAR_TYPE_INT, UL & Max_Nick_Length, 19, 0},
    {"max_reason", VAR_TYPE_INT, UL & Max_Reason, 96, 0},
    {"max_time_delta", VAR_TYPE_INT, UL & Max_Time_Delta, 90, 0},
    {"max_topic", VAR_TYPE_INT, UL & Max_Topic, 64, 0},
    {"max_user_channels", VAR_TYPE_INT, UL & Max_User_Channels, 5, 0},
    {"min_read", VAR_TYPE_INT, UL & global.min_read, 0, 0},
    {"nick_expire", VAR_TYPE_INT, UL & Nick_Expire, 2678400 /* 31 days */ ,
     0},
    {"ping_interval", VAR_TYPE_INT, UL & Ping_Interval, 600, 0},
    {"register_interval", VAR_TYPE_INT, UL & Register_Interval, 0, 0},
    {"registered_only", VAR_TYPE_BOOL, ON_REGISTERED_ONLY, 0, 0},
    {"restrict_registration", VAR_TYPE_BOOL, ON_RESTRICT_REGISTRATION, 0, 0},
    {"remote_browse", VAR_TYPE_BOOL, ON_REMOTE_BROWSE, 1, 0},
    {"remote_config", VAR_TYPE_BOOL, ON_REMOTE_CONFIG, 1, 0},
    {"search_timeout", VAR_TYPE_INT, UL & Search_Timeout, 180, 0},
    {"server_alias", VAR_TYPE_STR, UL & Server_Alias, 0, CF_ONCE | CF_OPTVAL},
    {"server_chunk", VAR_TYPE_INT, UL & Server_Chunk, 0, 0},
    {"server_name", VAR_TYPE_STR, UL & Server_Name, 0, CF_ONCE | CF_OPTVAL},
    {"server_ports", VAR_TYPE_LIST, UL & Server_Ports, UL "8888", CF_ONCE},
    {"server_queue_length", VAR_TYPE_INT, UL & Server_Queue_Length, 1048576,
     0},
    {"stat_click", VAR_TYPE_INT, UL & global.stat_click, 60, 0},
    {"strict_channels", VAR_TYPE_BOOL, ON_STRICT_CHANNELS, 0, 0},
    {"user_db_interval", VAR_TYPE_INT, UL & User_Db_Interval, 1800, 0},
    {"usermode", VAR_TYPE_STR, UL & UserMode, UL "ALL", CF_ONCE},
    {"warn_time_delta", VAR_TYPE_INT, UL & Warn_Time_Delta, 30, 0},
    {"who_was_time", VAR_TYPE_INT, UL & Who_Was_Time, 300, 0},
#ifdef USE_INVALID_CLIENTS
    {"invalid_clients", VAR_TYPE_STR, UL & Invalid_Clients, UL "", CF_OPTVAL}, /* Add by winter_mute */
    {"valid_clients", VAR_TYPE_STR, UL & Valid_Clients, UL "", CF_OPTVAL},
#endif
#ifdef USE_INVALID_NICKS
    {"invalid_nicks", VAR_TYPE_STR, UL & Invalid_Nicks, UL "", CF_OPTVAL}, /* Add by winter_mute */
#endif
#ifdef USE_PROTNET
    {"protnet", VAR_TYPE_STR, UL & Protnet, UL "*", CF_OPTVAL}, /* Add by winter_mute */
#endif
#if defined (USE_INVALID_CLIENTS) || defined (USE_INVALID_NICKS)
    {"set_server_nicks", VAR_TYPE_STR, UL & Set_Server_Nicks, UL "", CF_OPTVAL},
#endif
#ifndef WIN32
    {"connection_hard_limit", VAR_TYPE_INT, UL & Connection_Hard_Limit, FD_SETSIZE, CF_ONCE},
    {"max_data_size", VAR_TYPE_INT, UL & Max_Data_Size, 0, CF_ONCE},
    {"max_rss_size", VAR_TYPE_INT, UL & Max_Rss_Size, 0, CF_ONCE},
    {"lock_memory", VAR_TYPE_BOOL, ON_LOCK_MEMORY, 0, CF_ONCE},
#endif
    {"loopcount_output_interval", VAR_TYPE_INT, UL & global.loopcount_output_interval, 1000, 0},
    {"critical_delay", VAR_TYPE_INT, UL & global.critical_delay, 4, 0},
};

static int Vars_Size = sizeof (Vars) / sizeof (struct config);

/* Added by winter_mute
   returns 1 if none of the comma delimited strings in arg have a low ratio 
   of meta-chars (* ?) with respect to it's length.
*/
#if defined (USE_INVALID_CLIENTS) || defined (USE_INVALID_NICKS)
static int
check_meta_chars(const char* arg)
{ 
   unsigned short count = 0;
   const char *delim = ",";
   char *val, *tmp, *ptr, *ptrtmp;
   
   ptr = STRDUP( arg );
   ptrtmp = ptr;

   /* Seperate the string by , and loop throug each one */
   val = strsep(&ptr, delim);
    log_message_level (LOG_LEVEL_DEBUG, "check_meta_chars: \"%s\"", ptr);

   if (ptr)
   {
      do
      {
         tmp = val;
/*        log_message_level (LOG_LEVEL_DEBUG, "check_meta_chars: item \"%s\"", ptr); */
         while (*val != '\0')
         {   if (*val == '*' || *val == '?')
               count++;
            val++;
         }
/*         log_message_level (LOG_LEVEL_DEBUG, "strlen(tmp) - count: %d", strlen(tmp) - count); */
         if ((strlen(tmp) - count) <= 1) {
            FREE (ptr);
	    return 0;
         }
         count = 0;
      } while ((val = strsep(&ptr, delim)) != NULL);
   }
   else
   {  
      while (*val != '\0')
      {  if (*val == '*' || *val == '?')
            count++;
         val++;
      }
      if ((strlen(arg) - count) <= 2)
      {
	if (ptrtmp) {
	    FREE (ptrtmp);
	}
	return 0;
      }
   }
   if (ptrtmp) {
       FREE (ptrtmp);
   }
   return 1;
}
#endif

static void
set_int_var (struct config *v, int val)
{
    ASSERT (v->type == VAR_TYPE_INT);
    *(int *) v->val = val;
}

static void
set_str_var (struct config *v, const char *s)
{
    char  **ptr;

    ASSERT (v->type == VAR_TYPE_STR);
    ptr = (char **) v->val;
    if (*ptr)
	FREE (*ptr);
    *ptr = STRDUP (s);
}

static void
set_list_var (struct config *v, const char *s)
{
    int     ac, i;
    char   *av[32];
    LIST   *tmpList, *list = 0;


    ASSERT (v->type == VAR_TYPE_LIST);
    strncpy (Buf, s, sizeof (Buf) - 1);
    Buf[sizeof (Buf) - 1] = 0;
    ac = split_line (av, FIELDS (av), Buf);
    for (i = 0; i < ac; i++)
    {
	tmpList = CALLOC (1, sizeof (LIST));
	tmpList->data = STRDUP (av[i]);
	tmpList->next = list;
	list = tmpList;
    }
    list_free (*(LIST **) v->val, free_pointer);
    *(LIST **) v->val = list;
}

static void
set_bool_var (struct config *v, int on)
{
    ASSERT (v->type == VAR_TYPE_BOOL);
    if (on)
	Server_Flags |= v->val;
    else
	Server_Flags &= ~v->val;
}


/* Set variable with given value ------------------------------------------ */

static int set_var (const char *var, const char *val, int init) {
 int    i;
 int    n;
 char  *ptr;

/*    log_message_level (LOG_LEVEL_DEBUG, "set_var: var \"%s\", val \"%s\"", var, val); */
    for (i = 0; i < Vars_Size; i++)
    {
	if (!strcmp (Vars[i].name, var))
	{
	    if (!init && (Vars[i].flags & CF_ONCE))
	    {
		log_message_level (LOG_LEVEL_SERVER, "set_var: %s may not be reset/only set in the config file", Vars[i].name);
		return -1;
	    }
	    if (Vars[i].type == VAR_TYPE_INT)
	    {
		n = strtol (val, &ptr, 10);
		if (*ptr)
		{
		    log_message_level (LOG_LEVEL_ERROR, "set_var: invalid integer value: %s", val);
		    return -1;
		}
		set_int_var (&Vars[i], n);
	    }
	    else if (Vars[i].type == VAR_TYPE_STR)
       {

/* Added by winter_mute */
#if defined USE_PROTNET && (defined (USE_INVALID_CLIENTS) || defined (USE_INVALID_NICKS))
	if (!strcasecmp("protnet", var) && !check_meta_chars(val))
		return -1;
#endif
#ifdef USE_INVALID_CLIENTS
	if (!strcasecmp("invalid_clients", var) && !check_meta_chars(val))
		return -1;
	if (!strcasecmp("valid_clients", var) && !check_meta_chars(val))
		return -1;
#endif
#ifdef USE_INVALID_NICKS
	if (!strcmp("invalid_nicks", var) && !check_meta_chars(val))
		return -1;
#endif
		set_str_var (&Vars[i], val);
      }
       else if (Vars[i].type == VAR_TYPE_BOOL)
	    {
		if (!strcasecmp ("yes", val) || !strcasecmp ("on", val))
		    n = 1;
		else if (!strcasecmp ("no", val) || !strcasecmp ("off", val))
		    n = 0;
		else
		{
		    n = strtol (val, &ptr, 10);
		    if (*ptr)
		    {
			log_message_level (LOG_LEVEL_ERROR, "set_var: invalid boolean value: %s", val);
			return -1;
		    }
		}
		set_bool_var (&Vars[i], n);
	    }
	    else if (Vars[i].type == VAR_TYPE_LIST)
		set_list_var (&Vars[i], val);
	    else
	    {
		ASSERT (0);
	    }
	    return 0;
	}
    }
    log_message_level (LOG_LEVEL_ERROR, "set_var: unknown variable %s", var);
    return -1;
}

static char *
get_str_var (char *name)
{
    int     ac;

    for (ac = 0; ac < Vars_Size; ac++)
    {
	if (!strcasecmp (name, Vars[ac].name)
	    && Vars[ac].type == VAR_TYPE_STR)
	    return *(char **) Vars[ac].val;
    }
    return NULL;
}


static inline int get_var_flags (const char *var) {
 int    c;

    for (c = Vars_Size - 1; c >= 0; c--)
        if (!strcmp (Vars[c].name, var))
            return Vars[c].flags;
    return 0;
}


/* Read config file and set variables ------------------------------------- */

int config (int init) {
 char   buf[1024];
 int    fd;
 char  *ptr, *var;
 int    len, line = 0;
 char   path[_POSIX_PATH_MAX];

    snprintf (path, sizeof (path), "%s/" FILENAME_CONFIG, Config_Dir);
    if ((fd = open (path, O_RDONLY))) {
        log_message_level (LOG_LEVEL_DEBUG, "config: reading %s", path);
        buf[sizeof buf - 1] = 0;
        while (fake_fgets (buf, sizeof (buf) - 1, fd)) {
            line++;
            ptr = buf;
            while (isspace ((int)*ptr))
                ptr++;
            if (!*ptr || *ptr == '#')
                continue;
            len = strlen (ptr);
            while (len > 0 && isspace ((int)*(ptr + len - 1)))
                len--;
            *(ptr + len) = 0;
            var = next_arg (&ptr);
            if (ptr) {
                if (set_var (var, ptr, init) != 0) {
                    log_message_level (LOG_LEVEL_ERROR, "config: error in %s, line %d, variable", path, line, var);
                }
            } else if (!(get_var_flags (var) & CF_OPTVAL)) {
                log_message_level (LOG_LEVEL_ERROR, "config: error in %s, line %d: missing value for variable %s", path, line, var);
            }
        }
        close (fd);
    }
    else if (errno != ENOENT) {
        logerr (FILENAME_CONFIG, path);
        return -1;
    }
    if (init) {
	    config_user_level (get_str_var ("usermode"));
    }
    return 0;
}


static void
query_var (CONNECTION * con, struct config *v)
{
    if (v->type == VAR_TYPE_INT)
	send_cmd (con, MSG_SERVER_NOSUCH, "%s = %d", v->name,
		  *(int *) v->val);
    else if (v->type == VAR_TYPE_BOOL)
    {
	send_cmd (con, MSG_SERVER_NOSUCH, "%s = %s", v->name,
		  (Server_Flags & v->val) ? "on" : "off");
    }
    else if (v->type == VAR_TYPE_LIST)
    {
	char    buf[1024];
	LIST   *tmpList = 0;

	buf[0] = 0;
	for (tmpList = *(LIST **) v->val; tmpList; tmpList = tmpList->next)
	    snprintf (buf + strlen (buf), sizeof (buf) - strlen (buf),
		      "%s ", (char *) tmpList->data);
	send_cmd (con, MSG_SERVER_NOSUCH, "%s = %s", v->name, buf);
    }
    else
    {
	ASSERT (v->type == VAR_TYPE_STR);
	send_cmd (con, MSG_SERVER_NOSUCH, "%s = %s", v->name,
		  *(char **) v->val);
    }
}

/* 810 [ <var> [ <value> ] ] */
HANDLER (server_config)
{
    char   *av[2];
    int     ac;

    (void) tag;
    (void) len;
    ASSERT (validate_connection (con));
    /* only local users should be able to config the server.  this is still
     * problematic as currently user levels are shared across all servers
     * meaning an Elite from another server could still log in and alter the
     * settings.
     */
    CHECK_USER_CLASS ("server_config");

    if (!option (ON_REMOTE_CONFIG))
    {
	send_cmd (con, MSG_SERVER_NOSUCH, "remote configuration is disabled");
	return;
    }
    /* allow mods+ to query the config values, only elites can set them */
    if (con->user->level < LEVEL_MODERATOR)
    {
	permission_denied (con);
	return;
    }

    ac = split_line (av, FIELDS (av), pkt);
    if (ac == 0)
    {
	/* user requests all config variables */
	for (ac = 0; ac < Vars_Size; ac++)
	{
	    if (!(Vars[ac].flags & CF_HIDDEN))
		query_var (con, &Vars[ac]);
	}
    }
    else if (ac == 1)
    {
	/* user requests the value of a specific variable */
	for (ac = 0; ac < Vars_Size; ac++)
	    if (!strcasecmp (av[0], Vars[ac].name))
	    {
		if (Vars[ac].flags & CF_HIDDEN)
		    break;	/* hide this variable */
		query_var (con, &Vars[ac]);
		return;
	    }
	send_cmd (con, MSG_SERVER_NOSUCH, "no such variable %s", pkt);
    }
    else
    {
	if (con->user->level < LEVEL_ELITE)
	{
	    permission_denied (con);
	    return;
	}
   
/* Added by winter_mute */
#ifdef USE_PROTNET
      if (!strcasecmp(av[0], "protnet"))
      {  send_cmd (con, MSG_SERVER_NOSUCH, "protnet is only hashable");
         return;
      }
#endif
/* Added by winter_mute */
#ifdef USE_INVALID_CLIENTS
      if (!strcasecmp(av[0], "invalid_clients"))
      {  send_cmd (con, MSG_SERVER_NOSUCH, "invalid_clients is only hashable");
         return;
      }
      if (!strcasecmp(av[0], "valid_clients"))
      {  send_cmd (con, MSG_SERVER_NOSUCH, "valid_clients is only hashable");
         return;
      }
      if (!strcasecmp(av[0], "set_server_nicks"))
      {  send_cmd (con, MSG_SERVER_NOSUCH, "set_server_nicks is only hashable");
         return;
      }
#endif
/* Added by winter_mute */
#ifdef USE_INVALID_NICKS
      if (!strcasecmp(av[0], "invalid_nicks"))
      {  send_cmd (con, MSG_SERVER_NOSUCH, "invalid_nicks is only hashable");
         return;
      }
      if (!strcasecmp(av[0], "set_server_nicks"))
      {  send_cmd (con, MSG_SERVER_NOSUCH, "set_server_nicks is only hashable");
         return;
      }
#endif

	/* user changes the value of a specific variable */
	if (set_var (av[0], av[1], 0) != 0)
	{
	    send_cmd (con, MSG_SERVER_NOSUCH, "error setting variable %s",
		      av[0]);
	}
	else
	    notify_mods (CHANGELOG_MODE, "%s set %s to %s",
			 con->user->nick, av[0], av[1]);
    }
}

void
free_config (void)
{
    int     i;

    for (i = 0; i < Vars_Size; i++)
	if (Vars[i].type == VAR_TYPE_STR && *(char **) Vars[i].val) {
        FREE (*(char **) Vars[i].val);
    } else if (Vars[i].type == VAR_TYPE_LIST)
	    list_free (*(LIST **) Vars[i].val, free_pointer);
}

/* load the default settings of the server */
void
config_defaults (void)
{
    int     i;

    for (i = 0; i < Vars_Size; i++)
    {
	if (Vars[i].def)
	{
	    if (Vars[i].type == VAR_TYPE_STR)
		set_str_var (&Vars[i], (char *) Vars[i].def);
	    else if (Vars[i].type == VAR_TYPE_INT)
		set_int_var (&Vars[i], Vars[i].def);
	    else if (Vars[i].type == VAR_TYPE_LIST)
		set_list_var (&Vars[i], (char *) Vars[i].def);
	    else if (Vars[i].type == VAR_TYPE_BOOL)
		set_bool_var (&Vars[i], Vars[i].def);
#if DEBUG
	    else
		ASSERT (0);
#endif
	}
    }
}

/* 800 [ :<user> ] <var>
   reset `var' to its default value */
HANDLER (server_reconfig)
{
    int     i;

    (void) tag;
    (void) len;
    ASSERT (validate_connection (con));
    CHECK_USER_CLASS ("server_reconfig");
    ASSERT (validate_user (con->user));
    if (con->user->level < LEVEL_ELITE)
    {
	permission_denied (con);
	return;
    }
    if (!option (ON_REMOTE_CONFIG))
    {
	send_cmd (con, MSG_SERVER_NOSUCH, "remote configuration is disabled");
	return;
    }

    for (i = 0; i < Vars_Size; i++)
	if (!strcmp (pkt, Vars[i].name))
	{
	    if (!(Vars[i].flags & CF_ONCE))
	    {
		send_cmd (con, MSG_SERVER_NOSUCH,
			  "reconfig failed: %s may not be changed",
			  Vars[i].name);
	    }
	    else if (Vars[i].def)
	    {
		if (Vars[i].type == VAR_TYPE_STR)
		    set_str_var (&Vars[i], (char *) Vars[i].def);
		else if (Vars[i].type == VAR_TYPE_INT)
		    set_int_var (&Vars[i], Vars[i].def);
		else if (Vars[i].type == VAR_TYPE_BOOL)
		    set_bool_var (&Vars[i], Vars[i].def);
		else if (Vars[i].type == VAR_TYPE_BOOL)
		    set_list_var (&Vars[i], (char *) Vars[i].def);
		notify_mods (CHANGELOG_MODE, "%s reset %s",
			     con->user->nick, Vars[i].name);
	    }
	    else
	    {
		send_cmd (con, MSG_SERVER_NOSUCH, "no default value for %s",
			  pkt);
	    }
	    return;
	}
    send_cmd (con, MSG_SERVER_NOSUCH, "no such variable %s", pkt);
}

static void
nick_check (server_auth_t * auth, void *unused)
{
    USER   *user;
    USERDB *userdb;

    (void) unused;
    if (auth->alias)
    {
	user = hash_lookup (Users, auth->alias);
	if (user)
	{
	    kill_user_internal (0, user, Server_Name, 0,
				"you may not use this nickname");
	}
	/* if the nick is registered, drop it now */
	userdb = hash_lookup (User_Db, auth->alias);
	if (userdb)
	{
	    log_message_level (LOG_LEVEL_SECURITY, "nick_check: nuking account %s", userdb->nick);
	    hash_remove (User_Db, userdb->nick);
	}
    }
}

/* Added by winter_mute */
#ifdef USE_INVALID_NICKS
static void
my_nick_check (USER *user, void *unused)
{  
   USERDB *userdb;
   (void) unused;
   
   if (glob_match(Invalid_Nicks, user->nick) && ISUSER (user->con) )
   {  
      kill_user_internal (0, user, Server_Name, 0,
				"you may not use this nickname");
      
      log_message_level (LOG_LEVEL_DEBUG, "config: my_nick_check: nick: %s", user->nick);
      /* if the nick is registered, drop it now */
	   userdb = hash_lookup (User_Db, user->nick);
      if (userdb)
      {
         log_message_level (LOG_LEVEL_SECURITY, "config: nick_check: nuking account %s", userdb->nick);
         hash_remove (User_Db, userdb->nick);
      }
   }
}
#endif

/* Added by winter_mute */
#ifdef USE_INVALID_CLIENTS

static void my_client_check (USER *user, void *unused) {  
   (void) unused;
    if (!ISUSER (user->con)) return;
    if (glob_match(Invalid_Clients, user->clientinfo) ||
        (strlen (Valid_Clients) && !glob_match (Valid_Clients, user->clientinfo))) {
        kill_user_internal (0, user, Server_Name, 0,
            "your client, %s, is not welcome here.", user->clientinfo);
    }
}
#endif

/*
#if defined (USE_INVALID_CLIENTS) || defined (USER_INVALID_NICKS)
static void
set_server_check (USER *user, void *unused)
{  
   (void) unused;
   
   if (glob_match(Set_Server_Nicks, user->clientinfo) && ISUSER (user->con) )
     return 0;
   return 1;
}
#endif
*/

void
reload_config (void)
{
    log_message_level (LOG_LEVEL_SERVER, "reload_config: reloading configuration files");
    config (0);
    /* since the motd is stored in memory, reread it */
    motd_close ();
    motd_init ();
#ifndef ROUTING_ONLY
    /* reread filter file */
    load_filter ();
    load_block ();
#endif
    load_server_auth ();

    /* since the servers file may have changed, ensure that there is
     * no nickname that matches an alias for a server.
     */
    list_foreach (Server_Auth, (list_callback_t) nick_check, 0);
    
    /* Added by winter_mute */
    /* remove any nicks that are now not valid */
#ifdef USE_INVALID_NICKS
      hash_foreach (Users, (list_callback_t) my_nick_check, 0);
#endif
    /* remove any clients that are now not valid */
#ifdef USE_INVALID_CLIENTS
    hash_foreach (Users, (list_callback_t) my_client_check, 0);
#endif
/*
#if defined (USE_INVALID_CLIENTS) || defined (USE_INVALID_NICKS)
    hash_foreach (Users, (list_callback_t) set_server_check, 0);
#endif
*/
}

/* 10117 [ :user ] [server]
 * reload configuration file
 */
HANDLER (rehash)
{
    USER   *sender;

    (void) len;
    if (pop_user (con, &pkt, &sender))
	return;
    if (sender->level < LEVEL_ELITE)
    {
	permission_denied (con);
	return;
    }
    notify_mods (SERVERLOG_MODE, "%s reloaded configuration on %s",
		 sender->nick, pkt && *pkt ? pkt : Server_Name);
    if (!pkt || !*pkt || !strcasecmp (Server_Name, pkt))
	reload_config ();

    /* pass the message even if this is the server we are reloading so that
     * everyone sees the message
     */
    pass_message_args (con, tag, ":%s %s", sender->nick,
		       pkt && *pkt ? pkt : Server_Name);
}
