]> source.dussan.org Git - rspamd.git/commitdiff
* Make SURBL module to use rspamd dns module
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Fri, 9 Jul 2010 14:19:04 +0000 (18:19 +0400)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Fri, 9 Jul 2010 14:19:04 +0000 (18:19 +0400)
* Several fixes to DNS logic

src/dns.c
src/dns.h
src/plugins/surbl.c
src/spf.c
test/rspamd_dns_test.c

index dcbde4afeac00cb9221f6957912a18dd35f8e3ee..e9f057b5ef11b5c9b057e47d72a7314f93556c87 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
@@ -409,8 +409,8 @@ format_dns_name (struct rspamd_dns_request *req, const char *name, guint namelen
                }
        }
        /* Termination label */
-       *(++pos) = '\0';
-       req->pos += pos - (req->packet + req->pos);
+       *pos = '\0';
+       req->pos += pos - (req->packet + req->pos) + 1;
        if (table != NULL) {
                g_list_free (table);
        }
@@ -601,6 +601,7 @@ dns_request_reply_cmp (struct rspamd_dns_request *req, guint8 *in, int len)
                }
                /* This may be compressed, so we need to decompress it */
                if (len1 & DNS_COMPRESSION_BITS) {
+                       memcpy (&len1, p, sizeof (guint16));
                        l1 = decompress_label (in, &len1);
                        decompressed ++;
                        l1 ++;
@@ -611,6 +612,7 @@ dns_request_reply_cmp (struct rspamd_dns_request *req, guint8 *in, int len)
                        p += len1;
                }
                if (len2 & DNS_COMPRESSION_BITS) {
+                       memcpy (&len2, p, sizeof (guint16));
                        l2 = decompress_label (req->packet, &len2);
                        decompressed ++;
                        l2 ++;
@@ -796,8 +798,8 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
                        p += datalen;
                }
                else {
-                       elt->txt.data = memory_pool_alloc (rep->request->pool, datalen + 1);
-                       memcpy (elt->txt.data, p, datalen);
+                       elt->txt.data = memory_pool_alloc (rep->request->pool, datalen);
+                       memcpy (elt->txt.data, p + 1, datalen - 1);
                        *(elt->txt.data + datalen) = '\0';
                }
                break;
@@ -806,8 +808,8 @@ dns_parse_rr (guint8 *in, union rspamd_reply_element *elt, guint8 **pos, struct
                        p += datalen;
                }
                else {
-                       elt->spf.data = memory_pool_alloc (rep->request->pool, datalen + 1);
-                       memcpy (elt->spf.data, p, datalen);
+                       elt->spf.data = memory_pool_alloc (rep->request->pool, datalen);
+                       memcpy (elt->spf.data, p + 1, datalen - 1);
                        *(elt->spf.data + datalen) = '\0';
                }
                break;
@@ -924,7 +926,7 @@ dns_timer_cb (int fd, short what, void *arg)
        /* Retransmit dns request */
        req->retransmits ++;
        if (req->retransmits >= req->resolver->max_retransmits) {
-               msg_err ("maximum number of retransmits expired");
+               msg_err ("maximum number of retransmits expired for resolving %s of type %s", req->requested_name, dns_strtype (req->type));
                event_del (&req->timer_event);
                rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
                rep->request = req;
@@ -985,7 +987,7 @@ dns_retransmit_handler (int fd, short what, void *arg)
                /* Retransmit dns request */
                req->retransmits ++;
                if (req->retransmits >= req->resolver->max_retransmits) {
-                       msg_err ("maximum number of retransmits expired");
+                       msg_err ("maximum number of retransmits expired for %s", req->requested_name);
                        event_del (&req->io_event);
                        rep = memory_pool_alloc0 (req->pool, sizeof (struct rspamd_dns_reply));
                        rep->request = req;
@@ -1258,3 +1260,18 @@ dns_strerror (enum dns_rcode rcode)
        }
        return dns_rcodes[rcode];
 }
