/*
#ident	"@(#)smail/src:RELEASE-3_2_0_120:smailconf.c,v 1.101 2004/08/23 00:46:37 woods Exp"
 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * smailconf.c:
 *	loading and processing for the configuration files
 *
 *	external functions: read_config_file, format_attribute,
 *			    print_config_variable, read_standard_file,
 *			    fill_attributes, find_config_attribute,
 *			    add_config_stat, is_newconf
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <grp.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#include <pcre.h>

#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "log.h"
#include "match.h"
#include "direct.h"
#include "route.h"
#include "version.h"
#include "transport.h"
#include "smailconf.h"
#include "exitcodes.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"

/* These 2 values are used in handling int values as suffix multipliers */
#define K_MULTIPLIER	1024
#define M_MULTIPLIER	(1024*1024)

/* functions local to this file */
static char *check_type __P((struct attr_table *, struct attribute *, char *, char *));
static char *convert_value __P((struct attr_table *, struct attribute *, u_attr_t *, smail_bool_t *, char *, char *, char *));
static struct attr_table *find_attribute __P((char *, struct attr_table *, struct attr_table *));
static const char *conf_var_typename __P((struct attr_table *));
static void print_config_help __P((void));
static void print_config_all __P((void));

/* types local to this file */
/*
 * config_stat_list is used by add_config_stat() and is_newconf() to
 * determine if any configuration files have changed.  Optional config
 * files which do not exist have a 0 stored in the mtime field.
 */
/* FIXME: maybe move this declaration to smailconf.h */
struct config_stat {
    struct config_stat *succ;
    char    *fn;			/* name of config file */
    time_t  mtime;			/* time of last mod */
    dev_t   dev;			/* device file resides on */
    ino_t   ino;			/* inode number for file */
} *config_stat_list = NULL;

/*
 * define the attributes which may be in the config file
 *
 * NOTE: keep these in sorted order, except always give duplicate/deprecated
 * names (i.e. those which point to the same variable) immediately after the
 * canonical name so that they will be skipped by print_config_all();
 * (i.e. "smail -bP all").
 *
 * IMPORTANT:  Offset value must be "TRUE" for t_boolean attribues!
 */
