Skip to content

Commit

Permalink
[Fix] Use strict IDNA for utf8 DNS names + add sanity checks for DNS …
Browse files Browse the repository at this point in the history
…names
  • Loading branch information
vstakhov committed Sep 23, 2020
1 parent 876a816 commit 9029b54
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 2 deletions.
121 changes: 119 additions & 2 deletions src/libserver/dns.c
Expand Up @@ -15,7 +15,7 @@
*/


#include <contrib/librdns/rdns.h>
#include "contrib/librdns/rdns.h"
#include "config.h"
#include "dns.h"
#include "rspamd.h"
Expand All @@ -25,6 +25,8 @@
#include "contrib/librdns/rdns_ev.h"
#include "unix-std.h"

#include <unicode/uidna.h>

static const gchar *M = "rspamd dns";

static struct rdns_upstream_elt* rspamd_dns_select_upstream (const char *name,
Expand Down Expand Up @@ -66,6 +68,22 @@ struct rspamd_dns_fail_cache_entry {
enum rdns_request_type type;
};

static const gint8 ascii_dns_table[128]={
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* HYPHEN-MINUS..FULL STOP */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1,
/* 0..9 digits */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1,
/* LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z */
-1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* _ */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1,
/* LATIN SMALL LETTER A..LATIN SMALL LETTER Z */
-1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1
};

static guint
rspamd_dns_fail_hash (gconstpointer ptr)
{
Expand Down Expand Up @@ -189,20 +207,51 @@ rspamd_dns_resolver_request (struct rspamd_dns_resolver *resolver,
{
struct rdns_request *req;
struct rspamd_dns_request_ud *reqdata = NULL;
guint nlen = strlen (name);
gchar *real_name = NULL;

g_assert (resolver != NULL);

if (resolver->r == NULL) {
return NULL;
}

if (nlen == 0 || nlen > DNS_D_MAXNAME) {
return NULL;
}

if (session && rspamd_session_blocked (session)) {
return NULL;
}

if (rspamd_str_has_8bit (name, nlen)) {
/* Convert to idna using libicu as it follows all the standards */
real_name = rspamd_dns_resolver_idna_convert_utf8 (resolver, pool,
name, nlen, &nlen);

if (real_name == NULL) {
return NULL;
}

name = real_name;
}

/* Name is now in ASCII only */
for (gsize i = 0; i < nlen; i ++) {
if (ascii_dns_table[((unsigned int)name[i]) & 0x7F] == -1) {
/* Invalid DNS name requested */

if (!pool) {
g_free (real_name);
}

return NULL;
}
}

if (pool != NULL) {
reqdata =
rspamd_mempool_alloc0 (pool, sizeof (struct rspamd_dns_request_ud));
rspamd_mempool_alloc0 (pool, sizeof (struct rspamd_dns_request_ud));
}
else {
reqdata = g_malloc0 (sizeof (struct rspamd_dns_request_ud));
Expand Down Expand Up @@ -230,11 +279,16 @@ rspamd_dns_resolver_request (struct rspamd_dns_resolver *resolver,
if (req == NULL) {
if (pool == NULL) {
g_free (reqdata);
g_free (real_name);
}

return NULL;
}

if (real_name && pool == NULL) {
g_free (real_name);
}

return reqdata;
}

Expand Down Expand Up @@ -820,6 +874,7 @@ rspamd_dns_resolver_init (rspamd_logger_t *logger,

dns_resolver = g_malloc0 (sizeof (struct rspamd_dns_resolver));
dns_resolver->event_loop = ev_base;

if (cfg != NULL) {
dns_resolver->request_timeout = cfg->dns_timeout;
dns_resolver->max_retransmits = cfg->dns_retransmits;
Expand All @@ -830,6 +885,11 @@ rspamd_dns_resolver_init (rspamd_logger_t *logger,
}

dns_resolver->r = rdns_resolver_new ();

UErrorCode uc_err = U_ZERO_ERROR;

dns_resolver->uidna = uidna_openUTS46 (UIDNA_DEFAULT, &uc_err);
g_assert (!U_FAILURE (uc_err));
rdns_bind_libev (dns_resolver->r, dns_resolver->event_loop);

if (cfg != NULL) {
Expand Down Expand Up @@ -924,6 +984,8 @@ rspamd_dns_resolver_deinit (struct rspamd_dns_resolver *resolver)
rspamd_lru_hash_destroy (resolver->fails_cache);
}

uidna_close (resolver->uidna);

g_free (resolver);
}
}
Expand Down Expand Up @@ -999,3 +1061,58 @@ rspamd_dns_upstream_count (void *ups_data)

return rspamd_upstreams_alive (ups);
}

gchar*
rspamd_dns_resolver_idna_convert_utf8 (struct rspamd_dns_resolver *resolver,
rspamd_mempool_t *pool,
const char *name,
gint namelen,
guint *outlen)
{
if (resolver == NULL || resolver->uidna == NULL || name == NULL
|| namelen > DNS_D_MAXNAME) {
return NULL;
}

guint dest_len;
UErrorCode uc_err = U_ZERO_ERROR;
UIDNAInfo info = UIDNA_INFO_INITIALIZER;
/* Calculate length required */
dest_len = uidna_nameToASCII_UTF8 (resolver->uidna, name, namelen,
NULL, 0, &info, &uc_err);

if (uc_err == U_BUFFER_OVERFLOW_ERROR) {
gchar *dest;

if (pool) {
dest = rspamd_mempool_alloc (pool, dest_len + 1);
}
else {
dest = g_malloc (dest_len + 1);
}

uc_err = U_ZERO_ERROR;

dest_len = uidna_nameToASCII_UTF8 (resolver->uidna, name, namelen,
dest, dest_len + 1, &info, &uc_err);

if (U_FAILURE (uc_err)) {

if (!pool) {
g_free (dest);
}

return NULL;
}

dest[dest_len] = '\0';

if (outlen) {
*outlen = dest_len;
}

return dest;
}

return NULL;
}
15 changes: 15 additions & 0 deletions src/libserver/dns.h
Expand Up @@ -36,6 +36,7 @@ struct rspamd_dns_resolver {
struct rdns_resolver *r;
struct ev_loop *event_loop;
rspamd_lru_hash_t *fails_cache;
void *uidna;
ev_tstamp fails_cache_time;
struct upstream_list *ups;
struct rspamd_config *cfg;
Expand Down Expand Up @@ -87,6 +88,20 @@ gboolean rspamd_dns_resolver_request_task_forced (struct rspamd_task *task,
enum rdns_request_type type,
const char *name);

/**
* Converts a name into idna from UTF8
* @param resolver resolver (must be initialised)
* @param pool optional memory pool (can be NULL, then you need to g_free) the result
* @param name input name
* @param namelen length of input (-1 for zero terminated)
* @return encoded string
*/
gchar* rspamd_dns_resolver_idna_convert_utf8 (struct rspamd_dns_resolver *resolver,
rspamd_mempool_t *pool,
const char *name,
gint namelen,
guint *outlen);

#ifdef __cplusplus
}
#endif
Expand Down

0 comments on commit 9029b54

Please sign in to comment.