+
+static char dns_types[6][16] = {
+               [DNS_REQUEST_A] = "A request",
+               [DNS_REQUEST_PTR] = "PTR request",
+               [DNS_REQUEST_MX] = "MX request",
+               [DNS_REQUEST_TXT] = "TXT request",
+               [DNS_REQUEST_SRV] = "SRV request",
+               [DNS_REQUEST_SPF] = "SPF request"
+};
+
+const char *
+dns_strtype (enum rspamd_request_type type)
+{
+       return dns_types[type];
+}
index 338daf0334f31d0dfc44ae7f45a88b522f16fbd2..c0a79a6b04792852bcbb2404bbd96b3d8025f360 100644 (file)
--- a/src/dns.h
+++ b/src/dns.h
@@ -226,5 +226,6 @@ gboolean make_dns_request (struct rspamd_dns_resolver *resolver,
                struct rspamd_async_session *session, memory_pool_t *pool, dns_callback_type cb, 
                gpointer ud, enum rspamd_request_type type, ...);
 const char *dns_strerror (enum dns_rcode rcode);
+const char *dns_strtype (enum rspamd_request_type type);
 
 #endif
index f50bd8976044e6565ac90429cf466cc7d31501db..09c382b7f1055d4ef914af7365fe505ad5c59130 100644 (file)
@@ -46,7 +46,7 @@
 #include "../message.h"
 #include "../view.h"
 #include "../map.h"
-#include "../evdns/evdns.h"
+#include "../dns.h"
 
 #include "surbl.h"
 
@@ -54,7 +54,7 @@ static struct surbl_ctx        *surbl_module_ctx = NULL;
 
 static int                      surbl_filter (struct worker_task *task);
 static void                     surbl_test_url (struct worker_task *task, void *user_data);
-static void                     dns_callback (int result, char type, int count, int ttl, void *addresses, void *data);
+static void                     dns_callback (struct rspamd_dns_reply *reply, gpointer arg);
 static void                     process_dns_results (struct worker_task *task, struct suffix_item *suffix, char *url, uint32_t addr);
 static int                      urls_command_handler (struct worker_task *task);
 
