/*++
/* NAME
/*	postconf_dbms 3
/* SUMMARY
/*	legacy support for database-defined main.cf parameter names
/* SYNOPSIS
/*	#include <postconf.h>
/*
/*	void	pcf_register_dbms_parameters(mode, param_value, flag_parameter,
/*					local_scope)
/*	int	mode;
/*	const char *param_value;
/*	const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *);
/*	PCF_MASTER_ENT *local_scope;
/* DESCRIPTION
/*	This module implements legacy support for database configuration
/*	where main.cf parameter names are generated by prepending
/*	the database name to a database-defined suffix.
/*
/*	Arguments:
/* .IP mode
/*	If PCF_WARN_UNUSED_DEPRECATED is set, warn about unused
/*	database settings.
/* .IP param_value
/*	A parameter value to be searched for "type:table" strings.
/*	When a database type is found that supports legacy-style
/*	configuration, the table name is combined with each of the
/*	database-defined suffixes to generate candidate parameter
/*	names for that database type; if the table name specifies
/*	a client configuration file, that file is scanned for unused
/*	parameter settings.
/* .IP flag_parameter
/*	A function that takes as arguments a candidate parameter
/*	name, parameter flags, and a PCF_MASTER_ENT pointer.  The
/*	function will flag the parameter as "used" if it has a
/*	"name=value" entry in the local or global namespace.
/* .IP local_scope
/*	The local namespace.
/* DIAGNOSTICS
/*	No explicit diagnostics.
/* 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
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

/* Utility library. */

#include <stringops.h>
#include <split_at.h>
#include <mac_expand.h>
#include <dict.h>
#include <msg.h>
#include <mymalloc.h>

/* Global library. */

#include <mail_conf.h>
#include <mail_params.h>
#include <dict_ht.h>
#include <dict_proxy.h>
#include <dict_ldap.h>
#include <dict_mysql.h>
#include <dict_pgsql.h>
#include <dict_sqlite.h>
#include <dict_memcache.h>
#include <dict_mongodb.h>
#include <dict_regexp.h>
#include <dict_pcre.h>

/* Application-specific. */

#include <postconf.h>

 /*
  * SLMs.
  */
#define STR(x)	vstring_str(x)

#ifdef LEGACY_DBMS_SUPPORT

 /*
  * The legacy database interface automagically instantiates a list of
  * parameters by prepending the table name to database-specific suffixes.
  */

/* See ldap_table(5). */

static const char *pcf_ldap_suffixes[] = {
#include "pcf_ldap_suffixes.h"
    0,
};

/* See mysql_table(5). */

static const char *pcf_mysql_suffixes[] = {
#include "pcf_mysql_suffixes.h"
    0,
};

/* See pgsql_table(5). */

static const char *pcf_pgsql_suffixes[] = {
#include "pcf_pgsql_suffixes.h"
    0,
};

/* See sqlite_table(5). */

static const char *pcf_sqlite_suffixes[] = {
#include "pcf_sqlite_suffixes.h"
    0,
};

/* See memcache_table(5). */

static const char *pcf_memcache_suffixes[] = {
#include "pcf_memcache_suffixes.h"
    0,
};

/* See mongodb_table(5). */

static const char *pcf_mongodb_suffixes[] = {
#include "pcf_mongodb_suffixes.h"
    0,
};

 /*
  * Bundle up the database types and their suffix lists.
  */
typedef struct {
    const char *db_type;
    int     db_class;
    const char **db_suffixes;
} PCF_DBMS_INFO;

#define PCF_DBMS_CLASS_CLIENT	(1)	/* DB name is client config path */
#define PCF_DBMS_CLASS_REGEX	(2)	/* DB name contains regex patterns */