static struct attr_table conf_attributes[] = {
    { "allow_one_mx_target_cname_hack", t_boolean, 0, FALSE, (tup_t *)&allow_one_mx_target_cname_hack, TRUE },
    { "auth_domains", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&auth_domains, 0 },
/**/{ "auth_domain", t_string, FA_FMT_IS_LIST | FA_FMT_DEPRECATED, NULL, (tup_t *)&auth_domains, 0 },
    { "auto_mkdir", t_boolean, 0, FALSE, (tup_t *)&auto_mkdir, TRUE },
    { "auto_mkdir_mode", t_mode, 0, 0, (tup_t *)&auto_mkdir_mode, 0 },
    { "body_checks", t_string, FA_FMT_IS_PCRE_LIST, NULL, (tup_t *)&body_checks, 0 },
    { "body_checks_always", t_string, FA_FMT_IS_PCRE_LIST, NULL, (tup_t *)&body_checks_always, 0 },
    { "bypass_content_filtering_recipients", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&bypass_content_filtering_recipients, 0 },
    { "config_file", t_string, 0, NULL, (tup_t *)&config_file, 0 },
    { "console", t_string, 0, NULL, (tup_t *)&cons_fn, 0 },
    { "copying_file", t_string, 0, NULL, (tup_t *)&copying_file, 0 },
#ifdef USE_CYRUS_IMAPD /* XXX is #ifdef'ing these a good idea? */
    { "cyrus_deliver_path", t_string, 0, NULL, (tup_t *)&cyrus_deliver_path, 0 },
    { "cyrus_group", t_string, 0, NULL, (tup_t *)&cyrus_group, 0 },
    { "cyrus_noquota_passphrase", t_string, 0, NULL, (tup_t *)&cyrus_noquota_passphrase, 0 },
    { "cyrus_user", t_string, 0, NULL, (tup_t *)&cyrus_user, 0 },
#endif
    { "daemon_pidfile", t_string, 0, NULL, (tup_t *)&daemon_pidfile, 0 },
    { "date_field", t_string, 0, NULL, (tup_t *)&date_field, 0 },
    { "delivery_grades", t_string, 0, NULL, (tup_t *)&delivery_grades, 0 },
    { "delivery_mode", t_string, 0, NULL, (tup_t *)&delivery_mode_string, 0 },
    { "director_file", t_string, 0, NULL, (tup_t *)&director_file, 0 },
    { "domains", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&visible_domains, 0 }, /* XXX don't rename the variable to `domains'! */
/**/{ "domain", t_string, FA_FMT_IS_LIST | FA_FMT_DEPRECATED, NULL, (tup_t *)&visible_domains, 0 },
/**/{ "visible_domains", t_string, FA_FMT_IS_LIST | FA_FMT_DEPRECATED, NULL, (tup_t *)&visible_domains, 0 },
/**/{ "visible_domain", t_string, FA_FMT_IS_LIST | FA_FMT_DEPRECATED, NULL, (tup_t *)&visible_domains, 0 },
    { "error_copy_postmaster", t_boolean, 0, FALSE, (tup_t *)&error_copy_postmaster, TRUE },
    { "flock_mailbox", t_boolean, 0, FALSE, (tup_t *)&flock_mailbox, TRUE },
    { "fnlock_interval", t_int, 0, 0, (tup_t *)&fnlock_interval, 0 },
    { "fnlock_mode", t_mode, 0, 0, (tup_t *)&fnlock_mode, 0 },
    { "fnlock_retries", t_int, 0, 0, (tup_t *)&fnlock_retries, 0 },
    { "from_field", t_string, 0, NULL, (tup_t *)&from_field, 0 },
    { "grades", t_string, 0, NULL, (tup_t *)&grades, 0 },
    { "header_checks", t_string, FA_FMT_IS_PCRE_LIST, NULL, (tup_t *)&header_checks, 0 },
    { "header_checks_always", t_string, FA_FMT_IS_PCRE_LIST, NULL, (tup_t *)&header_checks_always, 0 },
    { "hit_table_len", t_int, 0, 0, (tup_t *)&hit_table_len, 0 },
    { "host_lock_timeout", t_interval, 0, 0, (tup_t *)&host_lock_timeout, 0 },
    { "hostnames", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&hostnames, 0 },
/**/{ "hostname", t_string, FA_FMT_IS_LIST | FA_FMT_DEPRECATED, NULL, (tup_t *)&hostnames, 0 },
    { "ignore_user_case", t_boolean, 0, FALSE, (tup_t *)&ignore_user_case, TRUE },
    { "listen_name", t_string, 0, NULL, (tup_t *)&listen_name, 0 },
    { "lock_by_name", t_boolean, 0, FALSE, (tup_t *)&lock_by_name, TRUE },
    { "lock_mode", t_mode, 0, 0, (tup_t *)&lock_mode, 0 },
    { "log_mode", t_mode, 0, 0, (tup_t *)&log_mode, 0 },
    { "logfile", t_string, 0, NULL, (tup_t *)&log_fn, 0 },
    { "max_hop_count", t_int, 0, 0, (tup_t *)&max_hop_count, 0 },
    { "max_load_ave", t_double, 0.0, NULL, (tup_t *)&max_load_ave, 0 },
    { "max_message_size", t_long, 0, 0L, (tup_t *)&max_message_size, 0 },
    { "message_buf_size", t_int, 0, 0, (tup_t *)&message_bufsiz, 0 },
    { "message_id_field", t_string, 0, NULL, (tup_t *)&message_id_field, 0 },
    { "message_log_mode", t_mode, 0, 0, (tup_t *)&message_log_mode, 0 },
    { "method_dir", t_string, 0, NULL, (tup_t *)&method_dir, 0 },
    { "more_hostnames", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&more_hostnames, 0 },
/**/{ "gateway_names", t_string, FA_FMT_IS_LIST | FA_FMT_DEPRECATED, NULL, (tup_t *)&more_hostnames, 0 },
    { "nobody", t_string, 0, NULL, (tup_t *)&nobody, 0 },
    { "open_interval", t_int, 0, 0, (tup_t *)&open_interval, 0 },
    { "open_retries", t_int, 0, 0, (tup_t *)&open_retries, 0 },
    { "paniclog", t_string, 0, NULL, (tup_t *)&panic_fn, 0 },
    { "postmaster_address", t_string, 0, NULL, (tup_t *)&postmaster_address, 0 },
/**/{ "postmaster", t_string, FA_FMT_DEPRECATED, NULL, (tup_t *)&postmaster_address, 0 },
    { "primary_name", t_string, 0, NULL, (tup_t *)&primary_name, 0 },
    { "qualify_file", t_string, 0, NULL, (tup_t *)&qualify_file, 0 },
    { "queue_only", t_boolean, 0, FALSE, (tup_t *)&queue_only, TRUE },
    { "received_field", t_string, 0, NULL, (tup_t *)&received_field, 0 },
    { "require_configs", t_boolean, 0, FALSE, (tup_t *)&require_configs, TRUE },
    { "resolve_timeout", t_interval, 0, 0, (tup_t *)&resolve_timeout, 0 },
    { "retry_duration", t_interval, 0, 0, (tup_t *)&retry_duration, 0 },
    { "retry_file", t_string, 0, NULL, (tup_t *)&retry_file, 0 },
    { "retry_interval", t_interval, 0, 0, (tup_t *)&retry_interval, 0 },
    { "return_path_field", t_string, 0, NULL, (tup_t *)&return_path_field, 0 },
    { "rfc1413_query_timeout", t_interval, 0, 0, (tup_t *)&rfc1413_query_timeout, 0 },
    { "router_file", t_string, 0, NULL, (tup_t *)&router_file, 0 },
    { "runq_grades", t_string, 0, NULL, (tup_t *)&runq_grades, 0 },
    { "runq_max", t_int, 0, 0, (tup_t *)&runq_max, 0 },
    { "second_config_file", t_string, 0, NULL, (tup_t *)&second_config_file, 0 },
    { "sender_env_variable", t_string, 0, NULL, (tup_t *)&sender_env_variable, 0 },
    { "sending_name", t_string, 0, NULL, (tup_t *)&sending_name, 0 },
    { "smail", t_string, 0, NULL, (tup_t *)&smail, 0 },
    { "smail_lib_dir", t_string, 0, NULL, (tup_t *)&smail_lib_dir, 0 },
    { "smail_util_dir", t_string, 0, NULL, (tup_t *)&smail_util_dir, 0 },
    { "smart_path", t_string, 0, NULL, (tup_t *)&smart_path, 0 },
    { "smart_transport", t_string, 0, NULL, (tup_t *)&smart_transport, 0 },
    { "smart_user", t_string, 0, NULL, (tup_t *)&smart_user, 0 },
    { "smtp_accept_max", t_int, 0, 0, (tup_t *)&smtp_accept_max, 0 },
    { "smtp_accept_queue", t_int, 0, 0, (tup_t *)&smtp_accept_queue, 0 },
    { "smtp_allow_debug", t_boolean, 0, FALSE, (tup_t *)&smtp_allow_debug, TRUE },
/**/{ "smtp_debug", t_boolean, FA_FMT_DEPRECATED, FALSE, (tup_t *)&smtp_allow_debug, TRUE },
    { "smtp_allow_expn", t_boolean, 0, FALSE, (tup_t *)&smtp_allow_expn, TRUE },
/**/{ "smtp_info", t_boolean, FA_FMT_DEPRECATED, FALSE, (tup_t *)&smtp_allow_expn, TRUE },
    { "smtp_bad_mx_except", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_bad_mx_except, 0 },
    { "smtp_bad_mx_targets", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_bad_mx_targets, 0 },
    { "smtp_banner", t_string, 0, NULL, (tup_t *)&smtp_banner, 0 },
    { "smtp_debug_pause", t_int, 0, 0, (tup_t *)&smtp_debug_pause, 0 },
    { "smtp_error_delay", t_int, 0, 0, (tup_t *)&smtp_error_delay, 0 },
    { "smtp_expn_delay", t_int, 0, 0, (tup_t *)&smtp_expn_delay, 0 },
    { "smtp_hello_broken_allow", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_hello_broken_allow, 0 },
    { "smtp_hello_dnsbl_domains", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_hello_dnsbl_domains, 0 },
    { "smtp_hello_dnsbl_except", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_hello_dnsbl_except, 0 },
    { "smtp_hello_reject_dns_paranoid", t_boolean, 0, FALSE, (tup_t *)&smtp_hello_reject_dns_paranoid, TRUE },
    { "smtp_hello_reject_hostnames", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_hello_reject_hostnames, 0 },
    { "smtp_hello_verify", t_boolean, 0, FALSE, (tup_t *)&smtp_hello_verify, TRUE },
    { "smtp_hello_verify_literal", t_boolean, 0, FALSE, (tup_t *)&smtp_hello_verify_literal, TRUE },
    { "smtp_hello_verify_ptr", t_boolean, 0, FALSE, (tup_t *)&smtp_hello_verify_ptr, TRUE },
    { "smtp_host_dnsbl_domains", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_host_dnsbl_domains, 0 },
    { "smtp_host_dnsbl_except", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_host_dnsbl_except, 0 },
    { "smtp_host_reject_hostnames", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_host_reject_hostnames, 0 },
    { "smtp_invalid_recipient_error_delay", t_int, 0, 0, (tup_t *)&smtp_invalid_recipient_error_delay, 0 },
    { "smtp_local_sender_allow", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_local_sender_allow, 0 },
    { "smtp_local_sender_restrict", t_boolean, 0, FALSE, (tup_t *)&smtp_local_sender_restrict, TRUE },
    { "smtp_max_recipients", t_int, 0, 0, (tup_t *)&smtp_max_recipients, 0 },
    { "smtp_max_bounce_recipients", t_int, 0, 0, (tup_t *)&smtp_max_bounce_recipients, 0 },
    { "smtp_mua_only_hosts", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_mua_only_hosts, 0},
    { "smtp_permit_mx_backup", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_permit_mx_backup, 0},
    { "smtp_rbl_domains", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_rbl_domains, 0 },
    { "smtp_rbl_except", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_rbl_except, 0 },
    { "smtp_receive_command_timeout", t_interval, 0, 0, (tup_t *)&smtp_receive_command_timeout, 0 },
    { "smtp_receive_message_timeout", t_interval, 0, 0, (tup_t *)&smtp_receive_message_timeout, 0 },
    { "smtp_recipient_no_verify", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_recipient_no_verify, 0 },
    { "smtp_reject_hosts", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_reject_hosts, 0 },
/**/{ "smtp_hello_reject_hosts", t_string, FA_FMT_IS_LIST | FA_FMT_DEPRECATED, NULL, (tup_t *)&smtp_reject_hosts, 0 }, /* was only in 3.2.0.112 - 3.2.0.114 */
    { "smtp_remote_allow", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_remote_allow, 0 },
    { "smtp_sender_no_verify", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_sender_no_verify, 0 },
    { "smtp_sender_reject", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_sender_reject, 0 },
    { "smtp_sender_reject_db", t_string, 0, NULL, (tup_t *)&smtp_sender_reject_db, 0 },
    { "smtp_sender_reject_db_proto", t_string, 0, NULL, (tup_t *)&smtp_sender_reject_db_proto, 0 },
    { "smtp_sender_reject_hostnames", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_sender_reject_hostnames, 0 },
    { "smtp_sender_rhsbl_domains", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_sender_rhsbl_domains, 0 },
    { "smtp_sender_rhsbl_except", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&smtp_sender_rhsbl_except, 0 },
    { "smtp_sender_verify_mx_only", t_boolean, 0, FALSE, (tup_t *)&smtp_sender_verify_mx_only, TRUE },
    { "smtp_vrfy_delay", t_int, 0, 0, (tup_t *)&smtp_vrfy_delay, 0 },
    { "spool_dirs", t_string, FA_FMT_IS_LIST, NULL, (tup_t *)&spool_dirs, 0 },
    { "spool_dir", t_string, 0, NULL, (tup_t *)&spool_dir, 0 }, /* set at runtime from spool_dirs */
    { "spool_grade", t_char, 0, '\0', (tup_t *)&spool_grade, 0 },
    { "spool_mode", t_mode, 0, 0, (tup_t *)&spool_mode, 0 },
    { "switch_percent_and_bang", t_boolean, 0, FALSE, (tup_t *)&switch_percent_and_bang, TRUE },
    { "transport_file", t_string, 0, NULL, (tup_t *)&transport_file, 0 },
    { "trusted_users", t_string, 0, NULL, (tup_t *)&trusted, 0 },
/**/{ "trusted_user", t_string, FA_FMT_DEPRECATED, NULL, (tup_t *)&trusted, 0 },
/**/{ "trusted", t_string, FA_FMT_DEPRECATED, NULL, (tup_t *)&trusted, 0 },
    { "trusted_groups", t_string, 0, NULL, (tup_t *)&trusted_groups, 0 },
/**/{ "trusted_group", t_string, FA_FMT_DEPRECATED, NULL, (tup_t *)&trusted_groups, 0 },
    { "uucp_name", t_string, 0, NULL, (tup_t *)&uucp_name, 0 },
    { "version", t_infoproc, 0, NULL, (tup_t *)version, 0 },
    { "visible_name", t_string, 0, NULL, (tup_t *)&visible_name, 0 },
#ifdef FIXME
    { NULL, 0, 0, NULL, NULL, 0 }
#endif
};
/* FIXME:  avoid this ugly ENDTABLE() stuff -- use null-entry termination semantics... */
/* point to the end of the configuration attributes table */
struct attr_table *end_conf_attributes = ENDTABLE(conf_attributes);



