/*++
/* NAME
/*	attr_override 3
/* SUMMARY
/*	apply name=value settings from string
/* SYNOPSIS
/*	#include <attr_override.h>
/*
/*	void	attr_override(bp, delimiters, parens, ... CA_ATTR_OVER_END);
/*	char	*bp;
/*	const char *delimiters;
/*	const char *parens;
/* DESCRIPTION
/*	This routine updates the values of known in-memory variables
/*	based on the name=value specifications from an input string.
/*	The input format supports parentheses around name=value to
/*	allow whitespace around "=" and within values.
/*
/*	This may be used, for example, with client endpoint
/*	specifications or with policy tables to allow selective
/*	overrides of global main.cf parameter settings (timeouts,
/*	fall-back policies, etc.).
/*
/*	Arguments:
/* .IP bp
/*	Pointer to input string. The input is modified.
/* .IP "delimiters, parens"
/*	See mystrtok(3) for description. Typical values are
/*	CHARS_COMMA_SP and CHARS_BRACE, respectively.
/* .PP
/*	The parens argument is followed by a list of macros
/*	with arguments. Each macro may appear only once.  The list
/*	must be terminated with CA_ATTR_OVER_END which has no argument.
/*	The following describes the expected values.
/* .IP "CA_ATTR_OVER_STR_TABLE(const ATTR_OVER_STR *)"
/*	The macro argument specifies a null-terminated table with
/*	attribute names, assignment targets, and range limits which
/*	should be the same as for the corresponding main.cf parameters.
/* .IP "CA_ATTR_OVER_TIME_TABLE(const ATTR_OVER_TIME *)"
/*	The macro argument specifies a null-terminated table with
/*	attribute names, their default time units (leading digits
/*	are skipped), assignment targets, and range limits which
/*	should be the same as for the corresponding main.cf parameters.
/* .IP "CA_ATTR_OVER_INT_TABLE(const ATTR_OVER_INT *)"
/*	The macro argument specifies a null-terminated table with
/*	attribute names, assignment targets, and range limits which
/*	should be the same as for the corresponding main.cf parameters.
/* SEE ALSO
/*	mystrtok(3), safe tokenizer
/*	extpar(3), extract text from parentheses
/*	split_nameval(3), name-value splitter
/* DIAGNOSTICS
/*	Panic: interface violations.
/*
/*	Fatal errors: memory allocation problem, syntax error,
/*	out-of-range error.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

 /*
  * System library.
  */
#include <sys_defs.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>			/* strtol() */

 /*
  * Utility library.
  */
#include <msg.h>
#include <stringops.h>

 /*
  * Global library.
  */
#include <mail_conf.h>
#include <conv_time.h>
#include <attr_override.h>

/* attr_override - apply settings from list of attribute=value pairs */

void    attr_override(char *cp, const char *sep, const char *parens,...)
{
    static const char myname[] = "attr_override";
    va_list ap;
    int     idx;
    char   *nameval;
    const ATTR_OVER_INT *int_table = 0;
    const ATTR_OVER_STR *str_table = 0;
    const ATTR_OVER_TIME *time_table = 0;

    /*
     * Get the lookup tables and assignment targets.
     */
    va_start(ap, parens);
    while ((idx = va_arg(ap, int)) != ATTR_OVER_END) {
	switch (idx) {
	case ATTR_OVER_INT_TABLE:
	    if (int_table)
		msg_panic("%s: multiple ATTR_OVER_INT_TABLE", myname);
	    int_table = va_arg(ap, const ATTR_OVER_INT *);
	    break;
	case ATTR_OVER_STR_TABLE:
	    if (str_table)
		msg_panic("%s: multiple ATTR_OVER_STR_TABLE", myname);
	    str_table = va_arg(ap, const ATTR_OVER_STR *);
	    break;
	case ATTR_OVER_TIME_TABLE:
	    if (time_table)
		msg_panic("%s: multiple ATTR_OVER_TIME_TABLE", myname);
	    time_table = va_arg(ap, const ATTR_OVER_TIME *);
	    break;
	default:
	    msg_panic("%s: unknown argument type: %d", myname, idx);
	}
    }
    va_end(ap);

    /*
     * Process each attribute=value override in the input string.
     */
    while ((nameval = mystrtokq(&cp, sep, parens)) != 0) {
	int     found = 0;
	char   *key;
	char   *value;
	const char *err;
	const ATTR_OVER_INT *ip;
	const ATTR_OVER_STR *sp;
	const ATTR_OVER_TIME *tp;
	int     int_val;
	int     def_unit;
	char   *end;
	long    longval;

	/*
	 * Split into name and value.
	 */
	/* { name = value } */
	if (*nameval == parens[0]
	    && (err = extpar(&nameval, parens, EXTPAR_FLAG_NONE)) != 0)
	    msg_fatal("%s in \"%s\"", err, nameval);
	if ((err = split_nameval(nameval, &key, &value)) != 0)
	    msg_fatal("malformed option: %s: \"...%s...\"", err, nameval);

	/*
	 * Look up the name and apply the value.
	 */
	for (sp = str_table; sp != 0 && found == 0 && sp->name != 0; sp++) {
	    if (strcmp(sp->name, key) != 0)
		continue;
	    check_mail_conf_str(sp->name, value, sp->min, sp->max);
	    sp->target[0] = value;
	    found = 1;
	}
	for (ip = int_table; ip != 0 && found == 0 && ip->name != 0; ip++) {
	    if (strcmp(ip->name, key) != 0)
		continue;
	    /* XXX Duplicated from mail_conf_int(3). */
	    errno = 0;
	    int_val = longval = strtol(value, &end, 10);
	    if (*value == 0 || *end != 0 || errno == ERANGE
		|| longval != int_val)
		msg_fatal("bad numerical configuration: %s = %s", key, value);
	    check_mail_conf_int(key, int_val, ip->min, ip->max);
	    ip->target[0] = int_val;
	    found = 1;
	}
	for (tp = time_table; tp != 0 && found == 0 && tp->name != 0; tp++) {
	    if (strcmp(tp->name, key) != 0)
		continue;
	    def_unit = tp->defval[strspn(tp->defval, "0123456789")];
	    if (conv_time(value, &int_val, def_unit) == 0)
		msg_fatal("%s: bad time value or unit: %s", key, value);
	    check_mail_conf_time(key, int_val, tp->min, tp->max);
	    tp->target[0] = int_val;
	    found = 1;
	}
	if (found == 0)
	    msg_fatal("unknown option: \"%s = %s\"", key, value);
    }
}
