X-Git-Url: http://git.madism.org/?p=apps%2Fpfixtools.git;a=blobdiff_plain;f=postlicyd%2Fstrlist.c;h=0c790cb1dc5283c0efd1da262eee9693339ad54e;hp=25ed069a290727dd8af71dd10118a50f1092629e;hb=59b8220d0227fe68537a563b3e5fa2e63e26bc0d;hpb=5c75febadf099c0a656b3b8072b14ec14b38c2f6 diff --git a/postlicyd/strlist.c b/postlicyd/strlist.c index 25ed069..0c790cb 100644 --- a/postlicyd/strlist.c +++ b/postlicyd/strlist.c @@ -37,21 +37,28 @@ #include "trie.h" #include "file.h" #include "str.h" +#include "rbl.h" #include "policy_tokens.h" typedef struct strlist_config_t { PA(trie_t) tries; A(int) weights; A(bool) reverses; + A(bool) partiales; + + A(char) hosts; + A(int) host_offsets; + A(int) host_weights; int soft_threshold; int hard_threshold; unsigned is_email :1; + unsigned is_hostname :1; + unsigned match_sender :1; unsigned match_recipient :1; - unsigned is_hostname :1; unsigned match_helo :1; unsigned match_client :1; unsigned match_reverse :1; @@ -69,6 +76,10 @@ static void strlist_config_delete(strlist_config_t **config) array_deep_wipe((*config)->tries, trie_delete); array_wipe((*config)->weights); array_wipe((*config)->reverses); + array_wipe((*config)->partiales); + array_wipe((*config)->hosts); + array_wipe((*config)->host_offsets); + array_wipe((*config)->host_weights); p_delete(config); } } @@ -109,8 +120,8 @@ static trie_t *strlist_create(const char *file, bool reverse, bool lock) --end; } if (end != map.end) { - syslog(LOG_WARNING, "file %s miss a final \\n, ignoring last line", - file); + warn("file %s miss a final \\n, ignoring last line", + file); } db = trie_new(); @@ -120,7 +131,7 @@ static trie_t *strlist_create(const char *file, bool reverse, bool lock) eol = end; } if (eol - p >= BUFSIZ) { - syslog(LOG_ERR, "unreasonnable long line"); + err("unreasonnable long line"); file_map_close(&map); trie_delete(&db); return NULL; @@ -145,6 +156,86 @@ static trie_t *strlist_create(const char *file, bool reverse, bool lock) return db; } +static bool strlist_create_from_rhbl(const char *file, bool lock, + trie_t **phosts, trie_t **pdomains) +{ + trie_t *hosts, *domains; + uint32_t host_count, domain_count; + file_map_t map; + const char *p, *end; + char line[BUFSIZ]; + + if (!file_map_open(&map, file, false)) { + return false; + } + p = map.map; + end = map.end; + while (end > p && end[-1] != '\n') { + --end; + } + if (end != map.end) { + warn("file %s miss a final \\n, ignoring last line", + file); + } + + hosts = trie_new(); + host_count = 0; + domains = trie_new(); + domain_count = 0; + while (p < end && p != NULL) { + const char *eol = (char *)memchr(p, '\n', end - p); + if (eol == NULL) { + eol = end; + } + if (eol - p >= BUFSIZ) { + err("unreasonnable long line"); + file_map_close(&map); + trie_delete(&hosts); + trie_delete(&domains); + return false; + } + if (*p != '#') { + const char *eos = eol; + while (p < eos && isspace(*p)) { + ++p; + } + while (p < eos && isspace(eos[-1])) { + --eos; + } + if (p < eos) { + if (isalnum(*p)) { + strlist_copy(line, p, eos - p, true); + trie_insert(hosts, line); + ++host_count; + } else if (*p == '*') { + ++p; + strlist_copy(line, p, eos - p, true); + trie_insert(domains, line); + ++domain_count; + } + } + } + p = eol + 1; + } + file_map_close(&map); + if (host_count > 0) { + trie_compile(hosts, lock); + *phosts = hosts; + } else { + trie_delete(&hosts); + *phosts = NULL; + } + if (domain_count > 0) { + trie_compile(domains, lock); + *pdomains = domains; + } else { + trie_delete(&domains); + *pdomains = NULL; + } + return hosts != NULL || domains != NULL; + +} + static bool strlist_filter_constructor(filter_t *filter) { @@ -152,7 +243,7 @@ static bool strlist_filter_constructor(filter_t *filter) #define PARSE_CHECK(Expr, Str, ...) \ if (!(Expr)) { \ - syslog(LOG_ERR, Str, ##__VA_ARGS__); \ + err(Str, ##__VA_ARGS__); \ strlist_config_delete(&config); \ return false; \ } @@ -162,7 +253,7 @@ static bool strlist_filter_constructor(filter_t *filter) foreach (filter_param_t *param, filter->params) { switch (param->type) { /* file parameter is: - * [no]lock:(prefix|suffix):weight:filename + * [no]lock:(partial-)(prefix|suffix):weight:filename * valid options are: * - lock: memlock the database in memory. * - nolock: don't memlock the database in memory. @@ -177,6 +268,7 @@ static bool strlist_filter_constructor(filter_t *filter) bool lock = false; int weight = 0; bool reverse = false; + bool partial = false; trie_t *trie = NULL; const char *current = param->value; const char *p = m_strchrnul(param->value, ':'); @@ -193,18 +285,23 @@ static bool strlist_filter_constructor(filter_t *filter) lock = false; } else { PARSE_CHECK(false, "illegal locking state %.*s", - p - current, current); + (int)(p - current), current); } break; case 1: + if (p - current > (ssize_t)strlen("partial-") + && strncmp(current, "partial-", strlen("partial-")) == 0) { + partial = true; + current += strlen("partial-"); + } if ((p - current) == 6 && strncmp(current, "suffix", 6) == 0) { reverse = true; } else if ((p - current) == 6 && strncmp(current, "prefix", 6) == 0) { reverse = false; } else { PARSE_CHECK(false, "illegal character order value %.*s", - p - current, current); + (int)(p - current), current); } break; @@ -212,7 +309,7 @@ static bool strlist_filter_constructor(filter_t *filter) weight = strtol(current, &next, 10); PARSE_CHECK(next == p && weight >= 0 && weight <= 1024, "illegal weight value %.*s", - (p - current), current); + (int)(p - current), current); break; case 3: @@ -222,10 +319,112 @@ static bool strlist_filter_constructor(filter_t *filter) array_add(config->tries, trie); array_add(config->weights, weight); array_add(config->reverses, reverse); + array_add(config->partiales, partial); break; } - current = p + 1; - p = m_strchrnul(current, ':'); + if (i != 3) { + current = p + 1; + p = m_strchrnul(current, ':'); + } + } + } break; + + /* rbldns parameter is: + * [no]lock::weight:filename + * valid options are: + * - lock: memlock the database in memory. + * - nolock: don't memlock the database in memory. + * - \d+: a number describing the weight to give to the match + * the given list [mandatory] + * directly import a file issued from a rhbl in rbldns format. + */ + case ATK_RBLDNS: { + bool lock = false; + int weight = 0; + trie_t *trie_hosts = NULL; + trie_t *trie_domains = NULL; + const char *current = param->value; + const char *p = m_strchrnul(param->value, ':'); + char *next = NULL; + for (int i = 0 ; i < 3 ; ++i) { + PARSE_CHECK(i == 2 || *p, + "file parameter must contains a locking state " + "and a weight option"); + switch (i) { + case 0: + if ((p - current) == 4 && strncmp(current, "lock", 4) == 0) { + lock = true; + } else if ((p - current) == 6 && strncmp(current, "nolock", 6) == 0) { + lock = false; + } else { + PARSE_CHECK(false, "illegal locking state %.*s", + (int)(p - current), current); + } + break; + + case 1: + weight = strtol(current, &next, 10); + PARSE_CHECK(next == p && weight >= 0 && weight <= 1024, + "illegal weight value %.*s", + (int)(p - current), current); + break; + + case 2: + PARSE_CHECK(strlist_create_from_rhbl(current, lock, + &trie_hosts, &trie_domains), + "cannot load string list from rhbl %s", current); + if (trie_hosts != NULL) { + array_add(config->tries, trie_hosts); + array_add(config->weights, weight); + array_add(config->reverses, true); + array_add(config->partiales, false); + } + if (trie_domains != NULL) { + array_add(config->tries, trie_domains); + array_add(config->weights, weight); + array_add(config->reverses, true); + array_add(config->partiales, true); + } + config->is_hostname = true; + break; + } + if (i != 2) { + current = p + 1; + p = m_strchrnul(current, ':'); + } + } + } break; + + /* dns parameter. + * weight:hostname. + * define a RBL to use through DNS resolution. + */ + case ATK_DNS: { + int weight = 0; + const char *current = param->value; + const char *p = m_strchrnul(param->value, ':'); + char *next = NULL; + for (int i = 0 ; i < 2 ; ++i) { + PARSE_CHECK(i == 1 || *p, + "host parameter must contains a weight option"); + switch (i) { + case 0: + weight = strtol(current, &next, 10); + PARSE_CHECK(next == p && weight >= 0 && weight <= 1024, + "illegal weight value %.*s", + (int)(p - current), current); + break; + + case 1: + array_add(config->host_offsets, array_len(config->hosts)); + array_append(config->hosts, current, strlen(current) + 1); + array_add(config->host_weights, weight); + break; + } + if (i != 1) { + current = p + 1; + p = m_strchrnul(current, ':'); + } } } break; @@ -265,11 +464,13 @@ static bool strlist_filter_constructor(filter_t *filter) CASE(HELO_NAME, helo, hostname); CASE(CLIENT_NAME, client, hostname); CASE(REVERSE_CLIENT_NAME, reverse, hostname); + CASE(SENDER_DOMAIN, sender, hostname); + CASE(RECIPIENT_DOMAIN, recipient, hostname); CASE(SENDER, sender, email); CASE(RECIPIENT, recipient, email); #undef CASE default: - PARSE_CHECK(false, "unknown field %.*s", p - current, current); + PARSE_CHECK(false, "unknown field %.*s", (int)(p - current), current); break; } if (!*p) { @@ -286,7 +487,7 @@ static bool strlist_filter_constructor(filter_t *filter) PARSE_CHECK(config->is_email != config->is_hostname, "matched field MUST be emails XOR hostnames"); - PARSE_CHECK(config->tries.len, + PARSE_CHECK(config->tries.len || config->host_offsets.len, "no file parameter in the filter %s", filter->name); filter->data = config; return true; @@ -305,40 +506,88 @@ static filter_result_t strlist_filter(const filter_t *filter, const query_t *que char normal[BUFSIZ]; const strlist_config_t *config = filter->data; int sum = 0; + bool error = true; + + if (config->is_email && ((config->match_sender && query->state < SMTP_MAIL) || (config->match_recipient && query->state != SMTP_RCPT))) { - syslog(LOG_WARNING, "trying to match an email against a field that is not " - "available in current protocol state"); + warn("trying to match an email against a field that is not " + "available in current protocol state"); return HTK_ABORT; } else if (config->is_hostname && config->match_helo && query->state < SMTP_HELO) { - syslog(LOG_WARNING, "trying to match hostname against helo before helo " - "is received"); + warn("trying to match hostname against helo before helo is received"); return HTK_ABORT; } #define LOOKUP(Flag, Field) \ - if (config->match_ ## Flag) { \ - const int len = m_strlen(query->Field); \ - strlist_copy(normal, query->Field, len, false); \ - strlist_copy(reverse, query->Field, len, true); \ - for (int i = 0 ; i < config->tries.len ; ++i) { \ - const int weight = array_elt(config->weights, i); \ - const trie_t *trie = array_elt(config->tries, i); \ - const bool rev = array_elt(config->reverses, i); \ - if (trie_lookup(trie, rev ? reverse : normal)) { \ - sum += weight; \ - } \ - } \ + if (config->match_ ## Flag) { \ + const int len = m_strlen(query->Field); \ + strlist_copy(normal, query->Field, len, false); \ + strlist_copy(reverse, query->Field, len, true); \ + for (uint32_t i = 0 ; i < config->tries.len ; ++i) { \ + const int weight = array_elt(config->weights, i); \ + const trie_t *trie = array_elt(config->tries, i); \ + const bool rev = array_elt(config->reverses, i); \ + const bool part = array_elt(config->partiales, i); \ + if ((!part && trie_lookup(trie, rev ? reverse : normal)) \ + || (part && trie_prefix(trie, rev ? reverse : normal))) { \ + sum += weight; \ + if (sum >= config->hard_threshold) { \ + return HTK_HARD_MATCH; \ + } \ + } \ + error = false; \ + } \ } +#define DNS(Flag, Field) \ + if (config->match_ ## Flag) { \ + const int len = m_strlen(query->Field); \ + strlist_copy(normal, query->Field, len, false); \ + for (uint32_t i = 0 ; len > 0 && i < config->host_offsets.len ; ++i) { \ + const char *rbl = array_ptr(config->hosts, \ + array_elt(config->host_offsets, i));\ + const int weight = array_elt(config->host_weights, i); \ + switch (rhbl_check(normal, rbl)) { \ + case RBL_FOUND: \ + error = false; \ + sum += weight; \ + if (sum >= config->hard_threshold) { \ + return HTK_HARD_MATCH; \ + } \ + break; \ + case RBL_NOTFOUND: \ + error = false; \ + break; \ + case RBL_ERROR: \ + warn("rbl %s unavailable", rbl); \ + break; \ + } \ + } \ + } + if (config->is_email) { LOOKUP(sender, sender); LOOKUP(recipient, recipient); + DNS(sender, sender); + DNS(recipient, recipient); } else if (config->is_hostname) { LOOKUP(helo, helo_name); LOOKUP(client, client_name); LOOKUP(reverse, reverse_client_name); + LOOKUP(recipient, recipient_domain); + LOOKUP(sender, sender_domain); + DNS(helo, helo_name); + DNS(client, client_name); + DNS(reverse, reverse_client_name); + DNS(recipient, recipient_domain); + DNS(sender, sender_domain); } +#undef DNS #undef LOOKUP + if (error) { + err("filter %s: all the rbls returned an error", filter->name); + return HTK_ERROR; + } if (sum >= config->hard_threshold) { return HTK_HARD_MATCH; } else if (sum >= config->soft_threshold) { @@ -363,6 +612,8 @@ static int strlist_init(void) /* Parameters. */ (void)filter_param_register(type, "file"); + (void)filter_param_register(type, "rbldns"); + (void)filter_param_register(type, "dns"); (void)filter_param_register(type, "hard_threshold"); (void)filter_param_register(type, "soft_threshold"); (void)filter_param_register(type, "fields");