/*
 * read_config_file - read a config file, or a colon separated list of config
 * files, and fill in conf_attributes
 *
 * return an error message or NULL (if no error).
 */
char *
read_config_file(fn)
    char *fn;				/* name of a config file */
{
    char *entry;			/* entry found by read_entry */
    FILE *f;				/* input file */
    struct stat statbuf;

    DEBUG1(DBG_CONF_MID, "read_config_file(): called for %s\n", fn);
    if (fn == NULL || EQ(fn, "-")) {
	/* a name of `-' turns off use of the external file */
	return NULL;
    }
    /* 
     * iterate through each file in succession if multiple names are given,
     * separated by colons, and recursively read and process them....
     */
    if (strchr(fn, ':')) {
	char *p_fn, *p_nextfn, *p_start, *p_end, *p_result;

	p_fn = COPY_STRING(fn);
	for (p_start = p_fn; (p_end = strchr(p_start, ':')); p_start = p_end + 1) {
	    *p_end = '\0';
	    p_nextfn = make_lib_fn(p_start);
	    p_result = read_config_file(p_nextfn);
	    if (p_nextfn) {
		xfree(p_nextfn);
	    }
	    if (p_result) {
		if (p_fn) {
		    xfree(p_fn);
		}
		return(p_result);	/* pop any errors back out */
	    }
	}
	p_result = NULL;
	if (p_start && *p_start) {
	    p_nextfn = make_lib_fn(p_start);
	    p_result = read_config_file(p_nextfn);
	    if (p_nextfn) {
		xfree(p_nextfn);
	    }
	}
	if (p_fn) {
	    xfree(p_fn);
	}
	return(p_result);
    }
    if (! (f = fopen(fn, "r"))) {
	DEBUG2(DBG_MAIN_MID, "read_config_file(): %s: fopen() failed: %s\n", fn, strerror(errno));
	if (require_configs) {
	    return xprintf("%s: %s", fn, strerror(errno));
	}

	add_config_stat(fn, (struct stat *)NULL);
	return NULL;
    }
    if (fstat(fileno(f), &statbuf) < 0) {
	DEBUG2(DBG_MAIN_LO, "read_config_file(): %s: fstat() failed: %s\n", fn, strerror(errno));
	return xprintf("%s: %s", fn, strerror(errno));
    }
    add_config_stat(fn, &statbuf);

    /*
     * read all of the entries in the config file
     */
    while ((entry = read_entry(f))) {
	char *error = NULL;
	struct attr_table *conf_attr;
	struct attribute *attr;
	smail_bool_t flags;

	DEBUG2(DBG_CONF_HI, "read_config_file(): read_entry(%v) returns:\n\t'%v'\n", fn, entry);
	attr = parse_config(entry, &error);
	if (attr == NULL) {
	    DEBUG1(DBG_CONF_LO, "read_config_file(): parse_config() failed: %s\n", error);
	    return xprintf("%v: parse error: %s", fn, error);
	}
	conf_attr = find_config_attribute(attr->name);
	if (conf_attr == NULL) {
	    DEBUG1(DBG_CONF_LO, "read_config_file(): find_config_attr() failed to find variable: %s\n", attr->name);
	    return xprintf("%v: unknown variable: %v", fn, attr->name);
	}
	if (conf_attr->type == t_boolean) {
	    flags = conf_attr->uptr->v_boolean;
	}
	conf_attr->value = attr->value;
	/* check if boolean value used was properly */
	if ((error = check_type(conf_attr, attr, fn, "variable"))) {
	    DEBUG2(DBG_CONF_LO, "read_config_file(): check_type(%s) failed for: %s\n", attr->name, error);
	    return error;
	}
	/* check for deprecated names */
	if (conf_attr->fmt_hints & FA_FMT_DEPRECATED) {
	    if (getuid() == 0) {
		write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
			  "%s: %s: attribute name is deprecated -- see manual", fn, attr->name);
	    } else if (errfile) {
		dprintf(errfile, "%s: %s: %s: attribute name is deprecated -- see manual\n", program, fn, attr->name);
	    }
	}
#if 0
	DEBUG3(DBG_CONF_HI, "read_config_file(): %s variable '%v', about to get value:\n\t'%v'\n",
	       conf_var_typename(conf_attr), conf_attr->name, conf_attr->value);
#endif
	if ((error = convert_value(conf_attr, attr, conf_attr->uptr, &flags, (char *) NULL, fn, "variable"))) {
	    DEBUG2(DBG_CONF_LO, "read_config_file(): convert_value(%s) failed for: %s\n", attr->name, error);
	    return error;
	}
	if (conf_attr->type == t_boolean) {
	    conf_attr->uptr->v_boolean = flags;
#if 0
	    DEBUG4(DBG_CONF_HI, "read_config_file(): bool_t %v := %v, flags = 0x%lx, offset = 0x%lx\n",
		   attr->name, attr->value, conf_attr->uptr->v_boolean, conf_attr->offset);
#endif
	}
    }

    return NULL;
}

