return m_stristrn(haystack, needle, m_strlen(needle));
}
+/****************************************************************************/
+/* static strings */
+/****************************************************************************/
+
+/** Store a pointer to a string with a pre-computed length.
+ * This intends to store pointers to a part of a longer string and to avoid
+ * useless strlen.
+ */
+typedef struct static_str_t {
+ const char *str;
+ ssize_t len;
+} static_str_t;
+
/*@}*/
#endif /* PFIXTOOLS_STR_H */
filter_tokens.h filter_tokens.c \
hook_tokens.h hook_tokens.c \
param_tokens.h param_tokens.c
-TESTS = tst-rbl tst-filters tst-greylist
+TESTS = tst-rbl tst-filters tst-greylist tst-qf
UB_LIBS = -lunbound
tst-greylist_SOURCES = tst-greylist.c resources.c ../common/lib.a
tst-greylist_LIBADD = $(TC_LIBS)
-hook_tokens.h hook_tokens.c: $(FILTERS)
-param_tokens.c param_tokens.h: $(FILTERS) config.c
+tst-qf_SOURCES = tst-qf.c query.c ../common/lib.a $(GENERATED)
all:
+hook_tokens.h hook_tokens.c: $(FILTERS)
+param_tokens.c param_tokens.h: $(FILTERS) config.c
+
include ../mk/common.mk
#include "str.h"
#include "resources.h"
+static const static_str_t static_cleanup = { "@@cleanup@@", 11 };
typedef struct greylist_config_t {
unsigned lookup_by_host : 1;
{
int len = 0;
time_t now = time(NULL);
- const time_t *last_cleanup = tcbdbget3(db, "@@cleanup@@", strlen("@@cleanup@@"), &len);
+ const time_t *last_cleanup = tcbdbget3(db, static_cleanup.str, static_cleanup.len, &len);
if (last_cleanup == NULL) {
debug("No last cleanup time");
} else {
}
++old_count;
} while (tcbdbcurnext(cur));
- tcbdbput(tmp_db, "@@cleanup@@", strlen("@@cleanup@@"), &now, sizeof(now));
+ tcbdbput(tmp_db, static_cleanup.str, static_cleanup.len, &now, sizeof(now));
}
tcxstrdel(key);
tcxstrdel(value);
static bool try_greylist(const greylist_config_t *config,
- const char *sender, const char *c_addr,
- const char *c_name, const char *rcpt)
+ const static_str_t *sender, const static_str_t *c_addr,
+ const static_str_t *c_name, const static_str_t *rcpt)
{
#define INCR_AWL \
aent.count++; \
aent.last = now; \
debug("whitelist entry for %.*s updated, count %d", \
- c_addrlen, c_addr, aent.count); \
- tcbdbput(awl_db, c_addr, c_addrlen, &aent, sizeof(aent));
+ c_addr->len, c_addr->str, aent.count); \
+ tcbdbput(awl_db, c_addr->str, c_addr->len, &aent, sizeof(aent));
char sbuf[BUFSIZ], cnet[64], key[BUFSIZ];
const void *res;
struct obj_entry oent = { now, now };
struct awl_entry aent = { 0, 0 };
- int len, klen, c_addrlen = strlen(c_addr);
+ int len, klen;
TCBDB * const awl_db = config->awl_db ? *(config->awl_db) : NULL;
TCBDB * const obj_db = config->obj_db ? *(config->obj_db) : NULL;
/* Auto whitelist clients.
*/
if (config->client_awl) {
- res = tcbdbget3(awl_db, c_addr, c_addrlen, &len);
+ res = tcbdbget3(awl_db, c_addr->str, c_addr->len, &len);
if (res && len == sizeof(aent)) {
memcpy(&aent, res, len);
debug("client %.*s has a whitelist entry, count is %d",
- c_addrlen, c_addr, aent.count);
+ c_addr->len, c_addr->str, aent.count);
}
if (!greylist_check_awlentry(config, &aent, now)) {
aent.count = 0;
aent.last = 0;
debug("client %.*s whitelist entry too old",
- c_addrlen, c_addr);
+ c_addr->len, c_addr->str);
}
/* Whitelist if count is enough.
*/
if (aent.count >= config->client_awl) {
- debug("client %.*s whitelisted", c_addrlen, c_addr);
+ debug("client %.*s whitelisted", c_addr->len, c_addr->str);
if (now < aent.last + 3600) {
INCR_AWL
}
/* Lookup.
*/
klen = snprintf(key, sizeof(key), "%s/%s/%s",
- c_net(config, c_addr, c_name, cnet, sizeof(cnet)),
- config->no_sender ? "" : sender_normalize(sender, sbuf, sizeof(sbuf)),
- config->no_recipient ? "" : rcpt);
+ c_net(config, c_addr->str, c_name->str, cnet, sizeof(cnet)),
+ config->no_sender ? "" : sender_normalize(sender->str, sbuf, sizeof(sbuf)),
+ config->no_recipient ? "" : rcpt->str);
klen = MIN(klen, ssizeof(key) - 1);
res = tcbdbget3(obj_db, key, klen, &len);
return HTK_ABORT;
}
- return try_greylist(config, query->sender, query->client_address,
- query->client_name, query->recipient) ?
+ return try_greylist(config, &query->sender, &query->client_address,
+ &query->client_name, &query->recipient) ?
HTK_WHITELIST : HTK_GREYLIST;
}
const iplist_filter_t *data = filter->data;
bool error = true;
- if (parse_ipv4(query->client_address, &end, &ip) != 0) {
- if (strchr(query->client_address, ':')) {
+ if (parse_ipv4(query->client_address.str, &end, &ip) != 0) {
+ if (strchr(query->client_address.str, ':')) {
/* iplist only works on IPv4 */
return HTK_FAIL;
}
warn("invalid client address: %s, expected ipv4",
- query->client_address);
+ query->client_address.str);
return HTK_ERROR;
}
for (uint32_t i = 0 ; i < data->rbls.len ; ++i) {
const query_t* query = &context->query;
const filter_t *filter;
if (mconfig->entry_points[query->state] == -1) {
- warn("no filter defined for current protocol_state (%s)", smtp_state_names[query->state]);
+ warn("no filter defined for current protocol_state (%s)", smtp_state_names[query->state].str);
return false;
}
if (context->context.current_filter != NULL) {
query->eoq = eoq + strlen("\n\n");
/* The instance changed => reset the static context */
- if (query->instance == NULL || strcmp(context->context.instance, query->instance) != 0) {
+ if (query->instance.str == NULL || query->instance.len == 0
+ || strcmp(context->context.instance, query->instance.str) != 0) {
filter_context_clean(&context->context);
- m_strcat(context->context.instance, 64, query->instance);
+ m_strcat(context->context.instance, 64, query->instance.str);
}
client_io_none(pcy);
return policy_process(pcy, mconfig) ? 0 : -1;
MATCH_EMPTY,
} condition;
- char *value;
- ssize_t value_len;
+ static_str_t value;
} match_condition_t;
ARRAY(match_condition_t)
"is empty"
};
-#define CONDITION_INIT { PTK_UNKNOWN, false, MATCH_UNKNOWN, NULL, 0 }
+#define CONDITION_INIT { PTK_UNKNOWN, false, MATCH_UNKNOWN, { NULL, 0 } }
typedef struct match_config_t {
A(match_condition_t) conditions;
static inline void match_condition_wipe(match_condition_t *condition)
{
- p_delete(&condition->value);
- condition->value_len = 0;
+ char *str = (char*)condition->value.str;
+ p_delete(&str);
+ condition->value.str = NULL;
+ condition->value.len = 0;
}
static void match_config_delete(match_config_t **config)
if (condition.condition != MATCH_EMPTY) {
p = skipspaces(n + 1);
PARSE_CHECK(*p, "no value defined to check the condition");
- condition.value_len = param->value_len - (p - param->value);
- condition.value = p_dupstr(p, condition.value_len);
+ condition.value.len = param->value_len - (p - param->value);
+ condition.value.str = p_dupstr(p, condition.value.len);
}
array_add(config->conditions, condition);
} break;
static inline bool match_condition(const match_condition_t *cond, const query_t *query)
{
- const char *field = query_field_for_id(query, cond->field);
+ const static_str_t *field = query_field_for_id(query, cond->field);
debug("running condition: \"%s\" %s %s\"%s\"",
- field, condition_names[cond->condition],
+ field->str, condition_names[cond->condition],
cond->case_sensitive ? "" : "(alternative) ",
- cond->value ? cond->value : "(none)");
+ cond->value.str ? cond->value.str : "(none)");
switch (cond->condition) {
case MATCH_EQUAL:
case MATCH_DIFFER:
- if (field == NULL) {
+ if (field == NULL || field->str == NULL) {
return cond->condition != MATCH_DIFFER;
}
if (cond->case_sensitive) {
- return !!((strcmp(field, cond->value) == 0)
+ return !!((strcmp(field->str, cond->value.str) == 0)
^ (cond->condition == MATCH_DIFFER));
} else {
- return !!((ascii_strcasecmp(field, cond->value) == 0)
+ return !!((ascii_strcasecmp(field->str, cond->value.str) == 0)
^ (cond->condition == MATCH_DIFFER));
}
break;
case MATCH_CONTAINS:
- if (field == NULL) {
+ if (field == NULL || field->str == NULL) {
return false;
}
if (cond->case_sensitive) {
- return strstr(field, cond->value);
+ return strstr(field->str, cond->value.str);
} else {
- return m_stristrn(field, cond->value, cond->value_len);
+ return m_stristrn(field->str, cond->value.str, cond->value.len);
}
break;
case MATCH_CONTAINED:
- if (field == NULL) {
+ if (field == NULL || field->str == NULL) {
return false;
}
if (cond->case_sensitive) {
- return strstr(cond->value, field);
+ return strstr(cond->value.str, field->str);
} else {
- return m_stristr(cond->value, field);
+ return m_stristr(cond->value.str, field->str);
}
break;
case MATCH_EMPTY:
- return !!((field == NULL || *field == '\0') ^ (!cond->case_sensitive));
+ return !!((field == NULL || field->len == 0) ^ (!cond->case_sensitive));
default:
assert(false && "invalid condition type");
#include "policy_tokens.h"
#include "str.h"
-const char *smtp_state_names[SMTP_count] = {
- "CONNECT",
- "HELO",
- "MAIL",
- "RCPT",
- "DATA",
- "END-OF-MESSAGE",
- "VRFY",
- "ETRN",
+const static_str_t smtp_state_names[SMTP_count] = {
+ { "CONNECT", 7 },
+ { "HELO", 4 },
+ { "MAIL", 4 },
+ { "RCPT", 4 },
+ { "DATA", 4 },
+ { "END-OF-MESSAGE", 14 },
+ { "VRFY", 4 },
+ { "ETRN", 4 },
};
+static const static_str_t static_ESMTP = { "ESMTP", 5 };
+static const static_str_t static_SMTP = { "SMTP", 4 };
+
bool query_parse(query_t *query, char *p)
{
#define PARSE_CHECK(expr, error, ...) \
vtk = policy_tokenize(v, vlen);
switch (policy_tokenize(k, klen)) {
-#define CASE(up, low) case PTK_##up: query->low = v; v[vlen] = '\0'; break;
+#define CASE(up, low) case PTK_##up: query->low.str = v; query->low.len = vlen; v[vlen] = '\0'; break;
CASE(HELO_NAME, helo_name);
CASE(QUEUE_ID, queue_id);
CASE(RECIPIENT_COUNT, recipient_count);
#undef CASE
case PTK_SENDER:
- query->sender = v;
+ query->sender.str = v;
+ query->sender.len = vlen;
v[vlen] = '\0';
- query->sender_domain = memchr(query->sender, '@', vlen);
- if (query->sender_domain != NULL) {
- ++query->sender_domain;
+ query->sender_domain.str = memchr(query->sender.str, '@', vlen);
+ if (query->sender_domain.str != NULL) {
+ ++query->sender_domain.str;
+ query->sender_domain.len = query->sender.len
+ - (query->sender_domain.str - query->sender.str);
}
break;
case PTK_RECIPIENT:
- query->recipient = v;
+ query->recipient.str = v;
+ query->recipient.len = vlen;
v[vlen] = '\0';
- query->recipient_domain = memchr(query->recipient, '@', vlen);
- if (query->recipient_domain != NULL) {
- ++query->recipient_domain;
+ query->recipient_domain.str = memchr(query->recipient.str, '@', vlen);
+ if (query->recipient_domain.str != NULL) {
+ ++query->recipient_domain.str;
+ query->recipient_domain.len = query->recipient.len
+ - (query->recipient_domain.str - query->recipient.str);
+
}
break;
#undef PARSE_CHECK
}
-const char *query_field_for_id(const query_t *query, postlicyd_token id)
+const static_str_t *query_field_for_id(const query_t *query, postlicyd_token id)
{
switch (id) {
#define CASE(Up, Low) \
- case PTK_ ## Up: return query->Low;
+ case PTK_ ## Up: return &query->Low;
CASE(HELO_NAME, helo_name)
CASE(QUEUE_ID, queue_id)
CASE(SENDER, sender)
CASE(STRESS, stress)
#undef CASE
case PTK_PROTOCOL_NAME:
- return query->esmtp ? "ESMTP" : "SMTP";
+ return query->esmtp ? &static_ESMTP : &static_SMTP;
case PTK_PROTOCOL_STATE:
- return smtp_state_names[query->state];
+ return &smtp_state_names[query->state];
default: return NULL;
}
}
-const char *query_field_for_name(const query_t *query, const char *name)
+const static_str_t *query_field_for_name(const query_t *query, const char *name)
{
postlicyd_token id = policy_tokenize(name, strlen(name));
if (id == PTK_UNKNOWN) {
{
size_t written = 0;
size_t pos = 0;
- const char *end = fmt + m_strlen(fmt);
#define WRITE(Src, Len) \
do { \
pos += __len; \
} while (0)
while (*fmt != '\0') {
- const char *next_format = strstr(fmt, "${");
+ const char *next_format = strchr(fmt, '$');
+ while (next_format != NULL && next_format[1] != '{') {
+ next_format = strchr(next_format + 1, '$');
+ }
if (next_format == NULL) {
- next_format = end;
+ next_format = fmt + m_strlen(fmt);
}
WRITE(fmt, next_format - fmt);
fmt = next_format;
if (tok == PTK_UNKNOWN) {
warn("unknown field name \"%.*s\"", (int)(next_format - fmt), fmt);
}
- const char *field = query == NULL ? NULL : query_field_for_id(query, tok);
+ const static_str_t *field = query == NULL ? NULL
+ : query_field_for_id(query, tok);
if (field == NULL) {
WRITE("(null)", 6);
} else {
- WRITE(field, m_strlen(field));
+ WRITE(field->str, field->len);
}
fmt = next_format + 1;
}
SMTP_UNKNOWN,
};
-extern const char *smtp_state_names[SMTP_count];
+extern const static_str_t smtp_state_names[SMTP_count];
/* \see http://www.postfix.org/SMTPD_POLICY_README.html */
typedef struct query_t {
unsigned state : 4;
unsigned esmtp : 1;
- const char *helo_name;
- const char *queue_id;
- const char *sender;
- const char *recipient;
- const char *recipient_count;
- const char *client_address;
- const char *client_name;
- const char *reverse_client_name;
- const char *instance;
+ static_str_t helo_name;
+ static_str_t queue_id;
+ static_str_t sender;
+ static_str_t recipient;
+ static_str_t recipient_count;
+ static_str_t client_address;
+ static_str_t client_name;
+ static_str_t reverse_client_name;
+ static_str_t instance;
/* useful data extracted from previous ones */
- const char *sender_domain;
- const char *recipient_domain;
+ static_str_t sender_domain;
+ static_str_t recipient_domain;
/* postfix 2.2+ */
- const char *sasl_method;
- const char *sasl_username;
- const char *sasl_sender;
- const char *size;
- const char *ccert_subject;
- const char *ccert_issuer;
- const char *ccert_fingerprint;
+ static_str_t sasl_method;
+ static_str_t sasl_username;
+ static_str_t sasl_sender;
+ static_str_t size;
+ static_str_t ccert_subject;
+ static_str_t ccert_issuer;
+ static_str_t ccert_fingerprint;
/* postfix 2.3+ */
- const char *encryption_protocol;
- const char *encryption_cipher;
- const char *encryption_keysize;
- const char *etrn_domain;
+ static_str_t encryption_protocol;
+ static_str_t encryption_cipher;
+ static_str_t encryption_keysize;
+ static_str_t etrn_domain;
/* postfix 2.5+ */
- const char *stress;
+ static_str_t stress;
const char *eoq;
} query_t;
/** Return the value of the field with the given name.
*/
__attribute__((nonnull(1,2)))
-const char *query_field_for_name(const query_t *query, const char *name);
+const static_str_t *query_field_for_name(const query_t *query, const char *name);
/** Returns the value of the field with the given id.
*/
__attribute__((nonnull))
-const char *query_field_for_id(const query_t *query, postlicyd_token id);
+const static_str_t *query_field_for_id(const query_t *query, postlicyd_token id);
/** Formats the given string by replacing ${field_name} with the content
* of the query.
}
#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); \
+ const int len = query->Field.len; \
+ strlist_copy(normal, query->Field.str, len, false); \
+ strlist_copy(reverse, query->Field.str, len, true); \
foreach (strlist_local_t *entry, config->locals) { \
if ((!entry->partial && trie_lookup(*(entry->db), \
entry->reverse ? reverse : normal)) \
}
#define DNS(Flag, Field) \
if (config->match_ ## Flag) { \
- const int len = m_strlen(query->Field); \
- strlist_copy(normal, query->Field, len, false); \
+ const int len = query->Field.len; \
+ strlist_copy(normal, query->Field.str, 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)); \
--- /dev/null
+/******************************************************************************/
+/* pfixtools: a collection of postfix related tools */
+/* ~~~~~~~~~ */
+/* ________________________________________________________________________ */
+/* */
+/* Redistribution and use in source and binary forms, with or without */
+/* modification, are permitted provided that the following conditions */
+/* are met: */
+/* */
+/* 1. Redistributions of source code must retain the above copyright */
+/* notice, this list of conditions and the following disclaimer. */
+/* 2. Redistributions in binary form must reproduce the above copyright */
+/* notice, this list of conditions and the following disclaimer in the */
+/* documentation and/or other materials provided with the distribution. */
+/* 3. The names of its contributors may not be used to endorse or promote */
+/* products derived from this software without specific prior written */
+/* permission. */
+/* */
+/* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND */
+/* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE */
+/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR */
+/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS */
+/* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR */
+/* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF */
+/* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS */
+/* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN */
+/* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) */
+/* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF */
+/* THE POSSIBILITY OF SUCH DAMAGE. */
+/******************************************************************************/
+
+/*
+ * Copyright © 2008 Florent Bruneau
+ */
+
+#include "common.h"
+#include "file.h"
+#include "query.h"
+
+static bool read_query(const char *base, const char *file, query_t *query,
+ char *buff)
+{
+ char path[FILENAME_MAX];
+ snprintf(path, FILENAME_MAX, "%s%s", base, file);
+ {
+ file_map_t map;
+ if (!file_map_open(&map, path, false)) {
+ UNIXERR("open");
+ return false;
+ }
+ if (map.end - map.map >= BUFSIZ) {
+ err("File too large for a testcase: %s", path);
+ file_map_close(&map);
+ return false;
+ }
+ memcpy(buff, map.map, map.end - map.map);
+ buff[map.end - map.map] = '\0';
+ file_map_close(&map);
+ }
+
+ char *eoq = strstr(buff, "\n\n");
+ if (eoq == NULL) {
+ return false;
+ }
+ if (!query_parse(query, buff)) {
+ err("Cannot parse query from file %s", path);
+ return false;
+ }
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ char basepath[FILENAME_MAX];
+ char buff[BUFSIZ];
+ char *p;
+
+ p = strrchr(argv[0], '/');
+ if (p == NULL) {
+ p = argv[0];
+ } else {
+ ++p;
+ }
+ snprintf(basepath, FILENAME_MAX, "%.*sdata/", p - argv[0], argv[0]);
+
+ query_t q;
+ if (!read_query(basepath, "testcase_1", &q, buff)) {
+ return EXIT_FAILURE;
+ }
+
+ static const char *format = "${sender} ${recipient} and ${client_name}[${client_address}] at ${protocol_state}";
+
+ time_t now = time(0);
+
+ char str[BUFSIZ];
+ static const int iterations = 10000000;
+ for (int i = 0 ; i < iterations ; ++i) {
+ query_format(str, BUFSIZ, format, &q);
+ }
+
+ time_t ellapsed = time(0) - now;
+ printf("Done %d iterations in %us (%d format per second)\n", iterations,
+ (uint32_t)ellapsed, (int)(iterations / ellapsed));
+ return 0;
+}