1 /* Convert a DNS packet to a human-readable representation.
2    Copyright (C) 2016-2022 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_16(struct in_buffer * in,unsigned short * value)35 extract_16 (struct in_buffer *in, unsigned short *value)
36 {
37   if (in->size < 2)
38     return false;
39   *value = (in->data[0] << 8) | in->data[1];
40   in->data += 2;
41   in->size -= 2;
42   return true;
43 }
44 
45 static inline bool
extract_32(struct in_buffer * in,unsigned * value)46 extract_32 (struct in_buffer *in, unsigned *value)
47 {
48   if (in->size < 4)
49     return false;
50   unsigned a = in->data[0];
51   unsigned b = in->data[1];
52   unsigned c = in->data[2];
53   unsigned d = in->data[3];
54   *value = (a << 24) | (b << 16) | (c << 8) | d;
55   in->data += 4;
56   in->size -= 4;
57   return true;
58 }
59 
60 static inline bool
extract_bytes(struct in_buffer * in,size_t length,struct in_buffer * value)61 extract_bytes (struct in_buffer *in, size_t length, struct in_buffer *value)
62 {
63   if (in->size < length)
64     return false;
65   *value = (struct in_buffer) {in->data, length};
66   in->data += length;
67   in->size -= length;
68   return true;
69 }
70 
71 struct dname
72 {
73   char name[MAXDNAME + 1];
74 };
75 
76 static bool
extract_name(struct in_buffer full,struct in_buffer * in,struct dname * value)77 extract_name (struct in_buffer full, struct in_buffer *in, struct dname *value)
78 {
79   const unsigned char *full_end = full.data + full.size;
80   /* Sanity checks; these indicate buffer misuse.  */
81   TEST_VERIFY_EXIT
82     (!(in->data < full.data || in->data > full_end
83        || in->size > (size_t) (full_end - in->data)));
84   int ret = dn_expand (full.data, full_end, in->data,
85                        value->name, sizeof (value->name));
86   if (ret < 0)
87     return false;
88   in->data += ret;
89   in->size -= ret;
90   return true;
91 }
92 
93 static void
extract_name_data(struct in_buffer full,struct in_buffer * rdata,const struct dname * owner,const char * typename,FILE * out)94 extract_name_data (struct in_buffer full, struct in_buffer *rdata,
95                    const struct dname *owner, const char *typename, FILE *out)
96 {
97   struct dname name;
98   if (extract_name (full, rdata, &name))
99     fprintf (out, "data: %s %s %s\n", owner->name, typename, name.name);
100   else
101     fprintf (out, "error: malformed CNAME/PTR record\n");
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           extract_name_data (full, &rdata, &rname, "CNAME", mem.out);
210           break;
211         case T_PTR:
212           extract_name_data (full, &rdata, &rname, "PTR", mem.out);
213           break;
214         }
215     }
216 
217  out:
218   xfclose_memstream (&mem);
219   return mem.buffer;
220 }
221