static char *
check_type(conf_attr, attr, fn, attr_class)
    struct attr_table *conf_attr;
    struct attribute *attr;
    char *fn;				/* configuration file attr was read from */
    char *attr_class;			/* "attribute" or "variable" */
{
    if (conf_attr->type == t_boolean) {
	/* make sure boolean types are given only boolean values */
	if (!EQ(attr->value, on) && !EQ(attr->value, off)) {
	    return xprintf("%v: boolean %s `%v' has non-boolean form of '%v'",
			   fn, attr_class, attr->name, attr->value);
	}
    } else {
	/* make sure non-boolean types aren't given with '+'/'-' prefixes */
	if ((attr->value == on &&
	     conf_attr->type != t_proc) || /* XXX does this ever make sense? */
	    (attr->value == off &&
	     conf_attr->type != t_string &&
	     conf_attr->type != t_char &&
	     conf_attr->type != t_int &&
	     conf_attr->type != t_mode &&
	     conf_attr->type != t_long &&
	     conf_attr->type != t_interval &&
	     conf_attr->type != t_double &&
	     conf_attr->type != t_proc &&
	     conf_attr->type != t_infoproc))
	{
	    return xprintf("%v: non-boolean %s `%v' has boolean form",
			   fn, attr_class, attr->name);
	}
    }

    return (char *) NULL;
}

/*
 * convert_value(): fill in the attribute given the string representation from attr
 */
static char *
convert_value(conf_attr, attr, uptr, flagsp, sp, fn, attr_class)
    struct attr_table *conf_attr;	/* pointer to attribute entry to fill in */
    struct attribute *attr;		/* pointer to attribute parsed from config flie */
    u_attr_t *uptr;			/* pointer to storage for converted attribute */
    smail_bool_t *flagsp;		/* pointer to bit value for boolean attribute */
    char *sp;				/* structure pointer for t_proc calls */
    char *fn;				/* configuration file attr was read from */
    char *attr_class;			/* "attribute" or "variable" */
{
    char *errmsg = NULL;

    switch (conf_attr->type) {
    case t_string:
	/* special case, '-' prefix on string attr name sets string to NULL */
	if (attr->value == off) {	/* pointer equivalence */
	    uptr->v_string = NULL;
	} else {
	    uptr->v_string = attr->value;
	}
	break;

    case t_boolean:
	if (!EQ(attr->value, on) && !EQ(attr->value, off)) {
	    return xprintf("%v: bad boolean constant for %s: %v",
			   fn, attr_class, attr->name);
	}
#if 0 /* turn on the one below too for the final value and newline! */
	DEBUG6(DBG_CONF_HI, "%v: convert_value(boolean %s): %v := %v, flags = 0x%lx, offset = 0x%lx",
	       fn, attr_class, attr->name, attr->value, *flagsp, conf_attr->offset);
#endif
	/*
	 * NOTE:  we can't use uptr->v_boolean because all boolean flags for
	 * driver instances are stored as different bits in one "flags"
	 * variable and we must toggle only the bit specified in the offset
	 * value to control any given boolean attribute.
	 */
	if (EQ(attr->value, on)) {
	    *flagsp |= conf_attr->offset;	/* turn on bit given in offset */
	} else {
	    *flagsp &= ~conf_attr->offset;	/* turn off bit given in offset */
	}
#if 0
	DEBUG1(DBG_CONF_HI, ", final flags = 0x%lx\n", *flagsp);
#endif
	break;

    case t_char:
	/* special case, '-' prefix on char attr name sets char to NUL */
	if (attr->value == off) {	/* pointer equivalence */
	    uptr->v_char = '\0';
	} else if (strlen(attr->value) != 1) {
	    return xprintf("%v: bad character constant for %s: %v",
			   fn, attr_class, attr->name);
	} else {
	    uptr->v_char = attr->value[0];
	}
	break;

    case t_int:
    case t_mode:
	uptr->v_int = (int) c_atol(attr->value, &errmsg);

	if (errmsg) {
	    return xprintf("%v: %s %v: %s",
			   fn, attr_class, attr->name, errmsg);
	}
	break;

    case t_long:
	uptr->v_long = c_atol(attr->value, &errmsg);

	if (errmsg) {
	    return xprintf("%v: %s %v: %s",
			   fn, attr_class, attr->name, errmsg);
	}
	break;

    case t_interval: {
	long val;

	if ((val = ivaltol(attr->value)) < 0) {
	    return xprintf("%v: %v %s: malformed interval value: %v",
			   fn, attr_class, attr->name, attr->value);
	}
	uptr->v_interval = val;
    }
	break;

    case t_double: {
	double val;
	char c;			/* catch sscanf spill over */

	/* should be exactly one item set by sscanf */
	if (sscanf(attr->value, "%lf%c", &val, &c) != 1) {
	    return xprintf("%v: %v %s: malformed floating number: %v",
			   fn, attr_class, attr->name, attr->value);
	}
	uptr->v_double = val;
    }
	break;

    case t_proc:
	if ((errmsg = (*((char * (*)__P((char *, struct attribute *))) conf_attr->uptr))(sp, attr))) {
	    return xprintf("%v: %s", fn, errmsg); /* XXX errmsg leaks? */
	}
	break;

    case t_infoproc:
	return xprintf("%v: %v: is a read-only %s!", fn, attr->name, attr_class);

    default:
	return xprintf("%v: %v: unknown %s type", fn, attr->name, attr_class);
    }

    return (char *) NULL;
}


