libcli/dns: Fix TCP fallback
authorVolker Lendecke <vl@samba.org>
Thu, 20 Jan 2022 11:23:43 +0000 (12:23 +0100)
committerJeremy Allison <jra@samba.org>
Thu, 20 Jan 2022 18:01:41 +0000 (18:01 +0000)
A customer has come across a DNS server that really just cuts a SRV
reply if it's too long. This makes the packet invalid according to
ndr_pull and according to wireshark. DNS_FLAG_TRUNCATION is however
set. As this seems to be legal according to the DNS RFCs, we need to
hand-parse the first two uint16's and look whether DNS_FLAG_TRUNCATION
is set.

Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
Autobuild-User(master): Jeremy Allison <jra@samba.org>
Autobuild-Date(master): Thu Jan 20 18:01:41 UTC 2022 on sn-devel-184

libcli/dns/dns.c

index 483697145ad8dec66ae7529d91a01b0b769193b1..943b4d5b33e1b4fc0582b551c5c2a9ec7754d88e 100644 (file)
@@ -475,6 +475,7 @@ static void dns_cli_request_udp_done(struct tevent_req *subreq)
                req, struct dns_cli_request_state);
        DATA_BLOB reply;
        enum ndr_err_code ndr_err;
+       uint16_t reply_id, operation;
        int ret;
 
        ret = dns_udp_request_recv(subreq, state, &reply.data, &reply.length);
@@ -483,46 +484,51 @@ static void dns_cli_request_udp_done(struct tevent_req *subreq)
                return;
        }
 
-       state->reply = talloc(state, struct dns_name_packet);
-       if (tevent_req_nomem(state->reply, req)) {
-               return;
-       }
-
-       ndr_err = ndr_pull_struct_blob(
-               &reply, state->reply, state->reply,
-               (ndr_pull_flags_fn_t)ndr_pull_dns_name_packet);
-       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-               tevent_req_error(req, ndr_map_error2errno(ndr_err));
+       if (reply.length < 4) {
+               DBG_DEBUG("Short DNS packet: length=%zu\n", reply.length);
+               tevent_req_error(req, EINVAL);
                return;
        }
-       TALLOC_FREE(reply.data);
 
-       if (state->reply->id != state->req_id) {
+       reply_id = PULL_BE_U16(reply.data, 0);
+       if (reply_id != state->req_id) {
                DBG_DEBUG("Got id %"PRIu16", expected %"PRIu16"\n",
                          state->reply->id, state->req_id);
                tevent_req_error(req, ENOMSG);
                return;
        }
 
-       if ((state->reply->operation & DNS_FLAG_TRUNCATION) == 0) {
-               DBG_DEBUG("Got op=%x %"PRIu16"/%"PRIu16"/%"PRIu16"/%"PRIu16
-                         " recs\n", (int)state->reply->operation,
-                         state->reply->qdcount, state->reply->ancount,
-                         state->reply->nscount, state->reply->nscount);
-               tevent_req_done(req);
+       operation = PULL_BE_U16(reply.data, 2);
+       if ((operation & DNS_FLAG_TRUNCATION) != 0) {
+               DBG_DEBUG("Reply was truncated, retrying TCP\n");
+               subreq = dns_tcp_request_send(
+                       state,
+                       state->ev,
+                       state->nameserver,
+                       state->query.data,
+                       state->query.length);
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
+               }
+               tevent_req_set_callback(subreq, dns_cli_request_tcp_done, req);
                return;
        }
 
-       DBG_DEBUG("Reply was truncated, retrying TCP\n");
-
-       TALLOC_FREE(state->reply);
+       state->reply = talloc(state, struct dns_name_packet);
+       if (tevent_req_nomem(state->reply, req)) {
+               return;
+       }
 
-       subreq = dns_tcp_request_send(state, state->ev, state->nameserver,
-                                     state->query.data, state->query.length);
-       if (tevent_req_nomem(subreq, req)) {
+       ndr_err = ndr_pull_struct_blob(
+               &reply, state->reply, state->reply,
+               (ndr_pull_flags_fn_t)ndr_pull_dns_name_packet);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               tevent_req_error(req, ndr_map_error2errno(ndr_err));
                return;
        }
-       tevent_req_set_callback(subreq, dns_cli_request_tcp_done, req);
+       TALLOC_FREE(reply.data);
+
+       tevent_req_done(req);
 }
 
 static void dns_cli_request_tcp_done(struct tevent_req *subreq)