1 /* Convert a DNS packet to a human-readable representation.
2    Copyright (C) 2016-2021 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <support/format_nss.h>
20 
21 #include <arpa/inet.h>
22 #include <resolv.h>
23 #include <stdbool.h>
24 #include <support/check.h>
25 #include <support/support.h>
26 #include <support/xmemstream.h>
27 
28 struct in_buffer
29 {
30   const unsigned char *data;
31   size_t size;
32 };
33 
34 static inline bool
extract_8(struct in_buffer * in,unsigned char * value)35 extract_8 (struct in_buffer *in, unsigned char *value)
36 {
37   if (in->size == 0)
38     return false;
39   *value = in->data[0];
40   ++in->data;
41   --in->size;
42   return true;
43 }
44 
45 static inline bool
extract_16(struct in_buffer * in,unsigned short * value)46 extract_16 (struct in_buffer *in, unsigned short *value)
47 {
48   if (in->size < 2)
49     return false;
50   *value = (in->data[0] << 8) | in->data[1];
51   in->data += 2;
52   in->size -= 2;
53   return true;
54 }
55 
56 static inline bool
extract_32(struct in_buffer * in,unsigned * value)57 extract_32 (struct in_buffer *in, unsigned *value)
58 {
59   if (in->size < 4)
60     return false;
61   unsigned a = in->data[0];
62   unsigned b = in->data[1];
63   unsigned c = in->data[2];
64   unsigned d = in->data[3];
65   *value = (a << 24) | (b << 16) | (c << 8) | d;
66   in->data += 4;
67   in->size -= 4;
68   return true;
69 }
70 
71 static inline bool
extract_bytes(struct in_buffer * in,size_t length,struct in_buffer * value)72 extract_bytes (struct in_buffer *in, size_t length, struct in_buffer *value)
73 {
74   if (in->size < length)
75     return false;
76   *value = (struct in_buffer) {in->data, length};
77   in->data += length;
78   in->size -= length;
79   return true;
80 }
81 
82 struct dname
83 {
84   char name[MAXDNAME + 1];
85 };
86 
87 static bool
extract_name(struct in_buffer full,struct in_buffer * in,struct dname * value)88 extract_name (struct in_buffer full, struct in_buffer *in, struct dname *value)
89 {
90   const unsigned char *full_end = full.data + full.size;
91   /* Sanity checks; these indicate buffer misuse.  */
92   TEST_VERIFY_EXIT
93     (!(in->data < full.data || in->data > full_end
94        || in->size > (size_t) (full_end - in->data)));
95   int ret = dn_expand (full.data, full_end, in->data,
96                        value->name, sizeof (value->name));
97   if (ret < 0)
98     return false;
99   in->data += ret;
100   in->size -= ret;
101   return true;
102 }
103 
104 char *
support_format_dns_packet(const unsigned char * buffer,size_t length)105 support_format_dns_packet (const unsigned char *buffer, size_t length)
106 {
107   struct in_buffer full = { buffer, length };
108   struct in_buffer in = full;
109   struct xmemstream mem;
110   xopen_memstream (&mem);
111 
112   unsigned short txnid;
113   unsigned short flags;
114   unsigned short qdcount;
115   unsigned short ancount;
116   unsigned short nscount;
117   unsigned short adcount;
118   if (!(extract_16 (&in, &txnid)
119         && extract_16 (&in, &flags)
120         && extract_16 (&in, &qdcount)
121         && extract_16 (&in, &ancount)
122         && extract_16 (&in, &nscount)
123         && extract_16 (&in, &adcount)))
124     {
125       fprintf (mem.out, "error: could not parse DNS header\n");
126       goto out;
127     }
128   if (qdcount != 1)
129     {
130       fprintf (mem.out, "error: question count is %d, not 1\n", qdcount);
131       goto out;
132     }
133   struct dname qname;
134   if (!extract_name (full, &in, &qname))
135     {
136       fprintf (mem.out, "error: malformed QNAME\n");
137       goto out;
138     }
139   unsigned short qtype;
140   unsigned short qclass;
141   if (!(extract_16 (&in, &qtype)
142         && extract_16 (&in, &qclass)))
143     {
144       fprintf (mem.out, "error: malformed question\n");
145       goto out;
146     }
147   if (qtype != T_A && qtype != T_AAAA && qtype != T_PTR)
148     {
149       fprintf (mem.out, "error: unsupported QTYPE %d\n", qtype);
150       goto out;
151     }
152 
153   fprintf (mem.out, "name: %s\n", qname.name);
154 
155   for (int i = 0; i < ancount; ++i)
156     {
157       struct dname rname;
158       if (!extract_name (full, &in, &rname))
159         {
160           fprintf (mem.out, "error: malformed record name\n");
161           goto out;
162         }
163       unsigned short rtype;
164       unsigned short rclass;
165       unsigned ttl;
166       unsigned short rdlen;
167       struct in_buffer rdata;
168       if (!(extract_16 (&in, &rtype)
169             && extract_16 (&in, &rclass)
170             && extract_32 (&in, &ttl)
171             && extract_16 (&in, &rdlen)
172             && extract_bytes (&in, rdlen, &rdata)))
173         {
174           fprintf (mem.out, "error: malformed record header\n");
175           goto out;
176         }
177       /* Skip non-matching record types.  */
178       if ((rtype != qtype && rtype != T_CNAME) || rclass != qclass)
179         continue;
180       switch (rtype)
181         {
182         case T_A:
183           if (rdlen == 4)
184               fprintf (mem.out, "address: %d.%d.%d.%d\n",
185                        rdata.data[0],
186                        rdata.data[1],
187                        rdata.data[2],
188                        rdata.data[3]);
189           else
190             fprintf (mem.out, "error: A record of size %d: %s\n",
191                      rdlen, rname.name);
192           break;
193         case T_AAAA:
194           {
195             if (rdlen == 16)
196               {
197                 char buf[100];
198                 if (inet_ntop (AF_INET6, rdata.data, buf, sizeof (buf)) == NULL)
199                   fprintf (mem.out, "error: AAAA record decoding failed: %m\n");
200                 else
201                   fprintf (mem.out, "address: %s\n", buf);
202               }
203             else
204               fprintf (mem.out, "error: AAAA record of size %d: %s\n",
205                        rdlen, rname.name);
206           }
207           break;
208         case T_CNAME:
209         case T_PTR:
210           {
211             struct dname name;
212             if (extract_name (full, &rdata, &name))
213               fprintf (mem.out, "name: %s\n", name.name);
214             else
215               fprintf (mem.out, "error: malformed CNAME/PTR record\n");
216           }
217         }
218     }
219 
220  out:
221   xfclose_memstream (&mem);
222   return mem.buffer;
223 }
224