/*++
/* NAME
/*	map_search_search 3
/* SUMMARY
/*	lookup table search list support
/* SYNOPSIS
/*	#include <map_search_search.h>
/*
/*	typedef struct {
/* .in +4
/*	    char   *map_type_name;	/* type:name, owned */
/*	    char   *search_order;	/* null or owned */
/* .in -4
/*	} MAP_SEARCH;
/*
/*	void	map_search_init(
/*	const NAME_CODE *search_actions)
/*
/*	const MAP_SEARCH *map_search_create(
/*	const char *map_spec)
/*
/*	const MAP_SEARCH *map_search_lookup(
/*	const char *map_spec);
/* DESCRIPTION
/*	This module implements configurable search order support
/*	for Postfix lookup tables.
/*
/*	map_search_init() must be called once, before other functions
/*	in this module.
/*
/*	map_search_create() creates a MAP_SEARCH instance for
/*	map_spec, ignoring duplicate requests.
/*
/*	map_search_lookup() looks up the MAP_SEARCH instance that
/*	was created by map_search_create().
/*
/*	Arguments:
/* .IP search_actions
/*	The mapping from search action string form to numeric form.
/*	The numbers must be in the range [1..126] (inclusive). The
/*	value 0 is reserved for the MAP_SEARCH.search_order terminator,
/*	and the value MAP_SEARCH_CODE_UNKNOWN is reserved for the
/*	'not found' result. The argument is copied (the pointer
/*	value, not the table).
/* .IP map_spec
/*	lookup table and optional search order: either maptype:mapname,
/*	or { maptype:mapname, { search = name, name }}. The search
/*	attribute is optional. The comma is equivalent to whitespace.
/* DIAGNOSTICS
/*	map_search_create() returns a null pointer when a map_spec
/*	is a) malformed, b) specifies an unexpected attribute name,
/*	c) the search attribute contains an unknown name. Thus,
/*	map_search_create() will never return a search_order that
/*	contains the value MAP_SEARCH_CODE_UNKNOWN.
/*
/*	Panic: interface violations. Fatal errors: out of memory.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

 /*
  * System library.
  */
#include <sys_defs.h>
#include <string.h>

#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif

 /*
  * Utility library.
  */
#include <htable.h>
#include <msg.h>
#include <mymalloc.h>
#include <name_code.h>
#include <stringops.h>
#include <vstring.h>

 /*
  * Global library.
  */
#include <map_search.h>

 /*
  * Application-specific.
  */
static HTABLE *map_search_table;
static const NAME_CODE *map_search_actions;

#define STR(x) vstring_str(x)

/* map_search_init - one-time initialization */

void    map_search_init(const NAME_CODE *search_actions)
{
    if (map_search_table != 0 || map_search_actions != 0)
	msg_panic("map_search_init: multiple calls");
    map_search_table = htable_create(100);
    map_search_actions = search_actions;
}

/* map_search_create - store MAP_SEARCH instance */