/*
 * format_attribute - given a attribute, return a formatted value
 *
 * Parameters:
 *	conf_attr	- pointer to attribute entry
 *	name		- name of entry (for errors)
 *	prefix		- (returned) prefix, ie +/- for boolean
 *			  or # for infoproc types
 *	value		- (returned) value, maybe NULL
 *	ptr		- ptr to config struct
 *	flags		- bit values for all boolean flags
 *
 * Data may either be directly pointed to by the attribute struct, as in
 * global config variables, or may be an offset into a struct in which case
 * ptr must be set.
 *
 * Return value is bit mask of which items are valid
 */
int
format_attribute(conf_attr, name, quoted, prefix, value, ptr, flags)
     struct attr_table * conf_attr;	/* The attriute info */
     char * name;			/* The conf variable name */
     int  quoted;			/* whether returned value should be quoted */
     char * * prefix;			/* Any prefix to be added to name */
     char * * value;			/* The value of the variable */
     char * ptr;			/* Pointer to anonymous struct */
     smail_bool_t flags;		/* flags entry from config */
{
    static struct str val;
    static int initialised = FALSE;
    tup_t * vptr;			/* pointer to attr value */
    int ret;				/* return mask */
    long lval;				/* int/long conversion var */

    /*
     * N.B.:  This routine depends on the minimum length of val (which should
     * be STR_BUMP) being reasonable -- i.e. greater than 20, and not being
     * shrunk, because it uses sprintf() to insert certain types of values
     * directly into the allocated storage for val.  If someone tampers behind
     * the scenes expect the whole lot to fall down :-(
     */
    if (!initialised) {
	STR_INIT(&val);
	initialised++;
    } else {
	STR_CHECK(&val);
	STR_CLEAR(&val);
    }

    /* Initialise default values */
    *prefix = NULL;
    *value = NULL;
    ret = 0;

    if (conf_attr == NULL) {
	if (getuid() == 0) {
	    write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
		      "%s: unknown attribute: %v\n", program, name);
	} else if (errfile) {
	    dprintf(errfile, "%s: unknown attribute: %v\n", program, name);
	}
	return ret;
    }
#if 0
    assert(EQ(name, conf_attr->name));
#endif

    if (ptr == NULL) {
	vptr = conf_attr->uptr;
    } else {
	vptr = (tup_t *) ((char *) ptr + conf_attr->offset);
    }

    switch (conf_attr->type) {
    case t_string:
	if (vptr->v_string) {
#ifdef USE_CYRUS_IMAPD
	    /*
	     * XXX  This is a very ugly hack.
	     *
	     * What we need instead is some generic attribute privacy flag....
	     *
	     * Note that full debugging in delivery mode will reveal the secret.
	     */
	    if ((operation_mode == PRINT_VARS_MODE ||
		 (debug > 1 && (operation_mode == TEST_MODE ||
				operation_mode == VERIFY_ADDRS))) &&
		EQ(conf_attr->name, "cyrus_noquota_passphrase")) {
		gid_t cyrus_gid = 0;	/* assume wheel group members are privileged enough */
		struct group *cygr = getgrnam(cyrus_group);
	
		if (cygr) {
		    cyrus_gid = cygr->gr_gid;
		} else {
		    DEBUG2(DBG_CONF_LO, "format_attribute(): cyrus_group of %v is invalid: %s\n", cyrus_group, strerror(errno));
		}
		if (real_uid != 0 && real_gid != cyrus_gid) {
		    vptr->v_string = "This secret is not for you to know!";
		} else {
		    DEBUG3(DBG_CONF_MID,
			   "format_attribute(): reporting passphrase to uid=%d, gid=%d, for cyrus_gid=%d\n",
			   real_uid, real_gid, cyrus_gid);
		}
	    }
#endif
	    if (quoted) {
		if (conf_attr->fmt_hints & FA_FMT_IS_PCRE_LIST) {
		    charplist_t *tlist;
		    char *error;

		    error = NULL;
		    tlist = compile_pcre_list(vptr->v_string, &error);
		    if (!tlist) {
			panic(EX_DATAERR, "error re-compiling %v: %s\n", name, error);
		    }
		    *value = format_pcre_list(tlist);
		    free_pcre_list(tlist);
		} else {
		    *value = quote(vptr->v_string,
				   FALSE,
				   (conf_attr->fmt_hints & FA_FMT_IS_LIST) ? TRUE : FALSE);
		}
	    } else {
		*value = vptr->v_string;
	    }
	    ret = FA_USE_VALUE;
	} else {
	    *prefix = "-";
	    ret = FA_USE_PREFIX;
	}
	break;

    case t_boolean: {
	unsigned long int v2;

	/* Booleans are handled a bit differently in the drivers */
	if (conf_attr->offset != 0) {
	    /* If offset is non-zero, then use it as a mask on flags */
	    v2 = flags & conf_attr->offset;
	} else {
	    v2 = vptr->v_boolean;
	}

	if (v2) {
	    *value = "true";
	    *prefix = "+";
	} else {
	    *value = "false";
	    *prefix = "-";
	}
	ret = FA_USE_PREFIX;
	break;
    }

    case t_char:
	if (vptr->v_char) {
	    STR_NEXT(&val, vptr->v_char);
	    STR_NEXT(&val, '\0');
	    *value = STR(&val);
	    ret = FA_USE_VALUE;
	} else {
	    *value = "\0";
	    *prefix = "-";
	    ret = FA_USE_PREFIX;
	}
	break;
	
    case t_int:
    case t_long:
	if (conf_attr->type == t_int) {
	    lval = vptr->v_int;
	} else {
	    lval = vptr->v_long;
	}
	
	if (lval == 0) {
	    *value = "0";
	    *prefix = "-";
	    ret = FA_USE_PREFIX;
	} else {
	    if ((lval >= M_MULTIPLIER) && !(lval % M_MULTIPLIER)) {
		str_printf(&val, "%ldM", (lval / M_MULTIPLIER));
	    } else if ((lval >= K_MULTIPLIER) && !(lval % K_MULTIPLIER)) {
		str_printf(&val, "%ldK", (lval / K_MULTIPLIER));
	    } else {
		str_printf(&val, "%ld", lval);
	    }
	    *value = STR(&val);
	    ret = FA_USE_VALUE;
	}
	break;

    case t_mode:
	if (vptr->v_int == 0) {
	    *value = "0";
	    *prefix = "-";
	    ret = FA_USE_PREFIX;
	} else {
	    str_printf(&val, "0%o", vptr->v_int);
	    *value = STR(&val);
	    ret = FA_USE_VALUE;
	}
	break;

    case t_interval:
	if (vptr->v_interval == 0) {
	    *value = "0";
	    *prefix = "-";
	    ret = FA_USE_PREFIX;
	} else {
	    if ((*value = ltoival(vptr->v_interval)) == NULL) {
		str_printf(&val, "%ld", vptr->v_interval);
		*value = STR(&val);
	    }
	    ret = FA_USE_VALUE;
	}
	break;

    case t_double:
	(void) sprintf(STR(&val), "%g", vptr->v_double);
	*value = STR(&val);
	ret = FA_USE_VALUE;
	break;

    case t_proc:
	if (errfile) {
	    dprintf(errfile, "%s: %v: cannot print proc attributes\n",
		    program, name);
	}
	break;

    case t_infoproc:
	*value = quote((*((char *(*) __P((char *))) vptr))(name), FALSE, FALSE);
	*prefix = "#";
	ret = FA_USE_VALUE | FA_USE_PREFIX | FA_IS_COMMENT;
	break;

    default:
	if (errfile) {
	    panic(EX_SOFTWARE, "variable %v: unknown type\n", name);
	}
	break;
    }

    return(ret);
}


/*
 * print_config_variable - write the value for a config variable on stdout
 */
void
print_config_variable(name)
    char *name;
{
    struct attr_table *conf_attr;
    char *prefix;
    char *value;
    int ret;

    if (EQ(name, "help")) {
	print_config_help();
	return;
    }
    if (EQ(name, "all") || EQ(name, "CONFIG")) {
	print_config_all();
	return;
    }
    if (EQ(name, "DIRECTORS")) {
	dump_director_config(stdout);
	return;
    }
    if (EQ(name, "ROUTERS")) {
	dump_router_config(stdout);
	return;
    }
    if (EQ(name, "TRANSPORTS")) {
	dump_transport_config(stdout);
	return;
    }
    if (EQ(name, "QUALIFY")) {
	dump_qualify_config(stdout);
	return;
    }
    if (EQ(name, "RETRY")) {
	dump_retry_config(stdout);
	return;
    }
    if (EQ(name, "ALL")) {
	fputs("#\n# -- config file\n#\n", stdout);
	print_config_all();
	fputs("#\n# -- end of config\n#\n", stdout);
	dump_director_config(stdout);
	dump_router_config(stdout);
	dump_transport_config(stdout);
	dump_qualify_config(stdout);
	dump_retry_config(stdout);
	return;
    }
    if (*name != '$') {
	conf_attr = find_config_attribute(name);
	if (conf_attr == NULL) {
	    dprintf(stdout, "%s: unknown configuration attribute: %v\n", program, name);
	} else {
	    smail_bool_t flags = 0;

	    if (conf_attr->type == t_boolean) {
		flags = conf_attr->uptr->v_boolean;
	    }
	    ret = format_attribute(conf_attr, name, ((debug > 0) ? TRUE : FALSE), &prefix, &value, (char *) NULL, flags);
	    if (ret) {
		if (debug) {
		    dprintf(stdout, "%s%v%s%s\n",
			    ((ret & FA_USE_PREFIX) ? prefix : ""),
			    name,
			    ((ret & FA_USE_VALUE) ? "=" : ""),
			    ((ret & FA_USE_VALUE) ? value : "")); /* value is already quoted */
		} else {
		    (void) printf("%s\n", value ? ((*value) ? value : "# (empty-value)") : "# (null-value)");
		}
	    } else {
		(void) printf("print_config_variable(): format_attribute(): impossible result!\n");
	    }
	}
    } else {
	value = expand_string(name, (struct addr *) NULL, (char *) NULL, (char *) NULL);
	if (value) {
	    if (debug >= DBG_CONF_LO) {
		/* assume everything's a list in debug mode */
		dprintf(stdout, "%v = %s\n", name, quote(value,
							     FALSE,
							     (debug) ? TRUE : FALSE));
	    } else {
		dprintf(stdout, "\"%v\"\n", value);
	    }
	    xfree(value);
	}
    }
    return;
}



static const char *
conf_var_typename(attr)
    struct attr_table *attr;
{
    switch (attr->type) {
    case t_string:  return "string";
    case t_boolean: return "boolean";
    case t_char:    return "char";
    case t_int:     return "int";
    case t_mode:    return "mode";
    case t_long:    return "long";
    case t_interval:return "interval";
    case t_double:  return "double";
    case t_proc:    return "special";
    case t_infoproc:return "info";
    default:        return "unkown type";
    }
}

/*
 * print_config_help - output a listing of all config attributes, with type
 */
static void
print_config_help()
{
    register struct attr_table *t;
    register tup_t *last_uptr = NULL;

    /* FIXME:  avoid this ugly end_conf_attributes stuff -- use null-entry termination semantics... */
    for (t = conf_attributes; t < end_conf_attributes; t++) {
	if (t->uptr == last_uptr) {
	    continue;
	}
	last_uptr = t->uptr;
	(void) printf("%s=%s\n", t->name, conf_var_typename(t));
    }
}

/*
 * print_config_all - print all config file variables
 */
static void
print_config_all()
{
    register struct attr_table *t;
    register tup_t *last_uptr = NULL;
    int save_debug = debug;

    debug = 1;
    /* FIXME:  avoid this ugly end_conf_attributes stuff -- use null-entry termination semantics... */
    for (t = conf_attributes; t < end_conf_attributes; t++) {
	if (t->uptr == last_uptr) {
	    continue;			/* skip duplicate names */
	}
	if (t->fmt_hints & FA_FMT_DEPRECATED) {
	    continue;			/* skip deprecated names */
	}
	last_uptr = t->uptr;
	switch(t->type) {
	case t_string:
	case t_boolean:
	case t_char:
	case t_int:
	case t_mode:
	case t_long:
	case t_interval:
	case t_double:
	case t_infoproc:		/* XXX should have t_proc too? */
	    print_config_variable(t->name);
	    break;
	default:
	    (void) printf("%s: has unknown attribute type\n", t->name);
	    break;
	}
    }
    debug = save_debug;
}


/*
 * read_standard_file - read (for example) a transport, director or router file
 *
 * given a template default structure, some information about that structure,
 * and an attr_table describing attributes, read a file of standard
 * attribute descriptions and return a list describing all of the entries
 * in that file.
 *
 * inputs:
 *	f	      - open input file.  File format must conform to what is
 *			expected by read_entry and parse_entry in parse.c.
 *	template      - pointer to the template structure.
 *	struct_size   - size of template structure.
 *	name_offset   - offset to the name element in the template structure.
 *	flags_offset  - offset to flags element of structure.
 *	succ_offset   - offset to succ element of structure.
 *	attr_table    - table defining generic attributes.
 *	end_attr_table- end of the attribute table
 *
 *	driv_function - function to handle driver attributes for each entry.
 *			Also, this function can check the validity of the
 *			generic attributes in the passed structure.  The
 *			function should return NULL, or an error message.
 *			It is called as:
 *
 *			    char *
 *			    f(s, d)
 *				char *s;   --- structure being defined
 *				struct attribute *d;  --- driver attributes
 *
 *			If driv_function is NULL, no driver attributes are
 *			allowed and no function is called.
 *	head	      - pointer to a variable in which to store the head
 *			of the generated structure list.
 *
 * output:
 *	If an error occured, an error message will be returned.
 *	Otherwise NULL will be returned and a pointer to the new
 *	structure list will be stored at the location pointed to by
 *	`head'.
 */
/* FIXME:  avoid this ugly end_attr_table stuff -- use null-entry termination semantics... */
char *
read_standard_file(f, template, struct_size,
		   name_offset, flags_offset, succ_offset,
		   attr_table, end_attr_table,
		   driv_function, head)
    FILE *f;				/* input file */
    register char *template;		/* template structure */
    size_t struct_size;			/* size of template structure */
    int name_offset;			/* name field offset in structure */
    int flags_offset;			/* where to store boolean flags */
    int succ_offset;			/* where to store succ pointer */
    struct attr_table *attr_table;	/* start of attribute table */
    struct attr_table *end_attr_table;	/* e.g., ENDTABLE(attr_table) */
    char *(*driv_function) __P((char *, struct attribute *)); /* function to handle driver attrs */
    char **head;			/* output structure list */
{
    char **link_ptr;			/* pointer to last succ element */
    char *cur;				/* current structure to define */
    char *s;				/* text for current entry */

    DEBUG(DBG_CONF_MID, "read_standard_file called\n");
    /* start out with no structure list entries */
    link_ptr = head;
    *link_ptr = NULL;

    /*
     * scan through reading entries.  For each entry allocate a
     * structure and fill it in.  Also, link the new structure to the
     * previous structure.
     */
    while ((s = read_entry(f))) {
	char *error;
	struct attribute *generic_attrs; /* generic attributes for entry */
	struct attribute *driver_attrs;	/* driver attributes for entry */
	char *name;			/* name of entry */

	/* create the new structure and forward link to it */
	*link_ptr = cur = xmalloc(struct_size);
	link_ptr = (char **)(cur + succ_offset);

	/* fill in the entry structure, starting with the template */
	(void) memcpy(cur, template, (size_t) struct_size);

	/* get all of the attributes */
	name = parse_entry(s, &generic_attrs, &driver_attrs, &error);
	if (name == NULL) {
#if 0
	    assert(error);
#endif
	    return error;
	}

	/* fill in the entry name */
	*(char **)(cur + name_offset) = name;

	/*
	 * fill in the generic attributes, calling attr_function for
	 * attributes not in attr_table
	 */
	/* FIXME:  avoid this ugly end_attr_table stuff -- use null-entry termination semantics... */
	error = fill_attributes(cur, generic_attrs,
				(smail_bool_t *) (cur + flags_offset),
				attr_table, end_attr_table, name);
	if (error) {
	    return xprintf("in entry %s", error); /* name is already included in error */
	}
	if (driv_function) {
	    error = (*driv_function)(cur, driver_attrs);
	    if (error) {
		return xprintf("in entry %v: %s", name, error);
	    }
	} else if (driver_attrs) {
	    return xprintf("in entry %v: no driver attributes allowed in file", name);
	}
    }

    /* everything went okay */
    return NULL;
}


/* 
 * dump_standard_config - print out the config used
 *
 * The reverse of read_standard_file - this prints out something
 * like the original config file!
 */
/* FIXME:  avoid this ugly end_attr_table stuff -- use null-entry termination semantics... */
void
dump_standard_config(f, config, name, flags, attr_table, end_attr_table)
    FILE *f;				/* output file */
    register char *config;		/* filled in config */
    char * name;			/* name of driver */
    smail_bool_t flags;			/* boolean flags */
    struct attr_table *attr_table;	/* start of attribute table */
    struct attr_table *end_attr_table;	/* e.g., ENDTABLE(attr_table) */
{
    int ret;				/* return mask from format_attribue */
    struct attr_table *attr;		/* attribute entry */
    char * prefix;			/* returned prefix */
    char * value;			/* returned value */

    /* FIXME:  avoid this ugly end_attr_table stuff -- use null-entry termination semantics... */
    for (attr = attr_table; (attr < end_attr_table); attr++) {
	if (attr->fmt_hints & FA_FMT_DEPRECATED) {
	    continue;			/* skip deprecated items */
	}
	if (attr->type != t_proc) {
	    ret = format_attribute(attr, name, 1, &prefix, &value, config, flags);
	    if (ret) {
		fprintf(f, "%s%s%s%s%s%s,\n",
			((ret & FA_IS_COMMENT) ? "" : "\t"),
			((ret & FA_USE_PREFIX) ? prefix : ""),
			((ret & FA_IS_COMMENT) ? "\t" : ""),
			attr->name,
			((ret & FA_USE_VALUE) ? "=" : ""),
			((ret & FA_USE_VALUE) ? value : ""));
	    }
	}
    }
}



/*
 * fill_attributes - build a structure and fill in attributes
 *
 * Given a list of attributes extracted from one of the configuration
 * files and an array of known attributes, fill in a stucture to which
 * those attributes pertain.
 *
 * inputs:
 *	sp		- pointer to stucture that is to be filled in
 *	attrs		- list of given attributes
 *	flags		- pointer to long where boolean flags should be OR'd in
 *	attr_table	- table describing known attributes
 *	end_attr_table	- end of known attribies (i.e., ENDTABLE(attr_table))
 *	name		- combined filename and entry name where attrs were read from
 *
 * output:
 *	returns either an error message, or NULL if everything went okay.
 */
/* FIXME:  avoid this ugly end_attr_table stuff -- use null-entry termination semantics... */
char *
fill_attributes(sp, attrs, flagsp, attr_table, end_attr_table, name)
    char *sp;
    struct attribute *attrs;
    smail_bool_t *flagsp;
    struct attr_table *attr_table;
    struct attr_table *end_attr_table;
    char *name;
{
    struct attribute *attr;
    char *error;

    /* step through all of the attributes from the file entry */
    for (attr = attrs; attr; attr = attr->succ) {
	register struct attr_table *conf_attr;	/* table entry */

	/* find the attribute in the list of known attributes */
	/* FIXME:  avoid this ugly end_attr_table stuff -- use null-entry termination semantics... */
	conf_attr = find_attribute(attr->name, attr_table, end_attr_table);
	if (conf_attr == NULL) {
	    /* didn't find the attribute */
	    return xprintf("%s: unknown attribute", attr->name);
	}

	/* check if boolean value used was properly */
	if ((error = check_type(conf_attr, attr, name, "attribute"))) {
	    return error;
	}

	/* check for deprecated names */
	if (conf_attr->fmt_hints & FA_FMT_DEPRECATED) {
	    if (getuid() == 0) {
		write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
			  "%s: attribute name (in %v) is deprecated -- see manual",
			  attr->name, name);
	    } else if (errfile) {
		dprintf(errfile,
			"%s: %s: attribute name (in %v) is deprecated -- see manual\n",
			program, attr->name, name);
	    }
	}

	/* fill in the attribute given the string representation from attr */
	if ((error = convert_value(conf_attr, attr, (u_attr_t *) (sp + conf_attr->offset), flagsp, sp, name, "attribute"))) {
	    return error;
	}
    }

    return NULL;
}

/*
 * find_attribute - find an attribute within an attribute table
 */
/* FIXME:  avoid this ugly "end" stuff -- use null-entry termination semantics... */
static struct attr_table *
find_attribute(name, table, end)
    register char *name;		/* name to search for */
    register struct attr_table *table;	/* table to search in */
    register struct attr_table *end;	/* end of table */
{
    /* FIXME:  avoid this ugly stuff -- use null-entry termination semantics... */
    while (table < end) {
	if (EQ(name, table->name)) {
	    return table;
	}
	table++;
    }
    return NULL;
}

struct attr_table *
find_config_attribute(name)
    char *name;
{
    /* FIXME:  avoid this ugly end_conf_attributes stuff -- use null-entry termination semantics... */
    return find_attribute(name, conf_attributes, end_conf_attributes);
}


/*
 * add_config_stat - add a new config file to config_stat_list
 *
 * Pass a filename plus a pointer to a stat structure or NULL.
 */
void
add_config_stat(fn, statp)
    char *fn;				/* name of file */
    struct stat *statp;			/* stat of the file, or NULL */
{
    struct config_stat *new = (struct config_stat *)xmalloc(sizeof(*new));

    DEBUG2(DBG_CONF_HI, "add_config_stat(%s, %s) called\n", fn, statp ? "FOUND" : "MISSING");
    new->fn = COPY_STRING(fn);
    if (statp) {
	new->mtime = statp->st_mtime;
	new->dev = statp->st_dev;
	new->ino = statp->st_ino;
    } else {
	new->mtime = 0;
    }
    new->succ = config_stat_list;
    config_stat_list = new;
}

/*
 * is_newconf - check if we have a new set of configuration files
 *
 * returns TRUE if a configuration file has changed, FALSE otherwise
 */
int
is_newconf()
{
    struct stat statbuf;
    struct config_stat *csp;

    for (csp = config_stat_list; csp; csp = csp->succ) {
	if (stat(csp->fn, &statbuf) < 0) {
	    if (csp->mtime != (time_t)0) {
		/* file no longer exists */
		return TRUE;
	    }
	} else {
	    if (csp->mtime == (time_t)0) {
		/* file now exists */
		return TRUE;
	    }
	    if (csp->mtime != statbuf.st_mtime ||
		csp->dev != statbuf.st_dev ||
		csp->ino != statbuf.st_ino)
	    {
		/* existing file has changed */
		return TRUE;
	    }
	}
    }

    /* nothing has changed */
    return FALSE;
}


#ifdef	STANDALONE

struct transport *transports;
struct director *directors;
struct router *routers;

/*
 * find_direct_driver - given a driver's name, return the driver structure
 *
 * return NULL if driver does not exist.
 */
struct direct_driver *
find_direct_driver(name)
    register char *name;		/* search key */
{
#ifdef	notyet
    register struct direct_driver *ddp;	/* pointer to table of drivers */

    for (ddp = direct_drivers; ddp->name; ddp++) {
	if (EQ(ddp->name, name)) {
	    return ddp;			/* found the driver */
	}
    }

    return NULL;			/* driver not found */
#else	/* notyet */
    static struct direct_driver dd = {
	NULL, 1, NULL, NULL, NULL, NULL,
    };

    return &dd;
#endif	/* notyet */
}

/*
 * find_transport_driver - given a driver's name, return the driver structure
 *
 * return NULL if driver does not exist.
 */
struct transport_driver *
find_transport_driver(name)
    register char *name;		/* search key */
{
#ifdef	notyet
    register struct transport_driver *tdp; /* pointer to table of drivers */

    for (tdp = transport_drivers; tdp->name; tdp++) {
	if (EQ(tdp->name, name)) {
	    return tdp;			/* found the driver */
	}
    }

    return NULL;			/* driver not found */
#else	/* notyet */
    static struct transport_driver td = {
	NULL, 1, NULL, NULL, NULL, NULL,
    };
    return &td;
#endif	/* notyet */
}

/*
 * find_route_driver - given a driver's name, return the driver structure
 *
 * return NULL if driver does not exist.
 */
struct route_driver *
find_route_driver(name)
    register char *name;		/* search key */
{
#ifdef	notyet
    register struct route_driver *rdp;	/* pointer to table of drivers */

    for (rdp = route_drivers; rdp->name; rdp++) {
	if (EQ(rdp->name, name)) {
	    return rdp;			/* found the driver */
	}
    }

    return NULL;			/* driver not found */
#else	/* notyet */
    static struct route_driver rd = {
	NULL, 1, NULL, NULL, NULL, NULL,
    };
    return &rd;
#endif	/* notyet */
}

void
main(argc, argv)
    int argc;				/* count of arguments */
    char *argv[];			/* vector of arguments */
{
    char *error;

    if (argc != 2) {
	(void) fprintf(stderr, "Usage: %s config-file", argv[0]);
	/*NOTREACHED*/
    }

    config_file = argv[1];
    if ((error = read_config_file(config_file, CONFIG_PRIMARY)) ||
	(second_config_file &&
	 error = read_config_file(second_config_file, CONFIG_SECONDARY))
	(error = read_transport_file()) ||
	(error = read_router_file()) ||
	(error = read_director_file()) ||
	(error = read_qualify_file()) ||
	(error = read_retry_file()))
    {
	(void) fprintf(stderr, "%s: %s\n", argv[0], error);
    }
}
#endif	/* STANDALONE */

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */
