pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file conf.c
 *
 * \brief PennMUSH runtime configuration.
 *
 * configuration adjustment. Some of the ideas and bits and pieces of the
 * code here are based on TinyMUSH 2.0.
 *
 */

#include "copyrite.h"
#include "config.h"

#include <stdio.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "conf.h"
#include "externs.h"
#include "pueblo.h"
#include "mushdb.h"
#include "parse.h"
#include "command.h"
#include "flags.h"
#include "log.h"
#include "dbdefs.h"
#include "game.h"
#include "attrib.h"
#include "help.h"
#include "function.h"
#include "confmagic.h"

time_t mudtime;			/**< game time, in seconds */

static void show_compile_options(dbref player);
static char *config_list_helper(dbref player, PENNCONF * cp, int lc);
static char *config_list_helper2(dbref player, PENNCONF * cp, int lc);

OPTTAB options;		/**< The table of configuration options */
HASHTAB local_options;	/**< Hash table for local config options */

int config_set(const char *opt, char *val, int source, int restrictions);
void conf_default_set(void);

/** Table of all runtime configuration options. */
PENNCONF conftable[] = {
  {"input_database", cf_str, options.input_db, sizeof options.input_db, 0,
   "files"}
  ,
  {"output_database", cf_str, options.output_db, sizeof options.output_db, 0,
   "files"}
  ,
  {"crash_database", cf_str, options.crash_db, sizeof options.crash_db, 0,
   "files"}
  ,
  {"mail_database", cf_str, options.mail_db, sizeof options.mail_db, 0, "files"}
  ,
  {"chat_database", cf_str, options.chatdb, sizeof options.chatdb, 0, "files"}
  ,
  {"compress_suffix", cf_str, options.compresssuff, sizeof options.compresssuff,
   0,
   "files"}
  ,
  {"compress_program", cf_str, options.compressprog,
   sizeof options.compressprog, 0,
   "files"}
  ,
  {"uncompress_program", cf_str, options.uncompressprog,
   sizeof options.uncompressprog, 0,
   "files"}
  ,
  {"access_file", cf_str, options.access_file, sizeof options.access_file, 0,
   "files"}
  ,
  {"names_file", cf_str, options.names_file, sizeof options.access_file, 0,
   "files"}
  ,
  {"connect_file", cf_str, options.connect_file[0],
   sizeof options.connect_file[0], 0, "messages"}
  ,
  {"motd_file", cf_str, options.motd_file[0], sizeof options.motd_file[0], 0,
   "messages"}
  ,
  {"wizmotd_file", cf_str, options.wizmotd_file[0],
   sizeof options.wizmotd_file[0], 0, "messages"}
  ,
  {"newuser_file", cf_str, options.newuser_file[0],
   sizeof options.newuser_file[0], 0, "messages"}
  ,

  {"register_create_file", cf_str, options.register_file[0],
   sizeof options.register_file[0],
   0, "messages"}
  ,
  {"quit_file", cf_str, options.quit_file[0], sizeof options.quit_file[0], 0,
   "messages"}
  ,
  {"down_file", cf_str, options.down_file[0], sizeof options.down_file[0], 0,
   "messages"}
  ,
  {"full_file", cf_str, options.full_file[0], sizeof options.full_file[0], 0,
   "messages"}
  ,
  {"guest_file", cf_str, options.guest_file[0], sizeof options.guest_file[0], 0,
   "messages"}
  ,

  {"connect_html_file", cf_str, options.connect_file[1],
   sizeof options.connect_file[1], 0,
   "messages"}
  ,
  {"motd_html_file", cf_str, options.motd_file[1],
   sizeof options.connect_file[1], 0,
   "messages"}
  ,
  {"wizmotd_html_file", cf_str, options.wizmotd_file[1],
   sizeof options.wizmotd_file[1], 0,
   "messages"}
  ,
  {"newuser_html_file", cf_str, options.newuser_file[1],
   sizeof options.newuser_file[1], 0,
   "messages"}
  ,
  {"register_create_html_file", cf_str, options.register_file[1],
   sizeof options.register_file[1], 0, "messages"}
  ,
  {"quit_html_file", cf_str, options.quit_file[1], sizeof options.quit_file[1],
   0,
   "messages"}
  ,
  {"down_html_file", cf_str, options.down_file[1], sizeof options.down_file[1],
   0,
   "messages"}
  ,
  {"full_html_file", cf_str, options.full_file[1], sizeof options.full_file[1],
   0,
   "messages"}
  ,
  {"guest_html_file", cf_str, options.guest_file[1],
   sizeof options.guest_file[1], 0,
   "messages"}
  ,

  {"player_start", cf_dbref, &options.player_start, 100000, 0, "db"}
  ,
  {"master_room", cf_dbref, &options.master_room, 100000, 0, "db"}
  ,
  {"base_room", cf_dbref, &options.base_room, 100000, 0, "db"}
  ,
  {"default_home", cf_dbref, &options.default_home, 100000, 0, "db"}
  ,
  {"exits_connect_rooms", cf_bool, &options.exits_connect_rooms, 2, 0, "db"}
  ,
  {"zone_control_zmp_only", cf_bool, &options.zone_control, 2, 0, "db"}
  ,
  {"ancestor_room", cf_int, &options.ancestor_room, 100000, 0, "db"}
  ,
  {"ancestor_exit", cf_int, &options.ancestor_exit, 100000, 0, "db"}
  ,
  {"ancestor_thing", cf_int, &options.ancestor_thing, 100000, 0, "db"}
  ,
  {"ancestor_player", cf_int, &options.ancestor_player, 100000, 0, "db"}
  ,
  {"mud_name", cf_str, options.mud_name, 128, 0, "net"}
  ,
  {"ip_addr", cf_str, options.ip_addr, 64, 0, "net"}
  ,
  {"ssl_ip_addr", cf_str, options.ssl_ip_addr, 64, 0, "net"}
  ,
  {"port", cf_int, &options.port, 32000, 0, "net"}
  ,
  {"ssl_port", cf_int, &options.ssl_port, 32000, 0, "net"}
  ,
  {"use_dns", cf_bool, &options.use_dns, 2, 0, "net"}
  ,
  {"use_ident", cf_bool, &options.use_ident, 2, 0, "net"}
  ,
  {"ident_timeout", cf_time, &options.ident_timeout, 60, 0, "net"}
  ,
  {"logins", cf_bool, &options.login_allow, 2, 0, "net"}
  ,
  {"player_creation", cf_bool, &options.create_allow, 2, 0, "net"}
  ,
  {"guests", cf_bool, &options.guest_allow, 2, 0, "net"}
  ,
  {"pueblo", cf_bool, &options.support_pueblo, 2, 0, "net"}
  ,
#ifdef HAS_MYSQL
  {"sql_platform", cf_str, options.sql_platform, sizeof options.sql_platform, 0,
   "net"}
  ,
  {"sql_host", cf_str, options.sql_host, sizeof options.sql_host, 0, "net"}
  ,
  {"sql_username", cf_str, options.sql_username, sizeof options.sql_username, 0,
   NULL}
  ,
  {"sql_password", cf_str, options.sql_password, sizeof options.sql_password, 0,
   NULL}
  ,
  {"sql_database", cf_str, options.sql_database, sizeof options.sql_database, 0,
   NULL}
  ,
#endif

  {"forking_dump", cf_bool, &options.forking_dump, 2, 0, "dump"}
  ,
  {"dump_message", cf_str, options.dump_message, sizeof options.dump_message, 0,
   "dump"}
  ,
  {"dump_complete", cf_str, options.dump_complete, sizeof options.dump_complete,
   0, "dump"}
  ,
  {"dump_warning_1min", cf_str, options.dump_warning_1min,
   sizeof options.dump_warning_1min,
   0, "dump"}
  ,
  {"dump_warning_5min", cf_str, options.dump_warning_5min,
   sizeof options.dump_warning_5min, 0,
   "dump"}
  ,
  {"dump_interval", cf_time, &options.dump_interval, 100000, 0, "dump"}
  ,
  {"warn_interval", cf_time, &options.warn_interval, 32000, 0, "dump"}
  ,
  {"purge_interval", cf_time, &options.purge_interval, 10000, 0, "dump"}
  ,
  {"dbck_interval", cf_time, &options.dbck_interval, 10000, 0, "dump"}
  ,

  {"money_singular", cf_str, options.money_singular,
   sizeof options.money_singular, 0,
   "cosmetic"}
  ,
  {"money_plural", cf_str, options.money_plural, sizeof options.money_plural, 0,
   "cosmetic"}
  ,
  {"player_name_spaces", cf_bool, &options.player_name_spaces, 2, 0,
   "cosmetic"}
  ,
  {"ansi_names", cf_bool, &options.ansi_names, 2, 0, "cosmetic"}
  ,
  {"only_ascii_in_names", cf_bool, &options.ascii_names, 2, 0, "cosmetic"}
  ,
  {"float_precision", cf_int, &options.float_precision, 10000, 0, "cosmetic"}
  ,
  {"comma_exit_list", cf_bool, &options.comma_exit_list, 2, 0, "cosmetic"}
  ,
  {"count_all", cf_bool, &options.count_all, 2, 0, "cosmetic"}
  ,
  {"blind_page", cf_bool, &options.blind_page, 2, 0, "cosmetic"}
  ,
  {"page_aliases", cf_bool, &options.page_aliases, 2, 0, "cosmetic"}
  ,
  {"flags_on_examine", cf_bool, &options.flags_on_examine, 2, 0, "cosmetic"}
  ,
  {"ex_public_attribs", cf_bool, &options.ex_public_attribs, 2, 0,
   "cosmetic"}
  ,
  {"wizwall_prefix", cf_str, options.wizwall_prefix,
   sizeof options.wizwall_prefix, 0,
   "cosmetic"}
  ,
  {"rwall_prefix", cf_str, options.rwall_prefix, sizeof options.rwall_prefix, 0,
   "cosmetic"}
  ,
  {"wall_prefix", cf_str, options.wall_prefix, sizeof options.wall_prefix, 0,
   "cosmetic"}
  ,
  {"announce_connects", cf_bool, &options.announce_connects, 2, 0, "cosmetic"}
  ,
  {"chat_strip_quote", cf_bool, &options.chat_strip_quote, 2, 0, "cosmetic"}
  ,
  {"newline_one_char", cf_bool, &options.newline_one_char, 2, 0, "cosmetic"}
  ,

  {"max_dbref", cf_dbref, &options.max_dbref, -1, 0, "limits"}
  ,
  {"max_attrs_per_obj", cf_int, &options.max_attrcount, 8192, 0, "limits"}
  ,
  {"max_logins", cf_int, &options.max_logins, 128, 0, "limits"}
  ,
  {"max_guests", cf_int, &options.max_guests, 128, 0, "limits"}
  ,
  {"idle_timeout", cf_time, &options.idle_timeout, 100000, 0, "limits"}
  ,
  {"unconnected_idle_timeout", cf_time, &options.unconnected_idle_timeout,
   100000, 0, "limits"}
  ,
  {"whisper_loudness", cf_int, &options.whisper_loudness, 100, 0, "limits"}
  ,
  {"starting_quota", cf_int, &options.starting_quota, 10000, 0, "limits"}
  ,
  {"starting_money", cf_int, &options.starting_money, 10000, 0, "limits"}
  ,
  {"paycheck", cf_int, &options.paycheck, 1000, 0, "limits"}
  ,
  {"guest_paycheck", cf_int, &options.guest_paycheck, 1000, 0, "limits"}
  ,
  {"max_pennies", cf_int, &options.max_pennies, 100000, 0, "limits"}
  ,
  {"max_guest_pennies", cf_int, &options.max_guest_pennies, 100000, 0,
   "limits"}
  ,
  {"max_parents", cf_int, &options.max_parents, 10000, 0, "limits"}
  ,
  {"mail_limit", cf_int, &options.mail_limit, 5000, 0, "limits"}
  ,
  {"max_depth", cf_int, &options.max_depth, 10000, 0, "limits"}
  ,
  {"player_queue_limit", cf_int, &options.player_queue_limit, 100000, 0,
   "limits"}
  ,
  {"queue_loss", cf_int, &options.queue_loss, 10000, 0, "limits"}
  ,
  {"queue_chunk", cf_int, &options.queue_chunk, 100000, 0, "limits"}
  ,
  {"active_queue_chunk", cf_int, &options.active_q_chunk, 100000, 0,
   "limits"}
  ,
  {"function_recursion_limit", cf_int, &options.func_nest_lim, 100000, 0,
   "limits"}
  ,
  {"function_invocation_limit", cf_int, &options.func_invk_lim, 100000, 0,
   "limits"}
  ,
  {"call_limit", cf_int, &options.call_lim, 1000000, 0,
   "limits"}
  ,
  {"player_name_len", cf_int, &options.player_name_len, BUFFER_LEN, 0,
   "limits"}
  ,
  {"queue_entry_cpu_time", cf_int, &options.queue_entry_cpu_time, 100000, 0,
   "limits"}
  ,
  {"max_global_fns", cf_int, &options.max_global_fns, 2000, 0, 0}
  ,
  {"use_quota", cf_bool, &options.use_quota, 2, 0, "limits"}
  ,
  {"max_channels", cf_int, &options.max_channels, 1000, 0, "chat"}
  ,
  {"max_player_chans", cf_int, &options.max_player_chans, 100, 0, "chat"}
  ,
  {"chan_cost", cf_int, &options.chan_cost, 10000, 0, "chat"}
  ,

  {"log_commands", cf_bool, &options.log_commands, 2, 0, "log"}
  ,
  {"log_forces", cf_bool, &options.log_forces, 2, 0, "log"}
  ,
  {"error_log", cf_str, options.error_log, sizeof options.error_log, 0,
   "log"}
  ,
  {"command_log", cf_str, options.command_log, sizeof options.command_log, 0,
   "log"}
  ,
  {"wizard_log", cf_str, options.wizard_log, sizeof options.wizard_log, 0,
   "log"}
  ,
  {"checkpt_log", cf_str, options.checkpt_log, sizeof options.checkpt_log, 0,
   "log"}
  ,
  {"trace_log", cf_str, options.trace_log, sizeof options.trace_log, 0,
   "log"}
  ,
  {"connect_log", cf_str, options.connect_log, sizeof options.connect_log, 0,
   "log"}
  ,

  {"player_flags", cf_flag, options.player_flags, sizeof options.player_flags,
   0, "flags"}
  ,
  {"room_flags", cf_flag, options.room_flags, sizeof options.room_flags, 0,
   "flags"}
  ,
  {"exit_flags", cf_flag, options.exit_flags, sizeof options.exit_flags, 0,
   "flags"}
  ,
  {"thing_flags", cf_flag, options.thing_flags, sizeof options.thing_flags, 0,
   "flags"}
  ,

  {"safer_ufun", cf_bool, &options.safer_ufun, 2, 0, "funcs"}
  ,
  {"function_side_effects", cf_bool, &options.function_side_effects, 2, 0,
   "funcs"}
  ,

  {"noisy_whisper", cf_bool, &options.noisy_whisper, 2, 0, "cmds"}
  ,
  {"possessive_get", cf_bool, &options.possessive_get, 2, 0, "cmds"}
  ,
  {"possessive_get_d", cf_bool, &options.possessive_get_d, 2, 0, "cmds"}
  ,
  {"link_to_object", cf_bool, &options.link_to_object, 2, 0, "cmds"}
  ,
  {"owner_queues", cf_bool, &options.owner_queues, 2, 0, "cmds"}
  ,
  {"full_invis", cf_bool, &options.full_invis, 2, 0, "cmds"}
  ,
  {"wiz_noaenter", cf_bool, &options.wiz_noaenter, 2, 0, "cmds"}
  ,
  {"really_safe", cf_bool, &options.really_safe, 2, 0, "cmds"}
  ,
  {"destroy_possessions", cf_bool, &options.destroy_possessions, 2, 0,
   "cmds"}
  ,

  {"null_eq_zero", cf_bool, &options.null_eq_zero, 2, 0, "tiny"}
  ,
  {"tiny_booleans", cf_bool, &options.tiny_booleans, 2, 0, "tiny"}
  ,
  {"tiny_trim_fun", cf_bool, &options.tiny_trim_fun, 2, 0, "tiny"}
  ,
  {"tiny_math", cf_bool, &options.tiny_math, 2, 0, "tiny"}
  ,
  {"silent_pemit", cf_bool, &options.silent_pemit, 2, 0, "tiny"}
  ,

  {"adestroy", cf_bool, &options.adestroy, 2, 0, "attribs"}
  ,
  {"amail", cf_bool, &options.amail, 2, 0, "attribs"}
  ,
  {"player_listen", cf_bool, &options.player_listen, 2, 0, "attribs"}
  ,
  {"player_ahear", cf_bool, &options.player_ahear, 2, 0, "attribs"}
  ,
  {"startups", cf_bool, &options.startups, 2, 0, "attribs"}
  ,
  {"read_remote_desc", cf_bool, &options.read_remote_desc, 2, 0, "attribs"}
  ,
  {"room_connects", cf_bool, &options.room_connects, 2, 0, "attribs"}
  ,
  {"reverse_shs", cf_bool, &options.reverse_shs, 2, 0, "attribs"}
  ,
  {"empty_attrs", cf_bool, &options.empty_attrs, 2, 0, "attribs"}
  ,
  {"object_cost", cf_int, &options.object_cost, 10000, 0, "costs"}
  ,
  {"exit_cost", cf_int, &options.exit_cost, 10000, 0, "costs"}
  ,
  {"link_cost", cf_int, &options.link_cost, 10000, 0, "costs"}
  ,
  {"room_cost", cf_int, &options.room_cost, 10000, 0, "costs"}
  ,
  {"queue_cost", cf_int, &options.queue_cost, 10000, 0, "costs"}
  ,
  {"quota_cost", cf_int, &options.quota_cost, 10000, 0, "costs"}
  ,
  {"find_cost", cf_int, &options.find_cost, 10000, 0, "costs"}
  ,
  {"page_cost", cf_int, &options.page_cost, 10000, 0, "costs"}
  ,
  {"kill_default_cost", cf_int, &options.kill_default_cost, 10000, 0,
   "costs"}
  ,
  {"kill_min_cost", cf_int, &options.kill_min_cost, 10000, 0, "costs"}
  ,
  {"kill_bonus", cf_int, &options.kill_bonus, 100, 0, "costs"}
  ,

  {"log_wipe_passwd", cf_str, options.log_wipe_passwd,
   sizeof options.log_wipe_passwd, 0,
   NULL}
  ,

  {"chunk_swap_file", cf_str, options.chunk_swap_file,
   sizeof options.chunk_swap_file, 0, "files"}
  ,
  {"chunk_cache_memory", cf_int, &options.chunk_cache_memory,
   1000000000, 65510 * 2, "files"}
  ,
  {"chunk_migrate", cf_int, &options.chunk_migrate_amount, 100000, 0,
   "limits"}
  ,
#ifdef HAS_OPENSSL
  {"ssl_private_key_file", cf_str, options.ssl_private_key_file,
   sizeof options.ssl_private_key_file, 0, "files"}
  ,
  {"ssl_ca_file", cf_str, options.ssl_ca_file,
   sizeof options.ssl_ca_file, 0, "files"}
  ,
  {"ssl_require_client_cert", cf_bool, &options.ssl_require_client_cert,
   2, 0, "net"}
  ,
#endif
  {"mem_check", cf_bool, &options.mem_check, 2, 0, "log"}
  ,

  {NULL, NULL, NULL, 0, 0, NULL}
};

