text: /*++
  /* NAME
  /*	match_ops 3
  /* SUMMARY
  /*	simple string or host pattern matching
  /* SYNOPSIS
  /*	#include <match_list.h>
  /*
  /*	int	match_string(list, string, pattern)
  /*	MATCH_LIST *list;
  /*	const char *string;
  /*	const char *pattern;
  /*
  /*	int	match_hostname(list, name, pattern)
  /*	MATCH_LIST *list;
  /*	const char *name;
  /*	const char *pattern;
  /*
  /*	int	match_hostaddr(list, addr, pattern)
  /*	MATCH_LIST *list;
  /*	const char *addr;
  /*	const char *pattern;
  /* DESCRIPTION
  /*	This module implements simple string and host name or address
  /*	matching. The matching process is case insensitive. If a pattern
  /*	has the form type:name, table lookup is used instead of string
  /*	or address comparison.
  /*
  /*	match_string() matches the string against the pattern, requiring
  /*	an exact (case-insensitive) match. The flags argument is not used.
  /*
  /*	match_hostname() matches the host name when the hostname matches
  /*	the pattern exactly, or when the pattern matches a parent domain
  /*	of the named host. The flags argument specifies the bit-wise OR
  /*	of zero or more of the following:
  /* .IP MATCH_FLAG_PARENT
  /*	The hostname pattern foo.com matches itself and any name below
  /*	the domain foo.com. If this flag is cleared, foo.com matches itself
  /*	only, and .foo.com matches any name below the domain foo.com.
  /* .IP MATCH_FLAG_RETURN
  /*	Log a warning, return "not found", and set list->error to
  /*	a non-zero dictionary error code, instead of raising a fatal
  /*	run-time error.
  /* .RE
  /*	Specify MATCH_FLAG_NONE to request none of the above.
  /*
  /*	match_hostaddr() matches a host address when the pattern is
  /*	identical to the host address, or when the pattern is a net/mask
  /*	that contains the address. The mask specifies the number of
  /*	bits in the network part of the pattern. The flags argument is
  /*	not used.
  /* 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 <netinet/in.h>
  #include <arpa/inet.h>
  #include <string.h>
  #include <stdlib.h>
  
  /* Utility library. */
  
  #include <msg.h>
  #include <mymalloc.h>
  #include <split_at.h>
  #include <dict.h>
  #include <match_list.h>
  #include <stringops.h>
  #include <cidr_match.h>
  
  #define MATCH_DICTIONARY(pattern) \
      ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
  
  /* match_error - return or raise fatal error */
  
  static int match_error(MATCH_LIST *list, const char *fmt,...)
  {
      VSTRING *buf = vstring_alloc(100);
      va_list ap;
  
      /*
       * Report, and maybe return.
       */
      va_start(ap, fmt);
      vstring_vsprintf(buf, fmt, ap);
      va_end(ap);
      if (list->flags & MATCH_FLAG_RETURN) {
  	msg_warn("%s: %s", list->pname, vstring_str(buf));
      } else {
  	msg_fatal("%s: %s", list->pname, vstring_str(buf));
      }
      vstring_free(buf);
      return (0);
  }
  
  /* match_string - match a string literal */
  
  int     match_string(MATCH_LIST *list, const char *string, const char *pattern)
  {
      const char *myname = "match_string";
      DICT   *dict;
  
      if (msg_verbose)
  	msg_info("%s: %s: %s ~? %s", myname, list->pname, string, pattern);
  
      /*
       * Try dictionary lookup: exact match.
       */
      if (MATCH_DICTIONARY(pattern)) {
  	if ((dict = dict_handle(pattern)) == 0)
  	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
  	if (dict_get(dict, string) != 0)
  	    return (1);
  	if ((list->error = dict->error) != 0)
  	    return (match_error(list, "%s:%s: table lookup problem",
  				dict->type, dict->name));
  	return (0);
      }
  
      /*
       * Try an exact string match. Note that the string and pattern are
       * already casefolded.
       */
      if (strcmp(string, pattern) == 0) {
  	return (1);
      }
  
      /*
       * No match found.
       */
      return (0);
  }
  
  /* match_hostname - match a host by name */
  
  int     match_hostname(MATCH_LIST *list, const char *name, const char *pattern)
  {
      const char *myname = "match_hostname";
      const char *pd;
      const char *entry;
      const char *next;
      int     match;
      DICT   *dict;
  
      if (msg_verbose)
  	msg_info("%s: %s: %s ~? %s", myname, list->pname, name, pattern);
  
      /*
       * Try dictionary lookup: exact match and parent domains.
       * 
       * Don't look up parent domain substrings with regexp maps etc.
       */
      if (MATCH_DICTIONARY(pattern)) {
  	if ((dict = dict_handle(pattern)) == 0)
  	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
  	match = 0;
  	for (entry = name; *entry != 0; entry = next) {
  	    if (entry == name || (dict->flags & DICT_FLAG_FIXED)) {
  		match = (dict_get(dict, entry) != 0);
  		if (msg_verbose > 1)
  		    msg_info("%s: %s: lookup %s:%s %s: %s",
  			     myname, list->pname, dict->type, dict->name,
  			     entry, match ? "found" : "notfound");
  		if (match != 0)
  		    break;
  		if ((list->error = dict->error) != 0)
  		    return (match_error(list, "%s:%s: table lookup problem",
  					dict->type, dict->name));
  	    }
  	    if ((next = strchr(entry + 1, '.')) == 0)
  		break;
  	    if (list->flags & MATCH_FLAG_PARENT)
  		next += 1;
  	}
  	return (match);
      }
  
      /*
       * Try an exact match with the host name. Note that the name and the
       * pattern are already casefolded.
       */
      if (strcmp(name, pattern) == 0) {
  	return (1);
      }
  
      /*
       * See if the pattern is a parent domain of the hostname. Note that the
       * name and the pattern are already casefolded.
       */
      else {
  	if (list->flags & MATCH_FLAG_PARENT) {
  	    pd = name + strlen(name) - strlen(pattern);
  	    if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0)
  		return (1);
  	} else if (pattern[0] == '.') {
  	    pd = name + strlen(name) - strlen(pattern);
  	    if (pd > name && strcmp(pd, pattern) == 0)
  		return (1);
  	}
      }
      return (0);
  }
  
  /* match_hostaddr - match host by address */
  
  int     match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern)
  {
      const char *myname = "match_hostaddr";
      char   *saved_patt;
      CIDR_MATCH match_info;
      DICT   *dict;
      VSTRING *err;
      int     rc;
  
      if (msg_verbose)
  	msg_info("%s: %s: %s ~? %s", myname, list->pname, addr, pattern);
  
  #define V4_ADDR_STRING_CHARS	"01234567890."
  #define V6_ADDR_STRING_CHARS	V4_ADDR_STRING_CHARS "abcdefABCDEF:"
  
      if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0)
  	return (0);
  
      /*
       * Try dictionary lookup. This can be case insensitive.
       */
      if (MATCH_DICTIONARY(pattern)) {
  	if ((dict = dict_handle(pattern)) == 0)
  	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
  	if (dict_get(dict, addr) != 0)
  	    return (1);
  	if ((list->error = dict->error) != 0)
  	    return (match_error(list, "%s:%s: table lookup problem",
  				dict->type, dict->name));
  	return (0);
      }
  
      /*
       * Try an exact match with the host address. Note that the address and
       * pattern are already casefolded.
       */
      if (pattern[0] != '[') {
  	if (strcmp(addr, pattern) == 0)
  	    return (1);
      } else {
  	size_t  addr_len = strlen(addr);
  
  	if (strncmp(addr, pattern + 1, addr_len) == 0
  	    && strcmp(pattern + 1 + addr_len, "]") == 0)
  	    return (1);
      }
  
      /*
       * Light-weight tests before we get into expensive operations.
       * 
       * - Don't bother matching IPv4 against IPv6. Postfix transforms
       * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in
       * Postfix; if not, then Postfix has no business dealing with IPv4
       * addresses anyway.
       * 
       * - Don't bother unless the pattern is either an IPv6 address or net/mask.
       * 
       * We can safely skip IPv4 address patterns because their form is
       * unambiguous and they did not match in the strcmp() calls above.
       * 
       * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
       * input, to avoid triggering false cidr_match_parse() errors.
       * 
       * The last two conditions below are for backwards compatibility with
       * earlier Postfix versions: don't abort with fatal errors on junk that
       * was silently ignored (principle of least astonishment).
       */
      if (!strchr(addr, ':') != !strchr(pattern, ':')
  	|| pattern[strcspn(pattern, ":/")] == 0
  	|| pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0
  	|| pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0)
  	return (0);
  
      /*
       * No escape from expensive operations: either we have a net/mask
       * pattern, or we have an address that can have multiple valid
       * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way
       * to find out if the address matches the pattern is to transform
       * everything into to binary form, and to do the comparison there.
       */
      saved_patt = mystrdup(pattern);
      err = cidr_match_parse(&match_info, saved_patt, (VSTRING *) 0);
      myfree(saved_patt);
      if (err != 0) {
  	list->error = DICT_ERR_RETRY;
  	rc = match_error(list, "%s", vstring_str(err));
  	vstring_free(err);
  	return (rc);
      }
      return (cidr_match_execute(&match_info, addr) != 0);
  }
  