1/*++ 2/* NAME 3/* match_ops 3 4/* SUMMARY 5/* simple string or host pattern matching 6/* SYNOPSIS 7/* #include <match_list.h> 8/* 9/* int match_string(list, string, pattern) 10/* MATCH_LIST *list; 11/* const char *string; 12/* const char *pattern; 13/* 14/* int match_hostname(list, name, pattern) 15/* MATCH_LIST *list; 16/* const char *name; 17/* const char *pattern; 18/* 19/* int match_hostaddr(list, addr, pattern) 20/* MATCH_LIST *list; 21/* const char *addr; 22/* const char *pattern; 23/* DESCRIPTION 24/* This module implements simple string and host name or address 25/* matching. The matching process is case insensitive. If a pattern 26/* has the form type:name, table lookup is used instead of string 27/* or address comparison. 28/* 29/* match_string() matches the string against the pattern, requiring 30/* an exact (case-insensitive) match. The flags argument is not used. 31/* 32/* match_hostname() matches the host name when the hostname matches 33/* the pattern exactly, or when the pattern matches a parent domain 34/* of the named host. The flags argument specifies the bit-wise OR 35/* of zero or more of the following: 36/* .IP MATCH_FLAG_PARENT 37/* The hostname pattern foo.com matches itself and any name below 38/* the domain foo.com. If this flag is cleared, foo.com matches itself 39/* only, and .foo.com matches any name below the domain foo.com. 40/* .IP MATCH_FLAG_RETURN 41/* Log a warning, return "not found", and set list->error to 42/* a non-zero dictionary error code, instead of raising a fatal 43/* run-time error. 44/* .RE 45/* Specify MATCH_FLAG_NONE to request none of the above. 46/* 47/* match_hostaddr() matches a host address when the pattern is 48/* identical to the host address, or when the pattern is a net/mask 49/* that contains the address. The mask specifies the number of 50/* bits in the network part of the pattern. The flags argument is 51/* not used. 52/* LICENSE 53/* .ad 54/* .fi 55/* The Secure Mailer license must be distributed with this software. 56/* AUTHOR(S) 57/* Wietse Venema 58/* IBM T.J. Watson Research 59/* P.O. Box 704 60/* Yorktown Heights, NY 10598, USA 61/*--*/ 62 63/* System library. */ 64 65#include <sys_defs.h> 66#include <netinet/in.h> 67#include <arpa/inet.h> 68#include <string.h> 69#include <stdlib.h> 70 71#ifdef STRCASECMP_IN_STRINGS_H 72#include <strings.h> 73#endif 74 75/* Utility library. */ 76 77#include <msg.h> 78#include <mymalloc.h> 79#include <split_at.h> 80#include <dict.h> 81#include <match_list.h> 82#include <stringops.h> 83#include <cidr_match.h> 84 85#define MATCH_DICTIONARY(pattern) \ 86 ((pattern)[0] != '[' && strchr((pattern), ':') != 0) 87 88/* match_error - return or raise fatal error */ 89 90static int match_error(MATCH_LIST *list, const char *fmt,...) 91{ 92 VSTRING *buf = vstring_alloc(100); 93 va_list ap; 94 95 /* 96 * Report, and maybe return. 97 */ 98 va_start(ap, fmt); 99 vstring_vsprintf(buf, fmt, ap); 100 va_end(ap); 101 if (list->flags & MATCH_FLAG_RETURN) { 102 msg_warn("%s", vstring_str(buf)); 103 } else { 104 msg_fatal("%s", vstring_str(buf)); 105 } 106 vstring_free(buf); 107 return (0); 108} 109 110/* match_string - match a string literal */ 111 112int match_string(MATCH_LIST *list, const char *string, const char *pattern) 113{ 114 const char *myname = "match_string"; 115 DICT *dict; 116 117 if (msg_verbose) 118 msg_info("%s: %s ~? %s", myname, string, pattern); 119 120 /* 121 * Try dictionary lookup: exact match. 122 */ 123 if (MATCH_DICTIONARY(pattern)) { 124 if ((dict = dict_handle(pattern)) == 0) 125 msg_panic("%s: unknown dictionary: %s", myname, pattern); 126 if (dict_get(dict, string) != 0) 127 return (1); 128 if ((list->error = dict->error) != 0) 129 return (match_error(list, "%s:%s: table lookup problem", 130 dict->type, dict->name)); 131 return (0); 132 } 133 134 /* 135 * Try an exact string match. 136 */ 137 if (strcasecmp(string, pattern) == 0) { 138 return (1); 139 } 140 141 /* 142 * No match found. 143 */ 144 return (0); 145} 146 147/* match_hostname - match a host by name */ 148 149int match_hostname(MATCH_LIST *list, const char *name, const char *pattern) 150{ 151 const char *myname = "match_hostname"; 152 const char *pd; 153 const char *entry; 154 const char *next; 155 int match; 156 DICT *dict; 157 158 if (msg_verbose) 159 msg_info("%s: %s ~? %s", myname, name, pattern); 160 161 /* 162 * Try dictionary lookup: exact match and parent domains. 163 * 164 * Don't look up parent domain substrings with regexp maps etc. 165 */ 166 if (MATCH_DICTIONARY(pattern)) { 167 if ((dict = dict_handle(pattern)) == 0) 168 msg_panic("%s: unknown dictionary: %s", myname, pattern); 169 match = 0; 170 for (entry = name; *entry != 0; entry = next) { 171 if (entry == name || (dict->flags & DICT_FLAG_FIXED)) { 172 match = (dict_get(dict, entry) != 0); 173 if (msg_verbose > 1) 174 msg_info("%s: lookup %s:%s %s: %s", 175 myname, dict->type, dict->name, entry, 176 match ? "found" : "notfound"); 177 if (match != 0) 178 break; 179 if ((list->error = dict->error) != 0) 180 return (match_error(list, "%s:%s: table lookup problem", 181 dict->type, dict->name)); 182 } 183 if ((next = strchr(entry + 1, '.')) == 0) 184 break; 185 if (list->flags & MATCH_FLAG_PARENT) 186 next += 1; 187 } 188 return (match); 189 } 190 191 /* 192 * Try an exact match with the host name. 193 */ 194 if (strcasecmp(name, pattern) == 0) { 195 return (1); 196 } 197 198 /* 199 * See if the pattern is a parent domain of the hostname. 200 */ 201 else { 202 if (list->flags & MATCH_FLAG_PARENT) { 203 pd = name + strlen(name) - strlen(pattern); 204 if (pd > name && pd[-1] == '.' && strcasecmp(pd, pattern) == 0) 205 return (1); 206 } else if (pattern[0] == '.') { 207 pd = name + strlen(name) - strlen(pattern); 208 if (pd > name && strcasecmp(pd, pattern) == 0) 209 return (1); 210 } 211 } 212 return (0); 213} 214 215/* match_hostaddr - match host by address */ 216 217int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern) 218{ 219 const char *myname = "match_hostaddr"; 220 char *saved_patt; 221 CIDR_MATCH match_info; 222 DICT *dict; 223 VSTRING *err; 224 int rc; 225 226 if (msg_verbose) 227 msg_info("%s: %s ~? %s", myname, addr, pattern); 228 229#define V4_ADDR_STRING_CHARS "01234567890." 230#define V6_ADDR_STRING_CHARS V4_ADDR_STRING_CHARS "abcdefABCDEF:" 231 232 if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0) 233 return (0); 234 235 /* 236 * Try dictionary lookup. This can be case insensitive. 237 */ 238 if (MATCH_DICTIONARY(pattern)) { 239 if ((dict = dict_handle(pattern)) == 0) 240 msg_panic("%s: unknown dictionary: %s", myname, pattern); 241 if (dict_get(dict, addr) != 0) 242 return (1); 243 if ((list->error = dict->error) != 0) 244 return (match_error(list, "%s:%s: table lookup problem", 245 dict->type, dict->name)); 246 return (0); 247 } 248 249 /* 250 * Try an exact match with the host address. 251 */ 252 if (pattern[0] != '[') { 253 if (strcasecmp(addr, pattern) == 0) 254 return (1); 255 } else { 256 size_t addr_len = strlen(addr); 257 258 if (strncasecmp(addr, pattern + 1, addr_len) == 0 259 && strcmp(pattern + 1 + addr_len, "]") == 0) 260 return (1); 261 } 262 263 /* 264 * Light-weight tests before we get into expensive operations. 265 * 266 * - Don't bother matching IPv4 against IPv6. Postfix transforms 267 * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in 268 * Postfix; if not, then Postfix has no business dealing with IPv4 269 * addresses anyway. 270 * 271 * - Don't bother unless the pattern is either an IPv6 address or net/mask. 272 * 273 * We can safely skip IPv4 address patterns because their form is 274 * unambiguous and they did not match in the strcasecmp() calls above. 275 * 276 * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST 277 * input, to avoid triggering false cidr_match_parse() errors. 278 * 279 * The last two conditions below are for backwards compatibility with 280 * earlier Postfix versions: don't abort with fatal errors on junk that 281 * was silently ignored (principle of least astonishment). 282 */ 283 if (!strchr(addr, ':') != !strchr(pattern, ':') 284 || pattern[strcspn(pattern, ":/")] == 0 285 || pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0 286 || pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0) 287 return (0); 288 289 /* 290 * No escape from expensive operations: either we have a net/mask 291 * pattern, or we have an address that can have multiple valid 292 * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way 293 * to find out if the address matches the pattern is to transform 294 * everything into to binary form, and to do the comparison there. 295 */ 296 saved_patt = mystrdup(pattern); 297 err = cidr_match_parse(&match_info, saved_patt, (VSTRING *) 0); 298 myfree(saved_patt); 299 if (err != 0) { 300 list->error = DICT_ERR_RETRY; 301 rc = match_error(list, "%s", vstring_str(err)); 302 vstring_free(err); 303 return (rc); 304 } 305 return (cidr_match_execute(&match_info, addr) != 0); 306} 307