diff options
author | Vsevolod Stakhov <vsevolod@rambler-co.ru> | 2010-07-06 20:38:03 +0400 |
---|---|---|
committer | Vsevolod Stakhov <vsevolod@rambler-co.ru> | 2010-07-06 20:38:03 +0400 |
commit | 75bf13b9bda0d1eb98671b68064becd4f6946c14 (patch) | |
tree | 91a5c03c1d4f394762301fa0aac588baacd99b49 /src | |
parent | 52c0b882c0b885a70d6e6af858c08771b6db3ea6 (diff) | |
download | rspamd-75bf13b9bda0d1eb98671b68064becd4f6946c14.tar.gz rspamd-75bf13b9bda0d1eb98671b68064becd4f6946c14.zip |
* Add ability to test regexp with 'T' flag
* Write more code for DNS resolver:
- initial RR parser
- name compression
- replies handler
Diffstat (limited to 'src')
-rw-r--r-- | src/cfg_file.h | 1 | ||||
-rw-r--r-- | src/dns.c | 265 | ||||
-rw-r--r-- | src/dns.h | 21 | ||||
-rw-r--r-- | src/expressions.c | 5 | ||||
-rw-r--r-- | src/expressions.h | 1 | ||||
-rw-r--r-- | src/plugins/regexp.c | 41 |
6 files changed, 317 insertions, 17 deletions
diff --git a/src/cfg_file.h b/src/cfg_file.h index aad42d88c..fa73643f0 100644 --- a/src/cfg_file.h +++ b/src/cfg_file.h @@ -82,6 +82,7 @@ struct rspamd_regexp { GRegex *regexp; /**< glib regexp structure */ GRegex *raw_regexp; /**< glib regexp structure for raw matching */ gchar *header; /**< header name for header regexps */ + gboolean is_test; /**< true if this expression must be tested */ }; /** @@ -47,6 +47,8 @@ #define DNS_RANDOM rand #endif +#define UDP_PACKET_SIZE 512 + /* * P E R M U T A T I O N G E N E R A T O R */ @@ -278,6 +280,40 @@ struct dns_request_key { guint16 port; }; +/** Message compression */ +struct dns_name_table { + guint8 off; + guint8 *label; + guint8 len; +}; + +static gboolean +try_compress_label (memory_pool_t *pool, guint8 *target, guint8 *start, guint8 len, guint8 *label, GList *table) +{ + GList *cur; + struct dns_name_table *tbl; + + cur = table; + while (cur) { + tbl = cur->data; + if (tbl->len == len) { + if (memcmp (label, tbl->label, len) == 0) { + *target = tbl->off | 0xC0; + return TRUE; + } + } + cur = g_list_next (cur); + } + + /* Insert label to list */ + tbl = memory_pool_alloc (pool, sizeof (struct dns_name_table)); + tbl->off = target - start; + tbl->label = label; + tbl->len = len; + table = g_list_prepend (table, tbl); + + return FALSE; +} /** Packet creating functions */ static void @@ -311,6 +347,7 @@ format_dns_name (struct rspamd_dns_request *req, const char *name, guint namelen { guint8 *pos = req->packet + req->pos, *begin, *end; guint remain = req->packet_len - req->pos - 5, label_len; + GList *table = NULL; if (namelen == 0) { namelen = strlen (name); @@ -320,7 +357,6 @@ format_dns_name (struct rspamd_dns_request *req, const char *name, guint namelen for (;;) { end = strchr (begin, '.'); if (end) { - label_len = end - begin; if (label_len > DNS_D_MAXLABEL) { msg_err ("dns name component is longer than 63 bytes, should be stripped"); label_len = DNS_D_MAXLABEL; @@ -329,11 +365,19 @@ format_dns_name (struct rspamd_dns_request *req, const char *name, guint namelen label_len = remain - 1; msg_err ("no buffer remain for constructing query, strip to %ud", label_len); } - *pos++ = (guint8)label_len; - memcpy (pos, begin, label_len); - pos += label_len; - remain -= label_len + 1; - begin = end + 1; + /* First try to compress name */ + if (! try_compress_label (req->pool, pos, req->packet, end - begin, begin, table)) { + label_len = end - begin; + + *pos++ = (guint8)label_len; + memcpy (pos, begin, label_len); + pos += label_len; + remain -= label_len + 1; + begin = end + 1; + } + else { + pos ++; + } } else { end = (guint8 *)name + namelen; @@ -364,6 +408,9 @@ format_dns_name (struct rspamd_dns_request *req, const char *name, guint namelen /* Termination label */ *(++pos) = '\0'; req->pos += pos - (req->packet + req->pos) + 1; + if (table != NULL) { + g_list_free (table); + } } static void @@ -386,6 +433,7 @@ make_ptr_req (struct rspamd_dns_request *req, struct in_addr addr) *p++ = htons (DNS_C_IN); *p = htons (DNS_T_PTR); req->pos += sizeof (guint16) * 2; + req->type = DNS_REQUEST_PTR; } static void @@ -400,6 +448,7 @@ make_a_req (struct rspamd_dns_request *req, const char *name) *p++ = htons (DNS_C_IN); *p = htons (DNS_T_A); req->pos += sizeof (guint16) * 2; + req->type = DNS_REQUEST_A; } static void @@ -414,7 +463,7 @@ make_txt_req (struct rspamd_dns_request *req, const char *name) *p++ = htons (DNS_C_IN); *p = htons (DNS_T_A); req->pos += sizeof (guint16) * 2; - + req->type = DNS_REQUEST_TXT; } static void @@ -429,7 +478,7 @@ make_mx_req (struct rspamd_dns_request *req, const char *name) *p++ = htons (DNS_C_IN); *p = htons (DNS_T_A); req->pos += sizeof (guint16) * 2; - + req->type = DNS_REQUEST_MX; } static int @@ -469,6 +518,193 @@ dns_fin_cb (gpointer arg) /* XXX: call callback if possible */ } +static guint8 * +decompress_label (guint8 *begin, guint8 *len) +{ + guint8 offset; + offset = (*len) ^ 0xC0; + + *len = *(begin + offset); + return begin + offset + 1; +} + +static guint8 * +dns_request_reply_cmp (struct rspamd_dns_request *req, guint8 *in, int len) +{ + guint8 *p, *c, *l1, *l2; + guint8 len1, len2; + + /* QR format: + * labels - len:octets + * null label - 0 + * class - 2 octets + * type - 2 octets + */ + + /* In p we would store current position in reply and in c - position in request */ + p = in; + c = req->packet + sizeof (struct dns_header); + + for (;;) { + /* Get current label */ + len1 = *p; + len2 = *c; + if (p - in > len) { + msg_info ("invalid dns reply"); + return NULL; + } + /* This may be compressed, so we need to decompress it */ + if (len1 & 0xC0) { + l1 = decompress_label (in, &len1); + p ++; + } + else { + l1 = ++p; + p += len1; + } + if (len2 & 0xC0) { + l2 = decompress_label (req->packet, &len2); + c ++; + } + else { + l2 = ++c; + c += len2; + } + if (len1 != len2) { + return NULL; + } + if (len1 == 0) { + break; + } + + if (memcmp (l1, l2, len1) != 0) { + return NULL; + } + } + + /* p now points to the end of QR section */ + /* Compare class and type */ + if (memcmp (p, c, sizeof (guint16) * 2) == 0) { + return p + sizeof (guint16) * 2; + } + return NULL; +} + +static gboolean +dns_parse_rr (union rspamd_reply_element *elt, guint8 **pos, struct rspamd_dns_reply *rep, int *remain) +{ + guint8 *p = *pos; + guint16 type, datalen; + + /* Skip the whole name */ + while (p - *pos < *remain) { + if (*p & 0xC0) { + p ++; + } + else if (*p == 0) { + p ++; + break; + } + else { + p += *p + 1; + } + } + if (p - *pos >= *remain - sizeof (guint16) * 5) { + msg_info ("stripped dns reply"); + return FALSE; + } + type = *((guint16 *)p); + /* Skip ttl and class */ + p += sizeof (guint16) * 2 + sizeof (guint32); + datalen = *((guint16 *)p); + p += sizeof (guint16); + *remain -= p - *pos; + /* Now p points to RR data */ + switch (type) { + case DNS_T_A: + if ((datalen & 0x3) && *remain >= datalen) { + elt->a.addr[0].s_addr = *((guint32 *)p); + p += sizeof (guint32); + } + else { + msg_info ("corrupted A record"); + return FALSE; + } + break; + } + *remain -= datalen; + *pos = p; +} + +static struct rspamd_dns_reply * +dns_parse_reply (guint8 *in, int r, struct rspamd_dns_resolver *resolver) +{ + struct dns_header *header = (struct dns_header *)in; + struct rspamd_dns_request *req; + struct rspamd_dns_reply *rep; + union rspamd_reply_element *elt; + guint8 *pos; + int i; + + /* First check header fields */ + if (header->qr == 0) { + msg_info ("got request while waiting for reply"); + return NULL; + } + + /* Now try to find corresponding request */ + if ((req = g_hash_table_lookup (resolver->requests, GUINT_TO_POINTER (header->qid))) == NULL) { + /* No such requests found */ + return NULL; + } + /* + * Now we have request and query data is now at the end of header, so compare + * request QR section and reply QR section + */ + if ((pos = dns_request_reply_cmp (req, in + sizeof (struct dns_header), r - sizeof (struct dns_header))) == NULL) { + return NULL; + } + /* + * Now pos is in answer section, so we should extract data and form reply + */ + rep = memory_pool_alloc (req->pool, sizeof (struct rspamd_dns_reply)); + rep->request = req; + rep->type = req->type; + rep->elements = NULL; + + r -= pos - in; + /* Extract RR records */ + for (i = 0; i < header->ancount; i ++) { + elt = memory_pool_alloc (req->pool, sizeof (union rspamd_reply_element)); + if (! dns_parse_rr (elt, &pos, rep, &r)) { + msg_info ("incomplete reply"); + break; + } + } + + return rep; +} + +static void +dns_read_cb (int fd, short what, void *arg) +{ + struct rspamd_dns_resolver *resolver = arg; + int i, r; + struct rspamd_dns_server *serv; + struct rspamd_dns_reply *rep; + guint8 in[UDP_PACKET_SIZE]; + + /* This function is called each time when we have data on one of server's sockets */ + + /* First read packet from socket */ + r = read (fd, in, sizeof (in)); + if (r > 96) { + if ((rep = dns_parse_reply (in, r, resolver)) != NULL) { + + } + } +} + static void dns_timer_cb (int fd, short what, void *arg) { @@ -676,7 +912,7 @@ dns_resolver_init (struct config_file *cfg) GList *cur; struct rspamd_dns_resolver *new; char *begin, *p; - int priority; + int priority, i; struct rspamd_dns_server *serv; new = memory_pool_alloc0 (cfg->cfg_pool, sizeof (struct rspamd_dns_resolver)); @@ -730,6 +966,17 @@ dns_resolver_init (struct config_file *cfg) } } + /* Now init all servers */ + for (i = 0; i < new->servers_num; i ++) { + serv = &new->servers[i]; + serv->sock = make_udp_socket (&serv->addr, htons (53), FALSE, TRUE); + if (serv->sock == -1) { + msg_warn ("cannot create socket to server %s", serv->name); + } + else { + event_set (&serv->ev, serv->sock, EV_READ, dns_read_cb, new); + } + } return new; } @@ -11,6 +11,8 @@ #define DNS_D_MAXLABEL 63 /* + 1 '\0' */ #define DNS_D_MAXNAME 255 /* + 1 '\0' */ +#define MAX_ADDRS 64 + struct rspamd_dns_reply; struct config_file; @@ -23,6 +25,7 @@ struct rspamd_dns_server { struct in_addr addr; /**< address of DNS server */ char *name; /**< name of DNS server */ int sock; /**< persistent socket */ + struct event ev; }; #define DNS_K_TEA_KEY_SIZE 16 @@ -52,6 +55,13 @@ struct rspamd_dns_resolver { struct dns_header; struct dns_query; +enum rspamd_request_type { + DNS_REQUEST_A = 0, + DNS_REQUEST_PTR, + DNS_REQUEST_MX, + DNS_REQUEST_TXT +}; + struct rspamd_dns_request { memory_pool_t *pool; /**< pool associated with request */ struct rspamd_dns_resolver *resolver; @@ -69,18 +79,15 @@ struct rspamd_dns_request { off_t pos; guint packet_len; int sock; + enum rspamd_request_type type; }; -enum rspamd_request_type { - DNS_REQUEST_A = 0, - DNS_REQUEST_PTR, - DNS_REQUEST_MX, - DNS_REQUEST_TXT -}; + union rspamd_reply_element { struct { - struct in_addr addr; + struct in_addr addr[MAX_ADDRS]; + guint16 addrcount; } a; struct { char *name; diff --git a/src/expressions.c b/src/expressions.c index f19e67670..c4465742c 100644 --- a/src/expressions.c +++ b/src/expressions.c @@ -244,6 +244,7 @@ is_regexp_flag (char a) case 'P': case 'U': case 'X': + case 'T': return TRUE; default: return FALSE; @@ -671,6 +672,10 @@ parse_regexp (memory_pool_t * pool, char *line, gboolean raw_mode) } p++; break; + case 'T': + result->is_test = TRUE; + p ++; + break; /* Stop flags parsing */ default: p = NULL; diff --git a/src/expressions.h b/src/expressions.h index 14ae03f4f..6ffc7359d 100644 --- a/src/expressions.h +++ b/src/expressions.h @@ -9,6 +9,7 @@ #include "config.h" struct worker_task; +struct rspamd_regexp; /** * Rspamd expression function diff --git a/src/plugins/regexp.c b/src/plugins/regexp.c index c660f16c3..58e91d14b 100644 --- a/src/plugins/regexp.c +++ b/src/plugins/regexp.c @@ -562,7 +562,13 @@ tree_url_callback (gpointer key, gpointer value, void *data) struct url_regexp_param *param = data; struct uri *url = value; + if (G_UNLIKELY (param->re->is_test)) { + msg_info ("process test regexp /%s/ for url %s", struri (url)); + } if (g_regex_match (param->regexp, struri (url), 0, NULL) == TRUE) { + if (G_UNLIKELY (param->re->is_test)) { + msg_info ("process test regexp /%s/ for url %s returned TRUE", struri (url)); + } task_cache_add (param->task, param->re, 1); param->found = TRUE; return TRUE; @@ -599,7 +605,13 @@ process_regexp (struct rspamd_regexp *re, struct worker_task *task, const char * if (additional != NULL) { /* We have additional parameter defined, so ignore type of regexp expression and use it for parsing */ + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ with test %s", re->regexp_text, additional); + } if (g_regex_match_full (re->regexp, additional, strlen (additional), 0, 0, NULL, NULL) == TRUE) { + if (G_UNLIKELY (re->is_test)) { + msg_info ("result of regexp /%s/ is true", re->regexp_text); + } task_cache_add (task, re, 1); return 1; } @@ -620,6 +632,9 @@ process_regexp (struct rspamd_regexp *re, struct worker_task *task, const char * return 0; } debug_task ("checking header regexp: %s = /%s/", re->header, re->regexp_text); + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for header %s", re->regexp_text, re->header); + } headerlist = message_get_header (task->task_pool, task->message, re->header); if (headerlist == NULL) { task_cache_add (task, re, 0); @@ -634,8 +649,14 @@ process_regexp (struct rspamd_regexp *re, struct worker_task *task, const char * } cur = headerlist; while (cur) { - debug_task ("found header \"%s\" with value \"%s\"", re->header, (char *)cur->data); + debug_task ("found header \"%s\" with value \"%s\"", re->header, (const char *)cur->data); + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for header %s with value '%s'", re->regexp_text, re->header, (const char *)cur->data); + } if (cur->data && g_regex_match (re->regexp, cur->data, 0, NULL) == TRUE) { + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for header %s with value '%s' returned TRUE", re->regexp_text, re->header, (const char *)cur->data); + } task_cache_add (task, re, 1); return 1; } @@ -661,7 +682,13 @@ process_regexp (struct rspamd_regexp *re, struct worker_task *task, const char * else { regexp = re->regexp; } + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for mime part of length %d", re->regexp_text, (int)part->orig->len); + } if (g_regex_match_full (regexp, part->orig->data, part->orig->len, 0, 0, NULL, NULL) == TRUE) { + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for mime part returned true", re->regexp_text); + } task_cache_add (task, re, 1); return 1; } @@ -671,7 +698,13 @@ process_regexp (struct rspamd_regexp *re, struct worker_task *task, const char * return 0; case REGEXP_MESSAGE: debug_task ("checking message regexp: /%s/", re->regexp_text); + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for message of length %d", re->regexp_text, (int)task->msg->len); + } if (g_regex_match_full (re->raw_regexp, task->msg->begin, task->msg->len, 0, 0, NULL, NULL) == TRUE) { + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for message of length %d returned TRUE", re->regexp_text, (int)task->msg->len); + } task_cache_add (task, re, 1); return 1; } @@ -745,7 +778,13 @@ process_regexp (struct rspamd_regexp *re, struct worker_task *task, const char * t = *c; *c = '\0'; debug_task ("found raw header \"%s\" with value \"%s\"", re->header, headerv); + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for header %s with value '%s'", re->regexp_text, re->header, headerv); + } if (g_regex_match (re->raw_regexp, headerv, 0, NULL) == TRUE) { + if (G_UNLIKELY (re->is_test)) { + msg_info ("process test regexp /%s/ for header %s with value '%s' returned TRUE", re->regexp_text, re->header, headerv); + } *c = t; task_cache_add (task, re, 1); return 1; |