commit 9029b54: [Fix] Use strict IDNA for utf8 DNS names + add sanity checks for DNS names
Vsevolod Stakhov
vsevolod at highsecure.ru
Wed Sep 23 11:21:06 UTC 2020
Author: Vsevolod Stakhov
Date: 2020-09-23 11:58:33 +0100
URL: https://github.com/rspamd/rspamd/commit/9029b54e9f2a3173391e630c8cff5758f2d56321
[Fix] Use strict IDNA for utf8 DNS names + add sanity checks for DNS names
---
src/libserver/dns.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++-
src/libserver/dns.h | 15 +++++++
2 files changed, 134 insertions(+), 2 deletions(-)
diff --git a/src/libserver/dns.c b/src/libserver/dns.c
index 8fe0c6f92..cbbed4aa8 100644
--- a/src/libserver/dns.c
+++ b/src/libserver/dns.c
@@ -15,7 +15,7 @@
*/
-#include <contrib/librdns/rdns.h>
+#include "contrib/librdns/rdns.h"
#include "config.h"
#include "dns.h"
#include "rspamd.h"
@@ -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,
@@ -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)
{
@@ -189,6 +207,8 @@ 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);
@@ -196,13 +216,42 @@ rspamd_dns_resolver_request (struct rspamd_dns_resolver *resolver,
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));
@@ -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;
}
@@ -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;
@@ -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) {
@@ -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);
}
}
@@ -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;
+}
\ No newline at end of file
diff --git a/src/libserver/dns.h b/src/libserver/dns.h
index 367053ef4..50db8c891 100644
--- a/src/libserver/dns.h
+++ b/src/libserver/dns.h
@@ -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;
@@ -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
More information about the Commits
mailing list