const MAP_SEARCH *map_search_create(const char *map_spec)
{
    char   *copy_of_map_spec = 0;
    char   *bp = 0;
    const char *const_err;
    char   *heap_err = 0;
    VSTRING *search_order = 0;
    const char *map_type_name;
    char   *attr_name_val = 0;
    char   *attr_name = 0;
    char   *attr_value = 0;
    MAP_SEARCH *map_search;
    char   *atom;
    int     code;

    /*
     * Sanity check.
     */
    if (map_search_table == 0 || map_search_actions == 0)
	msg_panic("map_search_create: missing initialization");

    /*
     * Allow exact duplicates. This won't catch duplicates that differ only
     * in their use of whitespace or comma.
     */
    if ((map_search =
	 (MAP_SEARCH *) htable_find(map_search_table, map_spec)) != 0)
	return (map_search);

    /*
     * Macro for readability and safety. Let the compiler worry about code
     * duplication and redundant conditions.
     */
#define MAP_SEARCH_CREATE_RETURN(x) do { \
	if (copy_of_map_spec) myfree(copy_of_map_spec); \
	if (heap_err) myfree(heap_err); \
	if (search_order) vstring_free(search_order); \
	return (x); \
    } while (0)

    /*
     * Long form specifies maptype_mapname and optional search attribute.
     */
    if (*map_spec == CHARS_BRACE[0]) {
	bp = copy_of_map_spec = mystrdup(map_spec);
	if ((heap_err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) {
	    msg_warn("malformed map specification: '%s'", heap_err);
	    MAP_SEARCH_CREATE_RETURN(0);
	} else if ((map_type_name = mystrtokq(&bp, CHARS_COMMA_SP,
					      CHARS_BRACE)) == 0) {
	    msg_warn("empty map specification: '%s'", map_spec);
	    MAP_SEARCH_CREATE_RETURN(0);
	}
    } else {
	map_type_name = map_spec;
    }

    /*
     * Sanity check the map spec before parsing attributes.
     */
    if (strchr(map_type_name, ':') == 0) {
	msg_warn("malformed map specification: '%s'", map_spec);
	msg_warn("expected maptype:mapname instead of '%s'", map_type_name);
	MAP_SEARCH_CREATE_RETURN(0);
    }

    /*
     * Parse the attribute list. XXX This does not detect multiple attributes
     * with the same attribute name.
     */
    if (bp != 0) {
	while ((attr_name_val = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
	    if (*attr_name_val == CHARS_BRACE[0]) {
		if ((heap_err = extpar(&attr_name_val, CHARS_BRACE,
				       EXTPAR_FLAG_STRIP)) != 0) {
		    msg_warn("malformed map attribute: %s", heap_err);
		    MAP_SEARCH_CREATE_RETURN(0);
		}
	    }
	    if ((const_err = split_nameval(attr_name_val, &attr_name,
					   &attr_value)) != 0) {
		msg_warn("malformed map attribute in '%s': '%s'",
			 map_spec, const_err);
		MAP_SEARCH_CREATE_RETURN(0);
	    }
	    if (strcasecmp(attr_name, MAP_SEARCH_ATTR_NAME_SEARCH) != 0) {
		msg_warn("unknown map attribute in '%s': '%s'",
			 map_spec, attr_name);
		MAP_SEARCH_CREATE_RETURN(0);
	    }
	}
    }

    /*
     * Parse the search list if any.
     */
    if (attr_name != 0) {
	search_order = vstring_alloc(10);
	while ((atom = mystrtok(&attr_value, CHARS_COMMA_SP)) != 0) {
	    if ((code = name_code(map_search_actions, NAME_CODE_FLAG_NONE,
				  atom)) == MAP_SEARCH_CODE_UNKNOWN) {
		msg_warn("unknown search type '%s' in '%s'", atom, map_spec);
		MAP_SEARCH_CREATE_RETURN(0);
	    }
	    VSTRING_ADDCH(search_order, code);
	}
	VSTRING_TERMINATE(search_order);
    }

    /*
     * Bundle up the result.
     */
    map_search = (MAP_SEARCH *) mymalloc(sizeof(*map_search));
    map_search->map_type_name = mystrdup(map_type_name);
    if (search_order) {
	map_search->search_order = vstring_export(search_order);
	search_order = 0;
    } else {
	map_search->search_order = 0;
    }

    /*
     * Save the ACL to cache.
     */
    (void) htable_enter(map_search_table, map_spec, map_search);

    MAP_SEARCH_CREATE_RETURN(map_search);
}

/* map_search_lookup - lookup MAP_SEARCH instance */

const MAP_SEARCH *map_search_lookup(const char *map_spec)
{

    /*
     * Sanity check.
     */
    if (map_search_table == 0 || map_search_actions == 0)
	msg_panic("map_search_lookup: missing initialization");

    return ((MAP_SEARCH *) htable_find(map_search_table, map_spec));
}
