]> source.dussan.org Git - rspamd.git/commitdiff
[Fix] Use strict IDNA for utf8 DNS names + add sanity checks for DNS names
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Wed, 23 Sep 2020 10:58:33 +0000 (11:58 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Wed, 23 Sep 2020 10:58:33 +0000 (11:58 +0100)
src/libserver/dns.c
src/libserver/dns.h

index 8fe0c6f92991a4eaabd2a11aa768e070d531e9bc..cbbed4aa8aac64b9a9f3793937cfeb0fbf3ff607 100644 (file)
@@ -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
index 367053ef44e8af47902c2fd311eaf82aaf626193..50db8c89161f8cdb5ae885dc9ed1ba95db903361 100644 (file)
@@ -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