/** A runtime configuration group.
 * This struction represents the name and information about a group
 * of runtime configuration directives. Groups are used to organize
 * the display of configuration options.
 */
typedef struct confgroupparm {
  const char *name;		/**< name of group */
  const char *desc;		/**< description of group */
  int viewperms;		/**< who can view this group */
} PENNCONFGROUP;

/** The table of all configuration groups. */
PENNCONFGROUP confgroups[] = {
  {"attribs", "Options affecting attributes", 0},
  {"chat", "Chat system options", 0},
  {"cmds", "Options affecting command behavior", 0},
  {"compile", "Compile-time options", 0},
  {"cosmetic", "Cosmetic options", 0},
  {"costs", "Costs", 0},
  {"db", "Database options", 0},
  {"dump", "Options affecting dumps and other periodic processes", 0},
  {"files", "Files used by the MUSH", CGP_GOD},
  {"flags", "Default flags for new objects", 0},
  {"funcs", "Options affecting function behavior", 0},
  {"limits", "Limits and other constants", 0},
  {"log", "Logging options", 0},
  {"messages", "Message files sent by the MUSH", CGP_GOD},
  {"net", "Networking and connection-related options", 0},
  {"tiny", "TinyMUSH compatibility options", 0},
  {NULL, NULL, 0}
};

