1/* Convert a DNS packet to a human-readable representation.
2 Copyright (C) 2016-2020 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
28struct in_buffer
29{
30 const unsigned char *data;
31 size_t size;
32};
33
34static inline bool
35extract_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
45static inline bool
46extract_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
56static inline bool
57extract_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
71static inline bool
72extract_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
82struct dname
83{
84 char name[MAXDNAME + 1];
85};
86
87static bool
88extract_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
104char *
105support_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