Imported Upstream version 1.38
[platform/upstream/connman.git] / src / dnsproxy.c
index 40b4f15..a7bf87a 100644 (file)
@@ -79,6 +79,11 @@ struct domain_hdr {
 #error "Unknown byte order"
 #endif
 
+struct qtype_qclass {
+       uint16_t qtype;
+       uint16_t qclass;
+} __attribute__ ((packed));
+
 struct partial_reply {
        uint16_t len;
        uint16_t received;
@@ -448,7 +453,7 @@ static void send_cached_response(int sk, unsigned char *buf, int len,
        hdr->nscount = 0;
        hdr->arcount = 0;
 
-       /* if this is a negative reply, we are authorative */
+       /* if this is a negative reply, we are authoritative */
        if (answers == 0)
                hdr->aa = 1;
        else
@@ -470,7 +475,7 @@ static void send_cached_response(int sk, unsigned char *buf, int len,
                        err, len, dns_len);
 }
 
-static void send_response(int sk, unsigned char *buf, int len,
+static void send_response(int sk, unsigned char *buf, size_t len,
                                const struct sockaddr *to, socklen_t tolen,
                                int protocol)
 {
@@ -482,21 +487,26 @@ static void send_response(int sk, unsigned char *buf, int len,
        if (offset < 0)
                return;
 
-       if (len < 12)
+       if (len < sizeof(*hdr) + offset)
                return;
 
        hdr = (void *) (buf + offset);
+       if (offset) {
+               buf[0] = 0;
+               buf[1] = sizeof(*hdr);
+       }
 
        debug("id 0x%04x qr %d opcode %d", hdr->id, hdr->qr, hdr->opcode);
 
        hdr->qr = 1;
        hdr->rcode = ns_r_servfail;
 
+       hdr->qdcount = 0;
        hdr->ancount = 0;
        hdr->nscount = 0;
        hdr->arcount = 0;
 
-       err = sendto(sk, buf, len, MSG_NOSIGNAL, to, tolen);
+       err = sendto(sk, buf, sizeof(*hdr) + offset, MSG_NOSIGNAL, to, tolen);
        if (err < 0) {
                connman_error("Failed to send DNS response to %d: %s",
                                sk, strerror(errno));
@@ -2221,7 +2231,7 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition,
                                                        gpointer user_data)
 {
        unsigned char buf[4096];
-       int sk, err, len;
+       int sk, len;
        struct server_data *data = user_data;
 
        if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
@@ -2233,12 +2243,9 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition,
        sk = g_io_channel_unix_get_fd(channel);
 
        len = recv(sk, buf, sizeof(buf), 0);
-       if (len < 12)
-               return TRUE;
 
-       err = forward_dns_reply(buf, len, IPPROTO_UDP, data);
-       if (err < 0)
-               return TRUE;
+       if (len >= 12)
+               forward_dns_reply(buf, len, IPPROTO_UDP, data);
 
        return TRUE;
 }
@@ -2905,34 +2912,77 @@ static void dnsproxy_default_changed(struct connman_service *service)
        cache_refresh();
 }
 
-static struct connman_notifier dnsproxy_notifier = {
+static void dnsproxy_service_state_changed(struct connman_service *service,
+                       enum connman_service_state state)
+{
+       GSList *list;
+       int index;
+
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_IDLE:
+               break;
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+       case CONNMAN_SERVICE_STATE_ONLINE:
+       case CONNMAN_SERVICE_STATE_READY:
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+               return;
+       }
+
+       index = __connman_service_get_index(service);
+       list = server_list;
+
+       while (list) {
+               struct server_data *data = list->data;
+
+               /* Get next before the list is changed by destroy_server() */
+               list = list->next;
+
+               if (data->index == index) {
+                       DBG("removing server data of index %d", index);
+                       destroy_server(data);
+               }
+       }
+}
+
+static const struct connman_notifier dnsproxy_notifier = {
        .name                   = "dnsproxy",
        .default_changed        = dnsproxy_default_changed,
        .offline_mode           = dnsproxy_offline_mode,
+       .service_state_changed  = dnsproxy_service_state_changed,
 };
 
-static unsigned char opt_edns0_type[2] = { 0x00, 0x29 };
+static const unsigned char opt_edns0_type[2] = { 0x00, 0x29 };
 
-static int parse_request(unsigned char *buf, int len,
+static int parse_request(unsigned char *buf, size_t len,
                                        char *name, unsigned int size)
 {
        struct domain_hdr *hdr = (void *) buf;
        uint16_t qdcount = ntohs(hdr->qdcount);
+       uint16_t ancount = ntohs(hdr->ancount);
+       uint16_t nscount = ntohs(hdr->nscount);
        uint16_t arcount = ntohs(hdr->arcount);
        unsigned char *ptr;
-       char *last_label = NULL;
        unsigned int remain, used = 0;
 
-       if (len < 12)
+       if (len < sizeof(*hdr) + sizeof(struct qtype_qclass) ||
+                       hdr->qr || qdcount != 1 || ancount || nscount) {
+               DBG("Dropped DNS request qr %d with len %zd qdcount %d "
+                       "ancount %d nscount %d", hdr->qr, len, qdcount, ancount,
+                       nscount);
+
+               return -EINVAL;
+       }
+
+       if (!name || !size)
                return -EINVAL;
 
        debug("id 0x%04x qr %d opcode %d qdcount %d arcount %d",
                                        hdr->id, hdr->qr, hdr->opcode,
                                                        qdcount, arcount);
 
-       if (hdr->qr != 0 || qdcount != 1)
-               return -EINVAL;
-
        name[0] = '\0';
 
        ptr = buf + sizeof(struct domain_hdr);
@@ -2942,7 +2992,23 @@ static int parse_request(unsigned char *buf, int len,
                uint8_t label_len = *ptr;
 
                if (label_len == 0x00) {
-                       last_label = (char *) (ptr + 1);
+                       uint8_t class;
+                       struct qtype_qclass *q =
+                               (struct qtype_qclass *)(ptr + 1);
+
+                       if (remain < sizeof(*q)) {
+                               DBG("Dropped malformed DNS query");
+                               return -EINVAL;
+                       }
+
+                       class = ntohs(q->qclass);
+                       if (class != 1 && class != 255) {
+                               DBG("Dropped non-IN DNS class %d", class);
+                               return -EINVAL;
+                       }
+
+                       ptr += sizeof(*q) + 1;
+                       remain -= (sizeof(*q) + 1);
                        break;
                }
 
@@ -2958,26 +3024,13 @@ static int parse_request(unsigned char *buf, int len,
                remain -= label_len + 1;
        }
 
-       if (last_label && arcount && remain >= 9 && last_label[4] == 0 &&
-                               !memcmp(last_label + 5, opt_edns0_type, 2)) {
-               uint16_t edns0_bufsize;
+       if (arcount && remain >= sizeof(struct domain_rr) + 1 && !ptr[0] &&
+               ptr[1] == opt_edns0_type[0] && ptr[2] == opt_edns0_type[1]) {
+               struct domain_rr *edns0 = (struct domain_rr *)(ptr + 1);
 
-               edns0_bufsize = last_label[7] << 8 | last_label[8];
-
-               debug("EDNS0 buffer size %u", edns0_bufsize);
-
-               /* This is an evil hack until full TCP support has been
-                * implemented.
-                *
-                * Somtimes the EDNS0 request gets send with a too-small
-                * buffer size. Since glibc doesn't seem to crash when it
-                * gets a response biffer then it requested, just bump
-                * the buffer size up to 4KiB.
-                */
-               if (edns0_bufsize < 0x1000) {
-                       last_label[7] = 0x10;
-                       last_label[8] = 0x00;
-               }
+               DBG("EDNS0 buffer size %u", ntohs(edns0->class));
+       } else if (!arcount && remain) {
+               DBG("DNS request with %d garbage bytes", remain);
        }
 
        debug("query %s", name);
@@ -3927,6 +3980,11 @@ destroy:
        return err;
 }
 
+int __connman_dnsproxy_set_mdns(int index, bool enabled)
+{
+       return -ENOTSUP;
+}
+
 void __connman_dnsproxy_cleanup(void)
 {
        DBG("");
@@ -3948,4 +4006,9 @@ void __connman_dnsproxy_cleanup(void)
        g_hash_table_destroy(listener_table);
 
        g_hash_table_destroy(partial_tcp_req_table);
+
+       if (ipv4_resolve)
+               g_resolv_unref(ipv4_resolve);
+       if (ipv6_resolve)
+               g_resolv_unref(ipv6_resolve);
 }