/** Returns a pointer to a newly allocated PENNCONF object.
 * \return pointer to newly allocated PENNCONF object.
 */
PENNCONF *
new_config(void)
{
  return ((PENNCONF *) mush_malloc(sizeof(PENNCONF), "config"));
}

/** Add a new local runtime configuration parameter to local_options.
 * This function will not override an existing local configuration
 * option.
 * \param name name of the configuration option.
 * \param handler cf_* function to handle the option.
 * \param loc address to store the value of the option.
 * \param max maximum value allowed for the option.
 * \param group name of the option group the option should display with.
 * \return pointer to configuration structure or NULL for failure.
 */
PENNCONF *
add_config(const char *name, config_func handler, void *loc, int max,
	   const char *group)
{
  PENNCONF *cnf;
  if ((cnf = get_config(name)))
    return cnf;
  if ((cnf = new_config()) == NULL)
    return NULL;
  cnf->name = mush_strdup(strupper(name), "config name");
  cnf->handler = handler;
  cnf->loc = loc;
  cnf->max = max;
  cnf->overridden = 0;
  cnf->group = group;
  hashadd(name, (void *) cnf, &local_options);
  return cnf;
}

/** Return a local runtime configuration parameter by name.
 * This function returns a point to a configuration structure (PENNCONF *)
 * if one exists in the local runtime options that matches the given
 * name. Only local_options is searched.
 * \param name name of the configuration option.
 * \return pointer to configuration structure or NULL for failure.
 */
PENNCONF *
get_config(const char *name)
{
  return ((PENNCONF *) hashfind(name, &local_options));
}

/** Parse a boolean configuration option.
 * \param opt name of the configuration option.
 * \param val value of the option.
 * \param loc address to store the value.
 * \param maxval (unused).
 * \param source 0 if read from config file; 1 if from command.
 * \retval 0 failure (unable to parse val).
 * \retval 1 success.
 */
int
cf_bool(const char *opt, const char *val, void *loc,
	int maxval __attribute__ ((__unused__)), int source)
{
  /* enter boolean parameter */

  if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
      !strcasecmp(val, "1"))
    *((int *) loc) = 1;
  else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
	   !strcasecmp(val, "0"))
    *((int *) loc) = 0;
  else {
    if (source == 0) {
      do_rawlog(LT_ERR, T("CONFIG: option %s value %s invalid.\n"), opt, val);
    }
    return 0;
  }
  return 1;
}


/** Parse a string configuration option.
 * \param opt name of the configuration option.
 * \param val value of the option.
 * \param loc address to store the value.
 * \param maxval maximum length of value string.
 * \param source 0 if read from config file; 1 if from command.
 * \retval 0 failure (unable to parse val).
 * \retval 1 success.
 */
int
cf_str(const char *opt, const char *val, void *loc, int maxval, int source)
{
  /* enter string parameter */
  size_t len = strlen(val);

  /* truncate if necessary */
  if (len >= (size_t) maxval) {
    if (source == 0) {
      do_rawlog(LT_ERR, T("CONFIG: option %s value truncated\n"), opt);
    }
    len = maxval - 1;
  }
  memcpy(loc, val, len);
  *((char *) loc + len) = '\0';
  return 1;
}

/** Parse a dbref configuration option.
 * \param opt name of the configuration option.
 * \param val value of the option.
 * \param loc address to store the value.
 * \param maxval maximum dbref.
 * \param source 0 if read from config file; 1 if from command.
 * \retval 0 failure (unable to parse val).
 * \retval 1 success.
 */
int
cf_dbref(const char *opt, const char *val, void *loc, int maxval, int source)
{
  /* enter dbref or integer parameter */

  int n;
  size_t offset = 0;

  if (val && val[0] == '#')
    offset = 1;

  n = parse_integer(val + offset);

  /* enforce limits */
  if ((maxval >= 0) && (n > maxval)) {
    n = maxval;
    if (source == 0) {
      do_rawlog(LT_ERR, T("CONFIG: option %s value limited to #%d\n"), opt,
		maxval);
    }
  }
  if (source && (!GoodObject(n) || IsGarbage(n))) {
    do_rawlog(LT_ERR,
	      T("CONFIG: attempt to set option %s to a bad dbref (#%d)"),
	      opt, n);
    return 0;
  }
  *((dbref *) loc) = n;
  return 1;
}

/** Parse an integer configuration option.
 * \param opt name of the configuration option.
 * \param val value of the option.
 * \param loc address to store the value.
 * \param maxval maximum value.
 * \param source 0 if read from config file; 1 if from command.
 * \retval 0 failure (unable to parse val).
 * \retval 1 success.
 */
int
cf_int(const char *opt, const char *val, void *loc, int maxval, int source)
{
  /* enter integer parameter */

  int n;
  int offset = 0;

  if (val && val[0] == '#')
    offset = 1;
  n = parse_integer(val + offset);

  /* enforce limits */
  if ((maxval >= 0) && (n > maxval)) {
    n = maxval;
    if (source == 0) {
      do_rawlog(LT_ERR, T("CONFIG: option %s value limited to %d\n"), opt,
		maxval);
    }
  }
  *((int *) loc) = n;
  return 1;
}


/** Parse an time configuration option with a default unit of seconds 
 * \param opt name of the configuration option.
 * \param val value of the option.
 * \param loc address to store the value.
 * \param maxval maximum value.
 * \param source 0 if read from config file; 1 if from command.
 * \retval 0 failure (unable to parse val).
 * \retval 1 success.
 */
int
cf_time(const char *opt, const char *val, void *loc, int maxval, int source)
{
  /* enter time parameter */
  char *end = NULL;
  int in_minutes = 0;
  int n, secs = 0;


  if (strstr(opt, "idle"))
    in_minutes = 1;

  while (val && *val) {
    n = strtol(val, &end, 10);

    switch (*end) {
    case '\0':
      if (in_minutes)
	secs += n * 60;
      else
	secs += n;
      goto done;		/* Sigh. What I wouldn't give for named loops in C */
    case 's':
    case 'S':
      secs += n;
      break;
    case 'm':
    case 'M':
      secs += n * 60;
      break;
    case 'h':
    case 'H':
      secs += n * 3600;
      break;
    default:
      if (source == 0)
	do_rawlog(LT_ERR, T("CONFIG: Unknown time interval in option %s"), opt);
      return 0;
    }
    val = end + 1;
  }
done:
  /* enforce limits */
  if ((maxval >= 0) && (secs > maxval)) {
    secs = maxval;
    if (source == 0) {
      do_rawlog(LT_ERR, T("CONFIG: option %s value limited to %d\n"), opt,
		maxval);
    }
  }
  *((int *) loc) = secs;
  return 1;
}

/** Parse a flag configuration option.
 * This is just like parsing a string option, but collects multiple
 * string options with the same name into a single value.
 * \param opt name of the configuration option.
 * \param val value of the option.
 * \param loc address to store the value.
 * \param maxval maximum length of value.
 * \param source 0 if read from config file; 1 if from command.
 * \retval 0 failure (unable to parse val).
 * \retval 1 success.
 */
int
cf_flag(const char *opt, const char *val, void *loc, int maxval, int source)
{
  size_t len = strlen(val);
  size_t total = strlen((char *) loc);

  /* truncate if necessary */
  if (len + total + 1 >= (size_t) maxval) {
    len = maxval - total - 1;
    if (len <= 0) {
      if (source == 0)
	do_rawlog(LT_ERR, T("CONFIG: option %s value overflow\n"), opt);
      return 0;
    }
    if (source == 0)
      do_rawlog(LT_ERR, T("CONFIG: option %s value truncated\n"), opt);
  }
  sprintf((char *) loc, "%s %s", (char *) loc, val);
  return 1;
}

/** Set a configuration option.
 * This function sets a runtime configuration option. During the load
 * of the configuration file, it gets run twice - once to set the
 * main set of options and once again to set restrictions and aliases
 * that require having the flag table available.
 * \param opt name of the option.
 * \param val value to set the option to.
 * \param source 0 if being set from mush.cnf, 1 from softcode.
 * \param restrictions 1 if we're setting restriction options, 0 for others.
 * \retval 1 success.
 * \retval 0 failure.
 */
int
config_set(const char *opt, char *val, int source, int restrictions)
{
  PENNCONF *cp;
  char *p;

  if (!val)
    return 0;			/* NULL val is no good, but "" is ok */

  /* Was this "restrict_command <command> <restriction>"? If so, do it */
  if (!strcasecmp(opt, "restrict_command")) {
    if (!restrictions)
      return 0;
    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
    if (*p) {
      *p++ = '\0';
      if (!restrict_command(val, p)) {
	if (source == 0) {
	  do_rawlog(LT_ERR,
		    T("CONFIG: Invalid command or restriction for %s.\n"), val);
	}
	return 0;
      }
    } else {
      if (source == 0) {
	do_rawlog(LT_ERR,
		  T
		  ("CONFIG: restrict_command %s requires a restriction value.\n"),
		  val);
      }
      return 0;
    }
    return 1;
  } else if (!strcasecmp(opt, "restrict_function")) {
    if (!restrictions)
      return 0;
    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
    if (*p) {
      *p++ = '\0';
      if (!restrict_function(val, p)) {
	if (source == 0) {
	  do_rawlog(LT_ERR,
		    T("CONFIG: Invalid function or restriction for %s.\n"),
		    val);
	}
	return 0;
      }
    } else {
      if (source == 0) {
	do_rawlog(LT_ERR,
		  T
		  ("CONFIG: restrict_function %s requires a restriction value.\n"),
		  val);
      }
      return 0;
    }
    return 1;
  } else if (!strcasecmp(opt, "reserve_alias")) {
    if (!restrictions)
      return 0;
    reserve_alias(val);
    return 1;
  } else if (!strcasecmp(opt, "command_alias")) {
    if (!restrictions)
      return 0;
    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
    if (*p) {
      *p++ = '\0';
      if (!alias_command(val, p)) {
	if (source == 0) {
	  do_rawlog(LT_ERR, T("CONFIG: Couldn't alias %s to %s.\n"), p, val);
	}
	return 0;
      }
    } else {
      if (source == 0) {
	do_rawlog(LT_ERR,
		  T("CONFIG: command_alias %s requires an alias.\n"), val);
      }
      return 0;
    }
    return 1;
  } else if (!strcasecmp(opt, "attribute_alias")) {
    if (!restrictions)
      return 0;
    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
    if (*p) {
      *p++ = '\0';
      if (!alias_attribute(val, p)) {
	if (source == 0) {
	  do_rawlog(LT_ERR, T("CONFIG: Couldn't alias %s to %s.\n"), p, val);
	}
	return 0;
      }
    } else {
      if (source == 0) {
	do_rawlog(LT_ERR,
		  T("CONFIG: attribute_alias %s requires an alias.\n"), val);
      }
      return 0;
    }
    return 1;
  } else if (!strcasecmp(opt, "function_alias")) {
    if (!restrictions)
      return 0;
    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
    if (*p) {
      *p++ = '\0';
      if (!alias_function(val, p)) {
	if (source == 0) {
	  do_rawlog(LT_ERR, T("CONFIG: Couldn't alias %s to %s.\n"), p, val);
	}
	return 0;
      }
    } else {
      if (source == 0) {
	do_rawlog(LT_ERR,
		  T("CONFIG: function_alias %s requires an alias.\n"), val);
      }
      return 0;
    }
    return 1;
  } else if (!strcasecmp(opt, "help_command")
	     || !strcasecmp(opt, "ahelp_command")) {
    char *comm, *file;
    int admin = !strcasecmp(opt, "ahelp_command");
    if (!restrictions)
      return 0;
    /* Add a new help-like command */
    if (source == 1)
      return 0;
    if (!val || !*val) {
      do_rawlog(LT_ERR,
		T
		("CONFIG: help_command requires a command name and file name.\n"));
      return 0;
    }
    comm = val;
    for (file = val; *file && !isspace((unsigned char) *file); file++) ;
    if (*file) {
      *file++ = '\0';
      add_help_file(comm, file, admin);
      return 1;
    } else {
      do_rawlog(LT_ERR,
		T
		("CONFIG: help_command requires a command name and file name.\n"));
      return 0;
    }
  } else if (restrictions) {
    return 0;
  }
  /* search conf table for the option; if found, add it, if not found,
   * complain about it. Forbid use of @config to set options without
   * groups (log_wipe_passwd), or the file and message groups (@config/set
   * output_data=../../.bashrc? Ouch.)  */
  for (cp = conftable; cp->name; cp++) {
    int i = 0;
    if ((!source || (cp->group && strcmp(cp->group, "files") != 0
		     && strcmp(cp->group, "messages") != 0))
	&& !strcasecmp(cp->name, opt)) {
      i = cp->handler(opt, val, cp->loc, cp->max, source);
      if (i)
	cp->overridden = 1;
      return i;
    }
  }
  for (cp = (PENNCONF *) hash_firstentry(&local_options); cp;
       cp = (PENNCONF *) hash_nextentry(&local_options)) {
    int i = 0;
    if ((!source || (cp->group && strcmp(cp->group, "files") != 0
		     && strcmp(cp->group, "messages") != 0))
	&& !strcasecmp(cp->name, opt)) {
      i = cp->handler(opt, val, cp->loc, cp->max, source);
      if (i)
	cp->overridden = 1;
      return i;
    }
  }

  if (source == 0) {
    do_rawlog(LT_ERR, T("CONFIG: directive '%s' in cnf file ignored.\n"), opt);
  }
  return 0;
}

/** Set the default configuration options.
 */
void
conf_default_set(void)
{
  strcpy(options.mud_name, "TinyMUSH");
  options.port = 4201;
  options.ssl_port = 0;
  strcpy(options.input_db, "data/indb");
  strcpy(options.output_db, "data/outdb");
  strcpy(options.crash_db, "data/PANIC.db");
  strcpy(options.chatdb, "data/chatdb");
  options.chan_cost = 1000;
  options.max_player_chans = 3;
  options.max_channels = 200;
  strcpy(options.mail_db, "data/maildb");
  options.player_start = 0;
  options.master_room = 2;
  options.base_room = 0;
  options.default_home = 0;
  options.ancestor_room = -1;
  options.ancestor_exit = -1;
  options.ancestor_thing = -1;
  options.ancestor_player = -1;
  options.idle_timeout = 0;
  options.unconnected_idle_timeout = 300;
  options.dump_interval = 3601;
  strcpy(options.dump_message,
	 T("GAME: Dumping database. Game may freeze for a minute"));
  strcpy(options.dump_complete, T("GAME: Dump complete. Time in."));
  options.ident_timeout = 5;
  options.max_logins = 128;
  options.max_guests = 0;
  options.whisper_loudness = 100;
  options.blind_page = 1;
  options.page_aliases = 0;
  options.paycheck = 50;
  options.guest_paycheck = 0;
  options.starting_money = 100;
  options.starting_quota = 20;
  options.player_queue_limit = 100;
  options.queue_chunk = 3;
  options.active_q_chunk = 0;
  options.func_nest_lim = 50;
  options.func_invk_lim = 2500;
  options.call_lim = 0;
  options.use_quota = 1;
  options.function_side_effects = 1;
  options.empty_attrs = 1;
  strcpy(options.money_singular, "Penny");
  strcpy(options.money_plural, "Pennies");
  strcpy(options.log_wipe_passwd, "zap!");
#ifdef WIN32
  strcpy(options.compressprog, "");
  strcpy(options.uncompressprog, "");
  strcpy(options.compresssuff, "");
#else
  strcpy(options.compressprog, "compress");
  strcpy(options.uncompressprog, "uncompress");
  strcpy(options.compresssuff, ".Z");
#endif				/* WIN32 */
  strcpy(options.connect_file[0], "txt/connect.txt");
  strcpy(options.motd_file[0], "txt/motd.txt");
  strcpy(options.wizmotd_file[0], "txt/wizmotd.txt");
  strcpy(options.newuser_file[0], "txt/newuser.txt");
  strcpy(options.register_file[0], "txt/register.txt");
  strcpy(options.quit_file[0], "txt/quit.txt");
  strcpy(options.down_file[0], "txt/down.txt");
  strcpy(options.full_file[0], "txt/full.txt");
  strcpy(options.guest_file[0], "txt/guest.txt");
  strcpy(options.error_log, "");
  strcpy(options.connect_log, "");
  strcpy(options.command_log, "");
  strcpy(options.trace_log, "");
  strcpy(options.wizard_log, "");
  strcpy(options.checkpt_log, "");
  options.log_commands = 0;
  options.log_forces = 1;
  options.support_pueblo = 0;
  options.login_allow = 1;
  options.guest_allow = 1;
  options.create_allow = 1;
  strcpy(options.player_flags, "");
  strcpy(options.room_flags, "");
  strcpy(options.exit_flags, "");
  strcpy(options.thing_flags, "");
  options.warn_interval = 3600;
  options.use_dns = 1;
  options.safer_ufun = 1;
  strcpy(options.dump_warning_1min,
	 T("GAME: Database will be dumped in 1 minute."));
  strcpy(options.dump_warning_5min,
	 T("GAME: Database will be dumped in 5 minutes."));
  options.noisy_whisper = 0;
  options.possessive_get = 1;
  options.possessive_get_d = 1;
  options.really_safe = 1;
  options.destroy_possessions = 1;
  options.null_eq_zero = 0;
  options.tiny_booleans = 0;
  options.tiny_math = 0;
  options.tiny_trim_fun = 0;
  options.adestroy = 0;
  options.amail = 0;
  options.mail_limit = 5000;
  options.player_listen = 1;
  options.player_ahear = 1;
  options.startups = 1;
  options.room_connects = 0;
  options.reverse_shs = 1;
  options.ansi_names = 1;
  options.comma_exit_list = 1;
  options.count_all = 0;
  options.exits_connect_rooms = 0;
  options.zone_control = 1;
  options.link_to_object = 1;
  options.owner_queues = 0;
  options.wiz_noaenter = 0;
  options.use_ident = 1;
  strcpy(options.ip_addr, "");
  strcpy(options.ssl_ip_addr, "");
  options.player_name_spaces = 0;
  options.forking_dump = 1;
  options.restrict_building = 0;
  options.free_objects = 1;
  options.flags_on_examine = 1;
  options.ex_public_attribs = 1;
  options.full_invis = 0;
  options.silent_pemit = 0;
  options.max_dbref = 0;
  options.chat_strip_quote = 1;
  strcpy(options.wizwall_prefix, "Broadcast:");
  strcpy(options.rwall_prefix, "Admin:");
  strcpy(options.wall_prefix, "Announcement:");
  strcpy(options.access_file, "access.cnf");
  strcpy(options.names_file, "names.cnf");
  options.object_cost = 10;
  options.exit_cost = 1;
  options.link_cost = 1;
  options.room_cost = 10;
  options.queue_cost = 10;
  options.quota_cost = 1;
  options.find_cost = 100;
  options.page_cost = 0;
  options.kill_default_cost = 100;
  options.kill_min_cost = 10;
  options.kill_bonus = 50;
  options.queue_loss = 63;
  options.max_pennies = 100000;
  options.max_guest_pennies = 100000;
  options.max_depth = 10;
  options.max_parents = 10;
  options.max_global_fns = 50;
  options.purge_interval = 601;
  options.dbck_interval = 599;
  options.max_attrcount = 2048;
  options.float_precision = 6;
  options.newline_one_char = 1;
  options.player_name_len = 16;
  options.queue_entry_cpu_time = 1500;
  options.ascii_names = 1;
  options.call_lim = 10000;
  strcpy(options.chunk_swap_file, "data/chunkswap");
  options.chunk_cache_memory = 1000000;
  options.chunk_migrate_amount = 50;
  options.read_remote_desc = 0;
#ifdef HAS_OPENSSL
  strcpy(options.ssl_private_key_file, "");
  strcpy(options.ssl_ca_file, "");
  options.ssl_require_client_cert = 0;
#endif
  options.mem_check = 0;
#ifdef HAS_MYSQL
  strcpy(options.sql_platform, "disabled");
  strcpy(options.sql_database, "");
  strcpy(options.sql_username, "");
  strcpy(options.sql_password, "");
  strcpy(options.sql_host, "127.0.0.1");
#endif
}

/* Limit how many files we can nest */
static int conf_recursion = 0;

/** Read a configuration file.
 * This function is called to read a configuration file. We may recurse,
 * as there's an 'include' directive. It's called twice, once before
 * the flag table load (when we want all options except restriction/alias
 * options) and once after (when we only want restriction/alias options.)
 * \param conf name of configuration file to read.
 * \param restrictions 1 if reading restriction options, 0 otherwise.
 * \retval 1 success.
 * \retval 0 failure.
 */
int
config_file_startup(const char *conf, int restrictions)
{
  /* read a configuration file. Return 0 on failure, 1 on success */
  /* If 'restrictions' is 0, ignore restrict*. If it's 1, only
   * look at restrict*
   */

  FILE *fp = NULL;
  PENNCONF *cp;
  char tbuf1[BUFFER_LEN];
  char *p, *q, *s;

  static char cfile[BUFFER_LEN];	/* Remember the last one */
  if (conf_recursion == 0) {
    if (conf && *conf)
      strcpy(cfile, conf);
    fp = fopen(cfile, FOPEN_READ);
    if (fp == NULL) {
      do_rawlog(LT_ERR, T("ERROR: Cannot open configuration file %s."), cfile);
      return 0;
    }
    do_rawlog(LT_ERR, "Reading %s", cfile);
  } else {
    if (conf && *conf)
      fp = fopen(conf, FOPEN_READ);
    if (fp == NULL) {
      do_rawlog(LT_ERR, T("ERROR: Cannot open configuration file %s."),
		(conf && *conf) ? conf : "Unknown");
      return 0;
    }
    do_rawlog(LT_ERR, "Reading %s", conf);
  }

  fgets(tbuf1, BUFFER_LEN, fp);
  while (!feof(fp)) {

    p = tbuf1;

    if (*p == '#') {
      /* comment line */
      fgets(tbuf1, BUFFER_LEN, fp);
      continue;
    }
    /* this is a real line. Strip the end-of-line and characters following it.
     * Split the line into command and argument portions. If it exists,
     * also strip off the trailing comment. We try to make this work
     * whether the eol is \n (unix, yay), \r\n (dos/win, ew), or \r (mac, hmm)
     * This basically rules out embedded newlines as currently written
     */

    for (p = tbuf1; *p && (*p != '\n') && (*p != '\r'); p++) ;
    *p = '\0';			/* strip the end of line char(s) */
    for (p = tbuf1; *p && isspace((unsigned char) *p); p++)	/* strip spaces */
      ;
    for (q = p; *q && !isspace((unsigned char) *q); q++)	/* move over command */
      ;
    if (*q)
      *q++ = '\0';		/* split off command */
    for (; *q && isspace((unsigned char) *q); q++)	/* skip spaces */
      ;
    /* If the first character of the value is a #, and that is
       followed by a number, treat it as a dbref instead of a
       comment. */
    if (*q == '#' && isdigit((unsigned char) *(q + 1))) {
      for (s = q + 1; *s && (*s != '#'); s++)	/* look for a real comment */
	;
    } else {
      for (s = q; *s && (*s != '#'); s++)	/* look for comment */
	;
    }
    if (*s)			/* if found nuke it */
      *s = '\0';
    for (s = s - 1; (s >= q) && isspace((unsigned char) *s); s--)	/* smash trailing stuff */
      *s = '\0';

    if (strlen(p) != 0) {	/* skip blank lines */
      /* Handle include filename directives separetly */
      if (strcasecmp(p, "include") == 0) {
	conf_recursion++;
	if (conf_recursion > 10) {
	  do_rawlog(LT_ERR, T("CONFIG: include depth too deep in file %s"),
		    conf);
	} else {
	  config_file_startup(q, restrictions);
	}
	conf_recursion--;
      } else
	config_set(p, q, 0, restrictions);
    }
    fgets(tbuf1, BUFFER_LEN, fp);
  }

  /* Warn about any config options that aren't overridden by the
   * config file.
   */
  if (conf_recursion == 0) {
    for (cp = conftable; cp->name; cp++) {
      if (!cp->overridden) {
	do_rawlog(LT_ERR,
		  T
		  ("CONFIG: directive '%s' missing from cnf file, using default value."),
		  cp->name);
      }
    }
    for (cp = (PENNCONF *) hash_firstentry(&local_options); cp;
	 cp = (PENNCONF *) hash_nextentry(&local_options)) {
      if (!cp->overridden) {
	do_rawlog(LT_ERR,
		  T
		  ("CONFIG: local directive '%s' missing from cnf file. using default value."),
		  cp->name);
      }
    }

    /* these directives aren't player-settable but need to be initialized */
    mudtime = time(NULL);
    options.dump_counter = mudtime + options.dump_interval;
    options.purge_counter = mudtime + options.purge_interval;
    options.dbck_counter = mudtime + options.dbck_interval;
    options.warn_counter = mudtime + options.warn_interval;

#ifdef WIN32
    /* if we're on Win32, complain about compression */
    if ((options.compressprog && *options.compressprog)) {
      do_rawlog(LT_ERR,
		T
		("CONFIG: compression program is specified but not used in Win32, ignoring"),
		options.compressprog);
    }

    if (((options.compresssuff && *options.compresssuff))) {
      do_rawlog(LT_ERR,
		T
		("CONFIG: compression suffix is specified but not used in Win32, ignoring"),
		options.compresssuff);
    }

    /* Also remove the compression options */
    *options.uncompressprog = 0;
    *options.compressprog = 0;
    *options.compresssuff = 0;

#endif

  }
  fclose(fp);
  return 1;
}

/** List configuration directives or groups.
 * \param player the enactor.
 * \param type the directive or group name.
 * \param lc if 1, list in lowercase.
 */
void
do_config_list(dbref player, const char *type, int lc)
{
  PENNCONFGROUP *cgp;
  PENNCONF *cp;

  if (SUPPORT_PUEBLO)
    notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
  if (type && *type) {
    /* Look up the type in the group table */
    int found = 0;
    for (cgp = confgroups; cgp->name; cgp++) {
      if (string_prefix(T(cgp->name), type)
	  && Can_View_Config_Group(player, cgp)) {
	found = 1;
	break;
      }
    }
    if (!found) {
      /* It wasn't a group. Is is one or more specific options? */
      for (cp = conftable; cp->name; cp++) {
	if (cp->group && string_prefix(cp->name, type)) {
	  notify(player, config_list_helper(player, cp, lc));
	  found = 1;
	}
      }
      if (!found) {
	/* Ok, maybe a local option? */
	for (cp = (PENNCONF *) hash_firstentry(&local_options); cp;
	     cp = (PENNCONF *) hash_nextentry(&local_options)) {
	  if (cp->group && !strcasecmp(cp->name, type)) {
	    notify(player, config_list_helper(player, cp, lc));
	    found = 1;
	  }
	}
      }
      if (!found) {
	/* Wasn't found at all. Ok. */
	notify(player, T("I only know the following types of options:"));
	for (cgp = confgroups; cgp->name; cgp++) {
	  if (Can_View_Config_Group(player, cgp))
	    notify_format(player, " %-15s %s", T(cgp->name), cgp->desc);
	}
      }
    } else {
      /* Show all entries of that type */
      notify(player, cgp->desc);
      if (string_prefix("compile", type))
	show_compile_options(player);
      else {
	for (cp = conftable; cp->name; cp++) {
	  if (cp->group && !strcmp(cp->group, cgp->name)) {
	    notify(player, config_list_helper(player, cp, lc));
	  }
	}
	for (cp = (PENNCONF *) hash_firstentry(&local_options); cp;
	     cp = (PENNCONF *) hash_nextentry(&local_options)) {
	  if (cp->group && !strcasecmp(cp->group, cgp->name)) {
	    notify(player, config_list_helper(player, cp, lc));
	  }
	}
      }
    }
  } else {
    /* If we're here, we ran @config without a type. */
    notify(player,
	   T("Use: @config/list <type of options> where type is one of:"));
    for (cgp = confgroups; cgp->name; cgp++) {
      if (Can_View_Config_Group(player, cgp))
	notify_format(player, " %-15s %s", T(cgp->name), cgp->desc);
    }
  }
  if (SUPPORT_PUEBLO)
    notify_noenter(player, tprintf("%c/SAMP%c", TAG_START, TAG_END));
}

