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