static const PCF_DBMS_INFO pcf_dbms_info[] = {
    {DICT_TYPE_LDAP, PCF_DBMS_CLASS_CLIENT, pcf_ldap_suffixes},
    {DICT_TYPE_MYSQL, PCF_DBMS_CLASS_CLIENT, pcf_mysql_suffixes},
    {DICT_TYPE_PGSQL, PCF_DBMS_CLASS_CLIENT, pcf_pgsql_suffixes},
    {DICT_TYPE_SQLITE, PCF_DBMS_CLASS_CLIENT, pcf_sqlite_suffixes},
    {DICT_TYPE_MEMCACHE, PCF_DBMS_CLASS_CLIENT, pcf_memcache_suffixes},
    {DICT_TYPE_MONGODB, PCF_DBMS_CLASS_CLIENT, pcf_mongodb_suffixes},
    {DICT_TYPE_REGEXP, PCF_DBMS_CLASS_REGEX},
    {DICT_TYPE_PCRE, PCF_DBMS_CLASS_REGEX},
    {0},
};

 /*
  * Workaround to prevent a false warning about "#comment after other text",
  * when an inline pcre or regexp pattern contains "#text".
  */
#define PCF_DBMS_RECURSE	1	/* Parse inline {map-entry} */
#define PCF_DBMS_NO_RECURSE	0	/* Don't parse inline {map-entry} */

/* pcf_check_dbms_client - look for unused names in client configuration */

static void pcf_check_dbms_client(int mode, const PCF_DBMS_INFO *dp,
				          const char *cf_file)
{
    DICT   *dict;
    VSTREAM *fp;
    const char **cpp;
    const char *name;
    const char *value;
    char   *dict_spec;
    int     dir;

    /*
     * We read each database client configuration file into its own
     * dictionary, and nag only the first time that a file is visited.
     */
    dict_spec = concatenate(dp->db_type, ":", cf_file, (char *) 0);
    if ((dict = dict_handle(dict_spec)) == 0) {
	struct stat st;

	/*
	 * Populate the dictionary with settings in this database client
	 * configuration file. Don't die if a file can't be opened - some
	 * files may contain passwords and should not be world-readable.
	 * Note: dict_load_fp() nags about duplicate parameter settings.
	 */
	dict = dict_ht_open(dict_spec, O_CREAT | O_RDWR, 0);
	dict_register(dict_spec, dict);
	if ((fp = vstream_fopen(cf_file, O_RDONLY, 0)) == 0) {
	    if (errno != EACCES)
		msg_warn("open \"%s\" configuration \"%s\": %m",
			 dp->db_type, cf_file);
	    myfree(dict_spec);
	    return;
	}
	if (fstat(vstream_fileno(fp), &st) == 0 && !S_ISREG(st.st_mode)) {
	    msg_warn("open \"%s\" configuration \"%s\": not a regular file",
		     dp->db_type, cf_file);
	    myfree(dict_spec);
	    (void) vstream_fclose(fp);
	    return;
	}
	dict_load_fp(dict_spec, fp);
	if (vstream_fclose(fp)) {
	    msg_warn("read \"%s\" configuration \"%s\": %m",
		     dp->db_type, cf_file);
	    myfree(dict_spec);
	    return;
	}

	/*
	 * Remove all known database client parameters from this dictionary,
	 * then report the remaining ones as "unused". We use ad-hoc logging
	 * code, because a database client parameter namespace is unlike the
	 * parameter namespaces in main.cf or master.cf.
	 */
	if (mode & PCF_WARN_UNUSED_DEPRECATED) {
	    for (cpp = dp->db_suffixes; *cpp; cpp++)
		(void) dict_del(dict, *cpp);
	    for (dir = DICT_SEQ_FUN_FIRST;
	      dict->sequence(dict, dir, &name, &value) == DICT_STAT_SUCCESS;
		 dir = DICT_SEQ_FUN_NEXT)
		msg_warn("%s: unused parameter: %s=%s", dict_spec, name, value);
	}
    }
    myfree(dict_spec);
}

/* pcf_register_dbms_helper - parse one possible database type:name */

static void pcf_register_dbms_helper(int mode, char *str_value,
         const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
				             PCF_MASTER_ENT *local_scope,
				             int recurse)
{
    const PCF_DBMS_INFO *dp;
    char   *db_type;
    char   *prefix;
    static VSTRING *candidate = 0;
    const char **cpp;
    char   *err;

    /*
     * Naive parsing. We don't really know if this substring specifies a
     * database or some other text.
     */
    while ((db_type = mystrtokq_cw(&str_value, CHARS_COMMA_SP, CHARS_BRACE,
	 local_scope ? pcf_get_master_path() : pcf_get_main_path())) != 0) {
	if (*db_type == CHARS_BRACE[0]) {
	    if ((err = extpar(&db_type, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) {
		/* XXX Encapsulate this in pcf_warn() function. */
		if (local_scope)
		    msg_warn("%s:%s: %s", pcf_get_master_path(),
			     local_scope->name_space, err);
		else
		    msg_warn("%s: %s", pcf_get_main_path(), err);
		myfree(err);
	    }
	    if (recurse)
		pcf_register_dbms_helper(mode, db_type, flag_parameter,
					 local_scope, recurse);
	    continue;
	}

	/*
	 * Skip over "proxy:" maptypes, to emulate the proxymap(8) server's
	 * behavior when opening a local database configuration file.
	 */
	while ((prefix = split_at(db_type, ':')) != 0
	       && strcmp(db_type, DICT_TYPE_PROXY) == 0)
	    db_type = prefix;

	if (prefix == 0)
	    continue;

	/*
	 * Look for database:prefix where the prefix is an absolute pathname.
	 * Then, report unknown database client configuration parameters.
	 * 
	 * XXX What about a pathname beginning with '.'? This supposedly is
	 * relative to the queue directory, which is the default directory
	 * for all Postfix daemon processes. This would also have to handle
	 * the case that the queue is not yet created.
	 */
	if (*prefix == '/') {
	    for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
		if (strcmp(db_type, dp->db_type) == 0) {
		    if (dp->db_class == PCF_DBMS_CLASS_CLIENT)
			pcf_check_dbms_client(mode, dp, prefix);
		    break;
		}
	    }
	    continue;
	}

	/*
	 * Look for database:prefix where the prefix is not a pathname and
	 * the database is a known type. Synthesize candidate parameter names
	 * from the user-defined prefix and from the database-defined suffix
	 * list, and see if those parameters have a "name=value" entry in the
	 * local or global namespace.
	 */
	if (*prefix != '.') {
	    int     next_recurse = recurse;

	    if (*prefix == CHARS_BRACE[0]) {
		if ((err = extpar(&prefix, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) {
		    /* XXX Encapsulate this in pcf_warn() function. */
		    if (local_scope)
			msg_warn("%s:%s: %s", pcf_get_master_path(),
				 local_scope->name_space, err);
		    else
			msg_warn("%s: %s", pcf_get_main_path(), err);
		    myfree(err);
		}
		for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
		    if (strcmp(db_type, dp->db_type) == 0) {
			if (dp->db_class == PCF_DBMS_CLASS_REGEX)
			    next_recurse = PCF_DBMS_NO_RECURSE;
			break;
		    }
		}
		pcf_register_dbms_helper(mode, prefix, flag_parameter,
					 local_scope, next_recurse);
		continue;
	    } else {
		for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
		    if (strcmp(db_type, dp->db_type) == 0) {
			if (dp->db_class == PCF_DBMS_CLASS_CLIENT) {
			    for (cpp = dp->db_suffixes; *cpp; cpp++) {
				vstring_sprintf(candidate ? candidate :
					    (candidate = vstring_alloc(30)),
						"%s_%s", prefix, *cpp);
				flag_parameter(STR(candidate),
				  PCF_PARAM_FLAG_DBMS | PCF_PARAM_FLAG_USER,
					       local_scope);
			    }
			}
			break;
		    }
		}
	    }
	}
    }
}

/* pcf_register_dbms_parameters - look for database_type:prefix_name */

void    pcf_register_dbms_parameters(int mode, const char *param_value,
         const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
				             PCF_MASTER_ENT *local_scope)
{
    char   *bufp;
    static VSTRING *buffer = 0;

    /*
     * XXX This does not examine both sides of conditional macro expansion,
     * and may expand the "wrong" conditional macros. This is the best we can
     * do for legacy database configuration support.
     */
    if (buffer == 0)
	buffer = vstring_alloc(100);
    bufp = pcf_expand_parameter_value(buffer, PCF_SHOW_EVAL, param_value,
				      local_scope);
    pcf_register_dbms_helper(mode, bufp, flag_parameter, local_scope,
			     PCF_DBMS_RECURSE);
}

#endif