/** Lowercase a string if we've been asked to */
#define MAYBE_LC(x) (lc ? strlower(x) : x)
static char *
config_list_helper(dbref player
		   __attribute__ ((__unused__)), PENNCONF * cp, int lc)
{
  static char result[BUFFER_LEN];
  char *bp = result;

  if ((cp->handler == cf_str) || (cp->handler == cf_flag))
    safe_format(result, &bp, " %-40s %s", MAYBE_LC(cp->name), (char *) cp->loc);
  else if (cp->handler == cf_int)
    safe_format(result, &bp, " %-40s %d", MAYBE_LC(cp->name),
		*((int *) cp->loc));
  else if (cp->handler == cf_time) {
    div_t n;
    int secs = *(int *) cp->loc;

    safe_format(result, &bp, " %-40s ", MAYBE_LC(cp->name));

    if (secs >= 3600) {
      n = div(secs, 3600);
      secs = n.rem;
      safe_format(result, &bp, "%dh", n.quot);
    }
    if (secs >= 60) {
      n = div(secs, 60);
      secs = n.rem;
      safe_format(result, &bp, "%dm", n.quot);
    }
    if (secs)
      safe_format(result, &bp, "%ds", secs);
  } else if (cp->handler == cf_bool)
    safe_format(result, &bp, " %-40s %s", MAYBE_LC(cp->name),
		(*((int *) cp->loc) ? "Yes" : "No"));
  else if (cp->handler == cf_dbref)
    safe_format(result, &bp, " %-40s #%d", MAYBE_LC(cp->name),
		*((dbref *) cp->loc));
  *bp = '\0';
  return result;
}

/* This one doesn't return the names */
static char *
config_list_helper2(dbref player
		    __attribute__ ((__unused__)), PENNCONF * cp, int lc
		    __attribute__ ((__unused__)))
{
  static char result[BUFFER_LEN];
  char *bp = result;
  if ((cp->handler == cf_str) || (cp->handler == cf_flag))
    safe_format(result, &bp, "%s", (char *) cp->loc);
  else if (cp->handler == cf_int)
    safe_format(result, &bp, "%d", *((int *) cp->loc));
  else if (cp->handler == cf_time) {
    div_t n;
    int secs = *(int *) cp->loc;

    if (secs >= 3600) {
      n = div(secs, 3600);
      secs = n.rem;
      safe_format(result, &bp, "%dh", n.quot);
    }
    if (secs >= 60) {
      n = div(secs, 60);
      secs = n.rem;
      safe_format(result, &bp, "%dm", n.quot);
    }
    if (secs)
      safe_format(result, &bp, "%ds", secs);
  } else if (cp->handler == cf_bool)
    safe_format(result, &bp, "%s", (*((int *) cp->loc) ? "Yes" : "No"));
  else if (cp->handler == cf_dbref)
    safe_format(result, &bp, "#%d", *((dbref *) cp->loc));
  *bp = '\0';
  return result;
}

#undef MAYBE_LC

/* config(option): returns value of option
 * config(): returns list of all option names
 */
FUNCTION(fun_config)
{
  PENNCONF *cp;

  if (args[0] && *args[0]) {
    for (cp = conftable; cp->name; cp++) {
      if (cp->group && !strcasecmp(cp->name, args[0])) {
	safe_str(config_list_helper2(executor, cp, 0), buff, bp);
	return;
      }
    }
    for (cp = (PENNCONF *) hash_firstentry(&local_options); cp;
	 cp = (PENNCONF *) hash_nextentry(&local_options)) {
      if (cp->group && !strcasecmp(cp->name, args[0])) {
	safe_str(config_list_helper2(executor, cp, 0), buff, bp);
	return;
      }
    }
    safe_str(T("#-1 NO SUCH CONFIG OPTION"), buff, bp);
    return;
  } else {
    for (cp = conftable; cp->name; cp++) {
      safe_str(cp->name, buff, bp);
      safe_chr(' ', buff, bp);
    }
    for (cp = (PENNCONF *) hash_firstentry(&local_options); cp;
	 cp = (PENNCONF *) hash_nextentry(&local_options)) {
      safe_str(cp->name, buff, bp);
      safe_chr(' ', buff, bp);
    }
  }
}

/** Enable or disable a configuration option.
 * \param player the enactor.
 * \param param the option to enable/disable.
 * \param state 1 to enable, 0 to disable.
 */
void
do_enable(dbref player, const char *param, int state)
{
  PENNCONF *cp;

  for (cp = conftable; cp->name; cp++) {
    if (cp->group && !strcasecmp(cp->name, param)) {
      if (cp->handler == cf_bool) {
	cf_bool(param, (state ? "yes" : "no"), cp->loc, cp->max, 1);
	if (state == 0)
	  notify(player, T("Disabled."));
	else
	  notify(player, T("Enabled."));
	do_log(LT_WIZ, player, NOTHING, "%s %s",
	       cp->name, (state) ? "ENABLED" : "DISABLED");
      } else
	notify(player, T("That isn't a on/off option."));
      return;
    }
  }
  notify(player, T("No such option."));
}

static void
show_compile_options(dbref player)
{
#if (COMPRESSION_TYPE == 0)
  notify(player, T(" Attributes are not compressed in memory."));
#endif
#if (COMPRESSION_TYPE == 1) || (COMPRESSION_TYPE == 2)
  notify(player, T(" Attributes are Huffman compressed in memory."));
#endif
#if (COMPRESSION_TYPE == 3)
  notify(player, T(" Attributes are word compressed in memory."));
#endif
#if (COMPRESSION_TYPE == 4)
  notify(player, T(" Attributes are 8-bit word compressed in memory."));
#endif

#ifdef HAS_OPENSSL
  notify(player, T(" The MUSH was compiled with SSL support."));
#endif

#ifdef INFO_SLAVE
  notify(player, T(" DNS and ident lookups are handled by a slave process."));
#else
  notify(player, T(" DNS and ident lookups are handled by the MUSH process."));
#endif

#ifdef NT_TCP
  notify(player, T(" Windows NT native TCP/IP networking functions in use."));
#else
  notify(player, T(" BSD sockets networking in use."));
#endif

#ifdef HAS_GETDATE
  notify(player, T(" Extended convtime() is supported."));
#else
  notify(player, T(" convtime() is stricter."));
#endif

#if defined(HAS_ITIMER) || defined(WIN32)
  notify(player, T(" CPU usage limiting is supported."));
#else
  notify(player, T(" CPU usage limiting is NOT supported."));
#endif

}