1/* $NetBSD: login_sender_match.c,v 1.2 2022/10/08 16:12:45 christos Exp $ */ 2 3/*++ 4/* NAME 5/* login_sender_match 3 6/* SUMMARY 7/* match login and sender against (login, sender) patterns 8/* SYNOPSIS 9/* #include <login_sender.h> 10/* 11/* typedef LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH; 12/* 13/* LOGIN_SENDER_MATCH *login_sender_create( 14/* const char *title, 15/* const char *map_names, 16/* const char *ext_delimiters, 17/* const char *null_sender, 18/* const char *wildcard) 19/* 20/* void login_sender_free( 21/* LOGIN_SENDER_MATCH *lsm) 22/* 23/* int login_sender_match( 24/* LOGIN_SENDER_MATCH *lsm, 25/* const char *login_name, 26/* const char *sender_addr) 27/* DESCRIPTION 28/* This module determines if a login name and internal-form 29/* sender address match a (login name, external-form sender 30/* patterns) table entry. A login name matches if it matches 31/* a lookup table key. A sender address matches the corresponding 32/* table entry if it matches a sender pattern. A wildcard 33/* sender pattern matches any sender address. A sender pattern 34/* that starts with '@' matches the '@' and the domain portion 35/* of a sender address. Otherwise, the matcher ignores the 36/* extension part of a sender address, and requires a 37/* case-insensitive match against a sender pattern. 38/* 39/* login_sender_create() creates a (login name, sender patterns) 40/* matcher. 41/* 42/* login_sender_free() destroys the specified (login name, 43/* sender patterns) matcher. 44/* 45/* login_sender_match() looks up an entry for the \fBlogin_name\fR 46/* argument, and determines if the lookup result matches the 47/* \fBsender_adddr\fR argument. 48/* 49/* Arguments: 50/* .IP title 51/* The name of the configuration parameter that specifies the 52/* map_names value, used for error messages. 53/* .IP map_names 54r* The lookup table(s) with (login name, sender patterns) entries. 55/* .IP ext_delimiters 56/* The set of address extension delimiters. 57/* .IP null_sender 58/* If a sender pattern equals the null_sender pattern, then 59/* the empty address is matched. 60/* .IP wildcard 61/* Null pointer, or non-empty string with a wildcard pattern. 62/* If a sender pattern equals the wildcard pattern, then any 63/* sender address is matched. 64/* .IP login_name 65/* The login name (for example, UNIX account, or SASL username) 66/* that will be used as a search key to locate a list of senders. 67/* .IP sender_addr 68/* The sender email address (unquoted form) that will be matched 69/* against a (login name, sender patterns) table entry. 70/* DIAGNOSTICS 71/* login_sender_match() returns LSM_STAT_FOUND if a 72/* match was found, LOGIN_STAT_NOTFOUND if no match was found, 73/* LSM_STAT_RETRY if the table lookup failed, or 74/* LSM_STAT_CONFIG in case of a configuration error. 75/* LICENSE 76/* .ad 77/* .fi 78/* The Secure Mailer license must be distributed with this software. 79/* AUTHOR(S) 80/* Wietse Venema 81/* Google, Inc. 82/* 111 8th Avenue 83/* New York, NY 10011, USA 84/*--*/ 85 86 /* 87 * System library. 88 */ 89#include <sys_defs.h> 90#include <string.h> 91 92 /* 93 * Utility library. 94 */ 95#include <msg.h> 96#include <mymalloc.h> 97#include <stringops.h> 98#include <vstring.h> 99 100 /* 101 * Global library. 102 */ 103#include <mail_params.h> 104#include <maps.h> 105#include <quote_822_local.h> 106#include <strip_addr.h> 107#include <login_sender_match.h> 108 109 /* 110 * Private data structure. 111 */ 112struct LOGIN_SENDER_MATCH { 113 MAPS *maps; 114 VSTRING *ext_stripped_sender; 115 char *ext_delimiters; 116 char *null_sender; 117 char *wildcard; 118}; 119 120 /* 121 * SLMs. 122 */ 123#define STR(x) vstring_str(x) 124 125/* login_sender_create - create (login name, sender patterns) matcher */ 126 127LOGIN_SENDER_MATCH *login_sender_create(const char *title, 128 const char *map_names, 129 const char *ext_delimiters, 130 const char *null_sender, 131 const char *wildcard) 132{ 133 LOGIN_SENDER_MATCH *lsm = mymalloc(sizeof *lsm); 134 135 lsm->maps = maps_create(title, map_names, DICT_FLAG_LOCK 136 | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); 137 lsm->ext_stripped_sender = vstring_alloc(100); 138 lsm->ext_delimiters = mystrdup(ext_delimiters); 139 if (null_sender == 0 || *null_sender == 0) 140 msg_panic("login_sender_create: null or empty null_sender"); 141 lsm->null_sender = mystrdup(null_sender); 142 lsm->wildcard = (wildcard && *wildcard) ? mystrdup(wildcard) : 0; 143 return (lsm); 144} 145 146/* login_sender_free - destroy (login name, sender patterns) matcher */ 147 148void login_sender_free(LOGIN_SENDER_MATCH *lsm) 149{ 150 maps_free(lsm->maps); 151 vstring_free(lsm->ext_stripped_sender); 152 myfree(lsm->ext_delimiters); 153 myfree(lsm->null_sender); 154 if (lsm->wildcard) 155 myfree(lsm->wildcard); 156 myfree((void *) lsm); 157} 158 159/* strip_externalize_addr - strip address extension and externalize remainder */ 160 161static VSTRING *strip_externalize_addr(VSTRING *ext_addr, const char *int_addr, 162 const char *delims) 163{ 164 char *int_stripped_addr; 165 166 if ((int_stripped_addr = strip_addr_internal(int_addr, 167 /* extension= */ (char **) 0, 168 delims)) != 0) { 169 quote_822_local(ext_addr, int_stripped_addr); 170 myfree(int_stripped_addr); 171 return (ext_addr); 172 } else { 173 return quote_822_local(ext_addr, int_addr); 174 } 175} 176 177/* login_sender_match - match login and sender against (login, senders) table */ 178 179int login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name, 180 const char *sender_addr) 181{ 182 int found_or_error = LSM_STAT_NOTFOUND; 183 184 /* Sender patterns and derived info */ 185 const char *sender_patterns; 186 char *saved_sender_patterns; 187 char *cp; 188 char *sender_pattern; 189 190 /* Actual sender and derived info */ 191 const char *ext_stripped_sender = 0; 192 const char *at_sender_domain; 193 194 /* 195 * Match the login. 196 */ 197 if ((sender_patterns = maps_find(lsm->maps, login_name, 198 /* flags= */ 0)) != 0) { 199 200 /* 201 * Match the sender. Don't break a sender pattern between double 202 * quotes. 203 */ 204 cp = saved_sender_patterns = mystrdup(sender_patterns); 205 while (found_or_error == LSM_STAT_NOTFOUND 206 && (sender_pattern = mystrtokdq(&cp, CHARS_COMMA_SP)) != 0) { 207 /* Special pattern: @domain. */ 208 if (*sender_pattern == '@') { 209 if ((at_sender_domain = strrchr(sender_addr, '@')) != 0 210 && strcasecmp_utf8(sender_pattern, at_sender_domain) == 0) 211 found_or_error = LSM_STAT_FOUND; 212 } 213 /* Special pattern: wildcard. */ 214 else if (strcasecmp(sender_pattern, lsm->wildcard) == 0) { 215 found_or_error = LSM_STAT_FOUND; 216 } 217 /* Special pattern: empty sender. */ 218 else if (strcasecmp(sender_pattern, lsm->null_sender) == 0) { 219 if (*sender_addr == 0) 220 found_or_error = LSM_STAT_FOUND; 221 } 222 /* Literal pattern: match the stripped and externalized sender. */ 223 else { 224 if (ext_stripped_sender == 0) 225 ext_stripped_sender = 226 STR(strip_externalize_addr(lsm->ext_stripped_sender, 227 sender_addr, 228 lsm->ext_delimiters)); 229 if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0) 230 found_or_error = LSM_STAT_FOUND; 231 } 232 } 233 myfree(saved_sender_patterns); 234 } else { 235 found_or_error = lsm->maps->error; 236 } 237 return (found_or_error); 238} 239 240#ifdef TEST 241 242int main(int argc, char **argv) 243{ 244 struct testcase { 245 const char *title; 246 const char *map_names; 247 const char *ext_delimiters; 248 const char *null_sender; 249 const char *wildcard; 250 const char *login_name; 251 const char *sender_addr; 252 int exp_return; 253 }; 254 struct testcase testcases[] = { 255 {"wildcard works", 256 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 257 "+-", "<>", "*", "root", "anything", LSM_STAT_FOUND 258 }, 259 {"unknown user", 260 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 261 "+-", "<>", "*", "toor", "anything", LSM_STAT_NOTFOUND 262 }, 263 {"bare user", 264 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 265 "+-", "<>", "*", "foo", "foo", LSM_STAT_FOUND 266 }, 267 {"user@domain", 268 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 269 "+-", "<>", "*", "foo", "foo@example.com", LSM_STAT_FOUND 270 }, 271 {"user+ext@domain", 272 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 273 "+-", "<>", "*", "foo", "foo+bar@example.com", LSM_STAT_FOUND 274 }, 275 {"wrong sender", 276 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 277 "+-", "<>", "*", "foo", "bar@example.com", LSM_STAT_NOTFOUND 278 }, 279 {"@domain", 280 "inline:{root=*, {foo = @example.com}, bar=<>}", 281 "+-", "<>", "*", "foo", "anyone@example.com", LSM_STAT_FOUND 282 }, 283 {"wrong @domain", 284 "inline:{root=*, {foo = @example.com}, bar=<>}", 285 "+-", "<>", "*", "foo", "anyone@example.org", LSM_STAT_NOTFOUND 286 }, 287 {"null sender", 288 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 289 "+-", "<>", "*", "bar", "", LSM_STAT_FOUND 290 }, 291 {"wrong null sender", 292 "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}", 293 "+-", "<>", "*", "baz", "", LSM_STAT_NOTFOUND 294 }, 295 {"error", 296 "inline:{root=*}, fail:sorry", 297 "+-", "<>", "*", "baz", "whatever", LSM_STAT_RETRY 298 }, 299 {"no error", 300 "inline:{root=*}, fail:sorry", 301 "+-", "<>", "*", "root", "whatever", LSM_STAT_FOUND 302 }, 303 {"unknown uid:number", 304 "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}", 305 "+-", "<>", "*", "uid:54321", "foo", LSM_STAT_NOTFOUND 306 }, 307 {"known uid:number", 308 "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}", 309 "+-", "<>", "*", "uid:12345", "foo", LSM_STAT_FOUND 310 }, 311 {"unknown \"other last\"", 312 "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}", 313 "+-", "<>", "*", "foo", "other last", LSM_STAT_NOTFOUND 314 }, 315 {"bare \"first last\"", 316 "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}", 317 "+-", "<>", "*", "foo", "first last", LSM_STAT_FOUND 318 }, 319 {"\"first last\"@domain", 320 "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}", 321 "+-", "<>", "*", "foo", "first last@example.com", LSM_STAT_FOUND 322 }, 323 }; 324 struct testcase *tp; 325 int act_return; 326 int pass; 327 int fail; 328 LOGIN_SENDER_MATCH *lsm; 329 330 /* 331 * Fake variable settings. 332 */ 333 var_double_bounce_sender = DEF_DOUBLE_BOUNCE; 334 var_ownreq_special = DEF_OWNREQ_SPECIAL; 335 336#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0]) 337 338 for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) { 339 msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title); 340#if 0 341 msg_info("title=%s", tp->title); 342 msg_info("map_names=%s", tp->map_names); 343 msg_info("ext_delimiters=%s", tp->ext_delimiters); 344 msg_info("null_sender=%s", tp->null_sender); 345 msg_info("wildcard=%s", tp->wildcard); 346 msg_info("login_name=%s", tp->login_name); 347 msg_info("sender_addr=%s", tp->sender_addr); 348 msg_info("exp_return=%d", tp->exp_return); 349#endif 350 lsm = login_sender_create("test map", tp->map_names, 351 tp->ext_delimiters, tp->null_sender, 352 tp->wildcard); 353 act_return = login_sender_match(lsm, tp->login_name, tp->sender_addr); 354 if (act_return == tp->exp_return) { 355 msg_info("PASS test %ld", (long) (tp - testcases)); 356 pass++; 357 } else { 358 msg_info("FAIL test %ld", (long) (tp - testcases)); 359 fail++; 360 } 361 login_sender_free(lsm); 362 } 363 return (fail > 0); 364} 365 366#endif /* TEST */ 367