/* $NetBSD: login_sender_match.c,v 1.2 2022/10/08 16:12:45 christos Exp $ */
/*++
/* NAME
/* login_sender_match 3
/* SUMMARY
/* match login and sender against (login, sender) patterns
/* SYNOPSIS
/* #include <login_sender.h>
/*
/* typedef LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH;
/*
/* LOGIN_SENDER_MATCH *login_sender_create(
/* const char *title,
/* const char *map_names,
/* const char *ext_delimiters,
/* const char *null_sender,
/* const char *wildcard)
/*
/* void login_sender_free(
/* LOGIN_SENDER_MATCH *lsm)
/*
/* int login_sender_match(
/* LOGIN_SENDER_MATCH *lsm,
/* const char *login_name,
/* const char *sender_addr)
/* DESCRIPTION
/* This module determines if a login name and internal-form
/* sender address match a (login name, external-form sender
/* patterns) table entry. A login name matches if it matches
/* a lookup table key. A sender address matches the corresponding
/* table entry if it matches a sender pattern. A wildcard
/* sender pattern matches any sender address. A sender pattern
/* that starts with '@' matches the '@' and the domain portion
/* of a sender address. Otherwise, the matcher ignores the
/* extension part of a sender address, and requires a
/* case-insensitive match against a sender pattern.
/*
/* login_sender_create() creates a (login name, sender patterns)
/* matcher.
/*
/* login_sender_free() destroys the specified (login name,
/* sender patterns) matcher.
/*
/* login_sender_match() looks up an entry for the \fBlogin_name\fR
/* argument, and determines if the lookup result matches the
/* \fBsender_adddr\fR argument.
/*
/* Arguments:
/* .IP title
/* The name of the configuration parameter that specifies the
/* map_names value, used for error messages.
/* .IP map_names
r* The lookup table(s) with (login name, sender patterns) entries.
/* .IP ext_delimiters
/* The set of address extension delimiters.
/* .IP null_sender
/* If a sender pattern equals the null_sender pattern, then
/* the empty address is matched.
/* .IP wildcard
/* Null pointer, or non-empty string with a wildcard pattern.
/* If a sender pattern equals the wildcard pattern, then any
/* sender address is matched.
/* .IP login_name
/* The login name (for example, UNIX account, or SASL username)
/* that will be used as a search key to locate a list of senders.
/* .IP sender_addr
/* The sender email address (unquoted form) that will be matched
/* against a (login name, sender patterns) table entry.
/* DIAGNOSTICS
/* login_sender_match() returns LSM_STAT_FOUND if a
/* match was found, LOGIN_STAT_NOTFOUND if no match was found,
/* LSM_STAT_RETRY if the table lookup failed, or
/* LSM_STAT_CONFIG in case of a configuration error.
/* 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>
/*
* Utility library.
*/
#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>
#include <vstring.h>
/*
* Global library.
*/
#include <mail_params.h>
#include <maps.h>
#include <quote_822_local.h>
#include <strip_addr.h>
#include <login_sender_match.h>
/*
* Private data structure.
*/
struct LOGIN_SENDER_MATCH {
MAPS *maps;
VSTRING *ext_stripped_sender;
char *ext_delimiters;
char *null_sender;
char *wildcard;
};
/*
* SLMs.
*/
#define STR(x) vstring_str(x)
/* login_sender_create - create (login name, sender patterns) matcher */
LOGIN_SENDER_MATCH *login_sender_create(const char *title,
const char *map_names,
const char *ext_delimiters,
const char *null_sender,
const char *wildcard)
{
LOGIN_SENDER_MATCH *lsm = mymalloc(sizeof *lsm);
lsm->maps = maps_create(title, map_names, DICT_FLAG_LOCK
| DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
lsm->ext_stripped_sender = vstring_alloc(100);
lsm->ext_delimiters = mystrdup(ext_delimiters);
if (null_sender == 0 || *null_sender == 0)
msg_panic("login_sender_create: null or empty null_sender");
lsm->null_sender = mystrdup(null_sender);
lsm->wildcard = (wildcard && *wildcard) ? mystrdup(wildcard) : 0;
return (lsm);
}
/* login_sender_free - destroy (login name, sender patterns) matcher */
void login_sender_free(LOGIN_SENDER_MATCH *lsm)
{
maps_free(lsm->maps);
vstring_free(lsm->ext_stripped_sender);
myfree(lsm->ext_delimiters);
myfree(lsm->null_sender);
if (lsm->wildcard)
myfree(lsm->wildcard);
myfree((void *) lsm);
}
/* strip_externalize_addr - strip address extension and externalize remainder */
static VSTRING *strip_externalize_addr(VSTRING *ext_addr, const char *int_addr,
const char *delims)
{
char *int_stripped_addr;
if ((int_stripped_addr = strip_addr_internal(int_addr,
/* extension= */ (char **) 0,
delims)) != 0) {
quote_822_local(ext_addr, int_stripped_addr);
myfree(int_stripped_addr);
return (ext_addr);
} else {
return quote_822_local(ext_addr, int_addr);
}
}
/* login_sender_match - match login and sender against (login, senders) table */
int login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name,
const char *sender_addr)
{
int found_or_error = LSM_STAT_NOTFOUND;
/* Sender patterns and derived info */
const char *sender_patterns;
char *saved_sender_patterns;
char *cp;
char *sender_pattern;
/* Actual sender and derived info */
const char *ext_stripped_sender = 0;
const char *at_sender_domain;
/*
* Match the login.
*/
if ((sender_patterns = maps_find(lsm->maps, login_name,
/* flags= */ 0)) != 0) {
/*
* Match the sender. Don't break a sender pattern between double
* quotes.
*/
cp = saved_sender_patterns = mystrdup(sender_patterns);
while (found_or_error == LSM_STAT_NOTFOUND
&& (sender_pattern = mystrtokdq(&cp, CHARS_COMMA_SP)) != 0) {
/* Special pattern: @domain. */
if (*sender_pattern == '@') {
if ((at_sender_domain = strrchr(sender_addr, '@')) != 0
&& strcasecmp_utf8(sender_pattern, at_sender_domain) == 0)
found_or_error = LSM_STAT_FOUND;
}
/* Special pattern: wildcard. */
else if (strcasecmp(sender_pattern, lsm->wildcard) == 0) {
found_or_error = LSM_STAT_FOUND;
}
/* Special pattern: empty sender. */
else if (strcasecmp(sender_pattern, lsm->null_sender) == 0) {
if (*sender_addr == 0)
found_or_error = LSM_STAT_FOUND;
}
/* Literal pattern: match the stripped and externalized sender. */
else {
if (ext_stripped_sender == 0)
ext_stripped_sender =
STR(strip_externalize_addr(lsm->ext_stripped_sender,
sender_addr,
lsm->ext_delimiters));
if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0)
found_or_error = LSM_STAT_FOUND;
}
}
myfree(saved_sender_patterns);
} else {
found_or_error = lsm->maps->error;
}
return (found_or_error);
}
#ifdef TEST
int main(int argc, char **argv)
{
struct testcase {
const char *title;
const char *map_names;
const char *ext_delimiters;
const char *null_sender;
const char *wildcard;
const char *login_name;
const char *sender_addr;
int exp_return;
};
struct testcase testcases[] = {
{"wildcard works",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "root", "anything", LSM_STAT_FOUND
},
{"unknown user",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "toor", "anything", LSM_STAT_NOTFOUND
},
{"bare user",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "foo", "foo", LSM_STAT_FOUND
},
{"user@domain",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "foo", "foo@example.com", LSM_STAT_FOUND
},
{"user+ext@domain",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "foo", "foo+bar@example.com", LSM_STAT_FOUND
},
{"wrong sender",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "foo", "bar@example.com", LSM_STAT_NOTFOUND
},
{"@domain",
"inline:{root=*, {foo = @example.com}, bar=<>}",
"+-", "<>", "*", "foo", "anyone@example.com", LSM_STAT_FOUND
},
{"wrong @domain",
"inline:{root=*, {foo = @example.com}, bar=<>}",
"+-", "<>", "*", "foo", "anyone@example.org", LSM_STAT_NOTFOUND
},
{"null sender",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "bar", "", LSM_STAT_FOUND
},
{"wrong null sender",
"inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "baz", "", LSM_STAT_NOTFOUND
},
{"error",
"inline:{root=*}, fail:sorry",
"+-", "<>", "*", "baz", "whatever", LSM_STAT_RETRY
},
{"no error",
"inline:{root=*}, fail:sorry",
"+-", "<>", "*", "root", "whatever", LSM_STAT_FOUND
},
{"unknown uid:number",
"inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "uid:54321", "foo", LSM_STAT_NOTFOUND
},
{"known uid:number",
"inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "uid:12345", "foo", LSM_STAT_FOUND
},
{"unknown \"other last\"",
"inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
"+-", "<>", "*", "foo", "other last", LSM_STAT_NOTFOUND
},
{"bare \"first last\"",
"inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
"+-", "<>", "*", "foo", "first last", LSM_STAT_FOUND
},
{"\"first last\"@domain",
"inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
"+-", "<>", "*", "foo", "first last@example.com", LSM_STAT_FOUND
},
};
struct testcase *tp;
int act_return;
int pass;
int fail;
LOGIN_SENDER_MATCH *lsm;
/*
* Fake variable settings.
*/
var_double_bounce_sender = DEF_DOUBLE_BOUNCE;
var_ownreq_special = DEF_OWNREQ_SPECIAL;
#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0])
for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
#if 0
msg_info("title=%s", tp->title);
msg_info("map_names=%s", tp->map_names);
msg_info("ext_delimiters=%s", tp->ext_delimiters);
msg_info("null_sender=%s", tp->null_sender);
msg_info("wildcard=%s", tp->wildcard);
msg_info("login_name=%s", tp->login_name);
msg_info("sender_addr=%s", tp->sender_addr);
msg_info("exp_return=%d", tp->exp_return);
#endif
lsm = login_sender_create("test map", tp->map_names,
tp->ext_delimiters, tp->null_sender,
tp->wildcard);
act_return = login_sender_match(lsm, tp->login_name, tp->sender_addr);
if (act_return == tp->exp_return) {
msg_info("PASS test %ld", (long) (tp - testcases));
pass++;
} else {
msg_info("FAIL test %ld", (long) (tp - testcases));
fail++;
}
login_sender_free(lsm);
}
return (fail > 0);
}
#endif /* TEST */