@@ -449,9 +449,8 @@ make_surbl_requests (struct uri *url, struct worker_task *task, GTree * tree, st
                                param->suffix = suffix;
                                param->host_resolve = memory_pool_strdup (task->task_pool, surbl_req);
                                debug_task ("send surbl dns request %s", surbl_req);
-                               if (evdns_resolve_ipv4 (surbl_req, DNS_QUERY_NO_SEARCH, dns_callback, (void *)param) == 0) {
+                               if (make_dns_request (task->resolver, task->s, task->task_pool, dns_callback, (void *)param, DNS_REQUEST_A, surbl_req)) {
                                        param->task->save.saved++;
-                                       register_async_event (task->s, (event_finalizer_t) dns_callback, NULL, TRUE);
                                }
                        }
                        else {
@@ -508,16 +507,18 @@ process_dns_results (struct worker_task *task, struct suffix_item *suffix, char
 }
 
 static void
-dns_callback (int result, char type, int count, int ttl, void *addresses, void *data)
+dns_callback (struct rspamd_dns_reply *reply, gpointer arg)
 {
-       struct dns_param               *param = (struct dns_param *)data;
+       struct dns_param               *param = (struct dns_param *)arg;
        struct worker_task             *task = param->task;
+       union rspamd_reply_element     *elt;
 
        debug_task ("in surbl request callback");
        /* If we have result from DNS server, this url exists in SURBL, so increase score */
-       if (result == DNS_ERR_NONE && type == DNS_IPv4_A) {
+       if (reply->code == DNS_RC_NOERROR && reply->elements) {
                msg_info ("<%s> domain [%s] is in surbl %s", param->task->message_id, param->host_resolve, param->suffix->suffix);
-               process_dns_results (param->task, param->suffix, param->host_resolve, (uint32_t) (((in_addr_t *) addresses)[0]));
+               elt = reply->elements->data;
+               process_dns_results (param->task, param->suffix, param->host_resolve, (uint32_t)elt->a.addr[0].s_addr);
        }
        else {
                debug_task ("<%s> domain [%s] is not in surbl %s", param->task->message_id, param->host_resolve, param->suffix->suffix);
@@ -529,8 +530,6 @@ dns_callback (int result, char type, int count, int ttl, void *addresses, void *
                param->task->save.saved = 1;
                process_filters (param->task);
        }
-       remove_forced_event (param->task->s, (event_finalizer_t) dns_callback);
-
 }
 
 static void
index 0cc1bf53e187b0dd5b616823ce5e66880b3515ec..3f735457ac11ba60784499f3c0e38bf169af95f0 100644 (file)
--- a/src/spf.c
+++ b/src/spf.c
@@ -23,7 +23,7 @@
  */
 
 #include "config.h"
-#include "evdns/evdns.h"
+#include "dns.h"
 #include "spf.h"
 #include "main.h"
 #include "message.h"
@@ -228,101 +228,103 @@ parse_spf_hostmask (struct worker_task *task, const char *begin, struct spf_addr
 }
 
 static void
-spf_record_dns_callback (int result, char type, int count, int ttl, void *addresses, void *data)
+spf_record_dns_callback (struct rspamd_dns_reply *reply, gpointer arg)
 {
-       struct spf_dns_cb *cb = data;
+       struct spf_dns_cb *cb = arg;
        char *begin;
-       struct evdns_mx *mx;
-       GList *tmp = NULL, *elt, *last;
+       union rspamd_reply_element *elt_data;
+       GList *tmp = NULL, *tmp1, *elt, *last;
+       struct worker_task *task;
 
-       if (result == DNS_ERR_NONE) {
-               if (addresses != NULL) {
+       task = cb->rec->task;
+
+       if (reply->code == DNS_RC_NOERROR) {
+               if (reply->elements != NULL) {
                        /* Add all logic for all DNS states here */
-                       switch (cb->cur_action) {
+                       elt = reply->elements;
+                       while (elt) {
+                               elt_data = elt->data;
+                               switch (cb->cur_action) {
                                case SPF_RESOLVE_MX:
-                                       if (type == DNS_MX) {
-                                               mx = (struct evdns_mx *)addresses;
+                                       if (reply->type == DNS_REQUEST_MX) {
                                                /* Now resolve A record for this MX */
-                                               if (evdns_resolve_ipv4 (mx->host, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
-                                                       return;
+                                               if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, DNS_REQUEST_A, elt_data->mx.name)) {
+                                                       task->save.saved++;
                                                }
                                        }
-                                       else if (type == DNS_IPv4_A) {
+                                       else if (reply->type == DNS_REQUEST_A) {
                                                /* XXX: process only one record */
-                                               cb->addr->addr = ntohl (*((uint32_t *)addresses));
+                                               cb->addr->addr = ntohl (elt_data->a.addr[0].s_addr);
                                        }
                                        break;
                                case SPF_RESOLVE_A:
-                                       if (type == DNS_IPv4_A) {
+                                       if (reply->type == DNS_REQUEST_A) {
                                                /* XXX: process only one record */
-                                               cb->addr->addr = ntohl (*((uint32_t *)addresses));
+                                               cb->addr->addr = ntohl (elt_data->a.addr[0].s_addr);
                                        }
                                        break;
                                case SPF_RESOLVE_PTR:
                                        break;
                                case SPF_RESOLVE_REDIRECT:
-                                       if (type == DNS_TXT) {
-                                               if (addresses != NULL) {
-                                                       begin = *(char **)addresses;
+                                       if (reply->type == DNS_REQUEST_TXT) {
+                                               begin = elt_data->txt.data;
 
-                                                       if (!cb->in_include && cb->rec->addrs) {
-                                                               g_list_free (cb->rec->addrs);
-                                                               cb->rec->addrs = NULL;
-                                                       }
-                                                       start_spf_parse (cb->rec, begin);
+                                               if (!cb->in_include && cb->rec->addrs) {
+                                                       g_list_free (cb->rec->addrs);
+                                                       cb->rec->addrs = NULL;
                                                }
+                                               start_spf_parse (cb->rec, begin);
+
                                        }
                                        break;
                                case SPF_RESOLVE_INCLUDE:
-                                       if (type == DNS_TXT) {
-                                               if (addresses != NULL) {
-                                                       begin = *(char **)addresses;
-                                                       if (cb->rec->addrs) {
-                                                               tmp = cb->rec->addrs;
-                                                               cb->rec->addrs = NULL;
-                                                       }
-                                                       cb->rec->in_include = TRUE;
-                                                       start_spf_parse (cb->rec, begin);
-                                                       cb->rec->in_include = FALSE;
-
-                                                       if (tmp) {
-                                                               elt = g_list_find (tmp, cb->addr);
-                                                               if (elt) {
-                                                                       /* Insert new list in place of include element */
-                                                                       last = g_list_last (cb->rec->addrs);
+                                       if (reply->type == DNS_REQUEST_TXT) {
+                                               begin = elt_data->txt.data;
+                                               if (cb->rec->addrs) {
+                                                       tmp = cb->rec->addrs;
+                                                       cb->rec->addrs = NULL;
+                                               }
+                                               cb->rec->in_include = TRUE;
+                                               start_spf_parse (cb->rec, begin);
+                                               cb->rec->in_include = FALSE;
+
+                                               if (tmp) {
+                                                       tmp1 = g_list_find (tmp, cb->addr);
+                                                       if (tmp1) {
+                                                               /* Insert new list in place of include element */
+                                                               last = g_list_last (cb->rec->addrs);
+
+                                                               if (tmp1->prev == NULL && tmp1->next == NULL) {
+                                                                       g_list_free1 (tmp1);
+                                                               }
+                                                               else {
 
-                                                                       if (elt->prev == NULL && elt->next == NULL) {
-                                                                               g_list_free1 (elt);
+                                                                       if (tmp1->prev) {
+                                                                               tmp1->prev->next = cb->rec->addrs;
                                                                        }
                                                                        else {
-
-                                                                               if (elt->prev) {
-                                                                                       elt->prev->next = cb->rec->addrs;
-                                                                               }
-                                                                               else {
-                                                                                       /* Elt is the first element, so we need to shift temporary list */
-                                                                                       tmp = elt->next;
-                                                                                       tmp->prev = NULL;
-                                                                               }
-                                                                               if (elt->next) {
-                                                                                       elt->next->prev = last;
-                                                                                       if (last != NULL) {
-                                                                                               last->next = elt->next;
-                                                                                       }
-                                                                               }
-                                                                               
-                                                                               if (cb->rec->addrs != NULL) {
-                                                                                       cb->rec->addrs->prev = elt->prev;
+                                                                               /* Elt is the first element, so we need to shift temporary list */
+                                                                               tmp = tmp1->next;
+                                                                               tmp->prev = NULL;
+                                                                       }
+                                                                       if (tmp1->next) {
+                                                                               tmp1->next->prev = last;
+                                                                               if (last != NULL) {
+                                                                                       last->next = tmp1->next;
                                                                                }
+                                                                       }
 
-                                                                               /* Shift temporary list */
-                                                                               while (tmp->prev) {
-                                                                                       tmp = tmp->prev;
-                                                                               }
+                                                                       if (cb->rec->addrs != NULL) {
+                                                                               cb->rec->addrs->prev = tmp1->prev;
+                                                                       }
 
-                                                                               cb->rec->addrs = tmp;
-                                                                               g_list_free1 (elt);
+                                                                       /* Shift temporary list */
+                                                                       while (tmp->prev) {
+                                                                               tmp = tmp->prev;
                                                                        }
+
+                                                                       cb->rec->addrs = tmp;
+                                                                       g_list_free1 (tmp1);
                                                                }
                                                        }
                                                }
@@ -331,29 +333,31 @@ spf_record_dns_callback (int result, char type, int count, int ttl, void *addres
                                case SPF_RESOLVE_EXP:
                                        break;
                                case SPF_RESOLVE_EXISTS:
-                                       if (type == DNS_IPv4_A) {
+                                       if (reply->type == DNS_REQUEST_A) {
                                                /* If specified address resolves, we can accept connection from every IP */
                                                cb->addr->addr = ntohl (INADDR_ANY);
                                                cb->addr->mask = 0;
                                        }
                                        break;
+                               }
+                               elt = g_list_next (elt);
                        }
                }
        }
-       else if (result == DNS_ERR_NOTEXIST) {
+       else if (reply->code == DNS_RC_NXDOMAIN) {
                switch (cb->cur_action) {
                                case SPF_RESOLVE_MX:
-                                       if (type == DNS_MX) {
+                                       if (reply->type == DNS_REQUEST_MX) {
                                                msg_info ("cannot find MX record for %s", cb->rec->cur_domain);
                                                cb->addr->addr = ntohl (INADDR_NONE);
                                        }
-                                       else if (type == DNS_IPv4_A) {
+                                       else if (reply->type != DNS_REQUEST_MX) {
                                                msg_info ("cannot resolve MX record for %s", cb->rec->cur_domain);
                                                cb->addr->addr = ntohl (INADDR_NONE);
                                        }
                                        break;
                                case SPF_RESOLVE_A:
-                                       if (type == DNS_IPv4_A) {
+                                       if (reply->type == DNS_REQUEST_A) {
                                                /* XXX: process only one record */
                                                cb->addr->addr = ntohl (INADDR_NONE);
                                        }
@@ -382,8 +386,6 @@ spf_record_dns_callback (int result, char type, int count, int ttl, void *addres
                        cb->rec->addrs = NULL;
                }
        }
-       remove_forced_event (cb->rec->task->s, (event_finalizer_t) spf_record_dns_callback);
-
 }
 
 static gboolean
@@ -411,10 +413,8 @@ parse_spf_a (struct worker_task *task, const char *begin, struct spf_record *rec
        cb->addr = addr;
        cb->cur_action = SPF_RESOLVE_A;
        cb->in_include = rec->in_include;
-
-       if (evdns_resolve_ipv4 (host, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+       if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, DNS_REQUEST_A, host)) {
                task->save.saved++;
-               register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
                
                return TRUE;
        }
@@ -459,10 +459,8 @@ parse_spf_mx (struct worker_task *task, const char *begin, struct spf_record *re
        cb->addr = addr;
        cb->cur_action = SPF_RESOLVE_MX;
        cb->in_include = rec->in_include;
-
-       if (evdns_resolve_mx (host, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+       if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, DNS_REQUEST_MX, host)) {
                task->save.saved++;
-               register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
                
                return TRUE;
        }
@@ -516,14 +514,13 @@ parse_spf_include (struct worker_task *task, const char *begin, struct spf_recor
        cb->cur_action = SPF_RESOLVE_INCLUDE;
        cb->in_include = rec->in_include;
        domain = memory_pool_strdup (task->task_pool, begin);
-
-       if (evdns_resolve_txt (domain, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+       if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, DNS_REQUEST_TXT, domain)) {
                task->save.saved++;
-               register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
                
                return TRUE;
        }
 
+
        return FALSE;
 }
 
@@ -556,10 +553,8 @@ parse_spf_redirect (struct worker_task *task, const char *begin, struct spf_reco
        cb->cur_action = SPF_RESOLVE_REDIRECT;
        cb->in_include = rec->in_include;
        domain = memory_pool_strdup (task->task_pool, begin);
-
-       if (evdns_resolve_txt (domain, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+       if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, DNS_REQUEST_TXT, domain)) {
                task->save.saved++;
-               register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
                
                return TRUE;
        }
@@ -589,9 +584,8 @@ parse_spf_exists (struct worker_task *task, const char *begin, struct spf_record
        cb->in_include = rec->in_include;
        host = memory_pool_strdup (task->task_pool, begin);
 
-       if (evdns_resolve_ipv4 (host, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+       if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, DNS_REQUEST_A, host)) {
                task->save.saved++;
-               register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
                
                return TRUE;
        }
@@ -1062,15 +1056,18 @@ start_spf_parse (struct spf_record *rec, char *begin)
 }
 
 static void
-spf_dns_callback (int result, char type, int count, int ttl, void *addresses, void *data)
+spf_dns_callback (struct rspamd_dns_reply *reply, gpointer arg)
 {
-       struct spf_record *rec = data;
-       char *begin;
-
-       if (result == DNS_ERR_NONE && type == DNS_TXT) {
-               if (addresses != NULL) {
-                       begin = *(char **)addresses;
-                       start_spf_parse (rec, begin);
+       struct spf_record *rec = arg;
+       union rspamd_reply_element *elt;
+       GList *cur;
+
+       if (reply->code == DNS_RC_NOERROR) {
+               cur = reply->elements;
+               while (cur) {
+                       elt = cur->data;
+                       start_spf_parse (rec, elt->txt.data);
+                       cur = g_list_next (cur);
                }
        }
 
@@ -1078,8 +1075,6 @@ spf_dns_callback (int result, char type, int count, int ttl, void *addresses, vo
        if (rec->task->save.saved == 0 && rec->callback) {
                rec->callback (rec, rec->task);
        }
-       remove_forced_event (rec->task->s, (event_finalizer_t) spf_dns_callback);
-
 }
 
 
@@ -1108,10 +1103,8 @@ resolve_spf (struct worker_task *task, spf_cb_t callback)
                }
                rec->sender_domain = rec->cur_domain;
 
-               if (evdns_resolve_txt (rec->cur_domain, DNS_QUERY_NO_SEARCH, spf_dns_callback, (void *)rec) == 0) {
+               if (make_dns_request (task->resolver, task->s, task->task_pool, spf_dns_callback, (void *)rec, DNS_REQUEST_TXT, rec->cur_domain)) {
                        task->save.saved++;
-                       register_async_event (task->s, (event_finalizer_t) spf_dns_callback, NULL, TRUE);
-
                        return TRUE;
                }
        }
@@ -1138,10 +1131,8 @@ resolve_spf (struct worker_task *task, spf_cb_t callback)
                                *domain = '\0';
                        }
                        rec->sender_domain = rec->cur_domain;
-                       if (evdns_resolve_txt (rec->cur_domain, DNS_QUERY_NO_SEARCH, spf_dns_callback, (void *)rec) == 0) {
+                       if (make_dns_request (task->resolver, task->s, task->task_pool, spf_dns_callback, (void *)rec, DNS_REQUEST_TXT, rec->cur_domain)) {
                                task->save.saved++;
-                               register_async_event (task->s, (event_finalizer_t) spf_dns_callback, NULL, TRUE);
-       
                                return TRUE;
                        }
                }
index a6ffcc79a53a553157ec928bb9e5ffd48acc1d73..13d27631360c75f020553ad8b60a6b188f9b3df2 100644 (file)
@@ -33,7 +33,7 @@ test_dns_cb (struct rspamd_dns_reply *reply, gpointer arg)
                                msg_debug ("got spf %s", elt->spf.data);
                                break;
                        case DNS_REQUEST_SRV:
-                               msg_debug ("got srv pri:%d, weight:%d, port: %d, target: %s", elt->srv.weight,
+                               msg_debug ("got srv pri: %d, weight: %d, port: %d, target: %s", elt->srv.weight,
                                                elt->srv.priority, elt->srv.port, elt->srv.target);
                                break;
                        case DNS_REQUEST_MX: