/*++
/* NAME
/*	dict_union 3
/* SUMMARY
/*	dictionary manager interface for union of tables
/* SYNOPSIS
/*	#include <dict_union.h>
/*
/*	DICT	*dict_union_open(name, open_flags, dict_flags)
/*	const char *name;
/*	int	open_flags;
/*	int	dict_flags;
/* DESCRIPTION
/*	dict_union_open() opens a sequence of one or more tables.
/*	Example: "\fBunionmap:{\fItype_1:name_1, ..., type_n:name_n\fR}".
/*
/*	Each "unionmap:" query is given to each table in the specified
/*	order. All found results are concatenated, separated by
/*	comma.  The unionmap table produces no result when all
/*	lookup tables return no result.
/*
/*	The first and last characters of a "unionmap:" table name
/*	must be '{' and '}'. Within these, individual maps are
/*	separated with comma or whitespace.
/*
/*	The open_flags and dict_flags arguments are passed on to
/*	the underlying dictionaries.
/* SEE ALSO
/*	dict(3) generic dictionary manager
/* 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
/*	porcupine.org
/*--*/

/* System library. */

#include <sys_defs.h>
#include <string.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <htable.h>
#include <dict.h>
#include <dict_union.h>
#include <stringops.h>
#include <vstring.h>

/* Application-specific. */

typedef struct {
    DICT    dict;			/* generic members */
    ARGV   *map_union;			/* pipelined tables */
    VSTRING *re_buf;			/* reply buffer */
} DICT_UNION;

#define STR(x) vstring_str(x)

/* dict_union_lookup - search a bunch of tables and combine the results */

static const char *dict_union_lookup(DICT *dict, const char *query)
{
    static const char myname[] = "dict_union_lookup";
    DICT_UNION *dict_union = (DICT_UNION *) dict;
    DICT   *map;
    char  **cpp;
    char   *reg_name;
    const char *result = 0;

    /*
     * After Roel van Meer, postfix-users mailing list, Sept 2014.
     */
    VSTRING_RESET(dict_union->re_buf);
    for (cpp = dict_union->map_union->argv; (reg_name = *cpp) != 0; cpp++) {
	if ((map = dict_handle(reg_name)) == 0)
	    msg_panic("%s: dictionary \"%s\" not found", myname, reg_name);
	if ((result = dict_get(map, query)) != 0) {
	    if (VSTRING_LEN(dict_union->re_buf) > 0)
		VSTRING_ADDCH(dict_union->re_buf, ',');
	    vstring_strcat(dict_union->re_buf, result);
	} else if (map->error != 0) {
	    DICT_ERR_VAL_RETURN(dict, map->error, 0);
	}
    }
    DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE,
			VSTRING_LEN(dict_union->re_buf) > 0 ?
			STR(dict_union->re_buf) : 0);
}

/* dict_union_close - disassociate from a bunch of tables */

static void dict_union_close(DICT *dict)
{
    static const char myname[] = "dict_union_close";
    DICT_UNION *dict_union = (DICT_UNION *) dict;
    DICT   *map;
    char  **cpp;
    char   *reg_name;

    for (cpp = dict_union->map_union->argv; (reg_name = *cpp) != 0; cpp++) {
	if ((map = dict_handle(reg_name)) == 0)
	    msg_panic("%s: dictionary \"%s\" not found", myname, reg_name);
	dict_close(map);
    }
    argv_free(dict_union->map_union);
    vstring_free(dict_union->re_buf);
    dict_free(dict);
}

/* dict_union_open - open a bunch of tables */

DICT   *dict_union_open(const char *name, int open_flags, int dict_flags)
{
    static const char myname[] = "dict_union_open";
    DICT_UNION *dict_union;
    char   *saved_name = 0;
    char   *dict_type_name;
    ARGV   *argv = 0;
    char  **cpp;
    DICT   *dict;
    int     match_flags = 0;
    struct DICT_OWNER aggr_owner;
    size_t  len;

    /*
     * Clarity first. Let the optimizer worry about redundant code.
     */
#define DICT_UNION_RETURN(x) do { \
	      if (saved_name != 0) \
		  myfree(saved_name); \
	      if (argv != 0) \
		  argv_free(argv); \
	      return (x); \
	  } while (0)

    /*
     * Sanity checks.
     */
    if (open_flags != O_RDONLY)
	DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
					 open_flags, dict_flags,
				  "%s:%s map requires O_RDONLY access mode",
					 DICT_TYPE_UNION, name));

    /*
     * Split the table name into its constituent parts.
     */
    if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
	|| *(saved_name = mystrndup(name + 1, len - 2)) == 0
	|| ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)),
	    (argv->argc == 0)))
	DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
					 open_flags, dict_flags,
					 "bad syntax: \"%s:%s\"; "
					 "need \"%s:{type:name...}\"",
					 DICT_TYPE_UNION, name,
					 DICT_TYPE_UNION));

    /*
     * Check all underlying table specs before registering any of them to
     * avoid leaking refcounts if one of them is bad.
     */
    for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
	if (strchr(dict_type_name, ':') == 0)
	    DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
					     open_flags, dict_flags,
					     "bad syntax: \"%s:%s\"; "
					     "need \"%s:{type:name...}\"",
					     DICT_TYPE_UNION, name,
					     DICT_TYPE_UNION));
    }

    /*
     * The least-trusted table in the set determines the over-all trust
     * level. The first table determines the pattern-matching flags.
     */
    DICT_OWNER_AGGREGATE_INIT(aggr_owner);
    for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
	if (msg_verbose)
	    msg_info("%s: %s", myname, dict_type_name);
	dict = dict_open(dict_type_name, open_flags, dict_flags);
	argv_replace_one(argv, cpp - argv->argv, dict->reg_name);
	DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
	if (cpp == argv->argv)
	    match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
    }

    /*
     * Bundle up the result.
     */
    dict_union =
	(DICT_UNION *) dict_alloc(DICT_TYPE_UNION, name, sizeof(*dict_union));
    dict_union->dict.lookup = dict_union_lookup;
    dict_union->dict.close = dict_union_close;
    dict_union->dict.flags = dict_flags | match_flags;
    dict_union->dict.owner = aggr_owner;
    dict_union->re_buf = vstring_alloc(100);
    dict_union->map_union = argv;
    argv = 0;
    DICT_UNION_RETURN(&dict_union->dict);
}
