xref: /openbsd-src/usr.sbin/smtpd/unpack_dns.c (revision ce6743a3b05387e650bcbabf73c82d33d634a9dc)
1 /*	$OpenBSD: unpack_dns.c,v 1.3 2022/01/20 14:18:10 naddy Exp $	*/
2 
3 /*
4  * Copyright (c) 2011-2014 Eric Faurot <eric@faurot.net>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <string.h>
20 
21 #include "unpack_dns.h"
22 
23 static int unpack_data(struct unpack *, void *, size_t);
24 static int unpack_u16(struct unpack *, uint16_t *);
25 static int unpack_u32(struct unpack *, uint32_t *);
26 static int unpack_inaddr(struct unpack *, struct in_addr *);
27 static int unpack_in6addr(struct unpack *, struct in6_addr *);
28 static int unpack_dname(struct unpack *, char *, size_t);
29 
30 void
unpack_init(struct unpack * unpack,const char * buf,size_t len)31 unpack_init(struct unpack *unpack, const char *buf, size_t len)
32 {
33 	unpack->buf = buf;
34 	unpack->len = len;
35 	unpack->offset = 0;
36 	unpack->err = NULL;
37 }
38 
39 int
unpack_header(struct unpack * p,struct dns_header * h)40 unpack_header(struct unpack *p, struct dns_header *h)
41 {
42 	if (unpack_data(p, h, HFIXEDSZ) == -1)
43 		return (-1);
44 
45 	h->flags = ntohs(h->flags);
46 	h->qdcount = ntohs(h->qdcount);
47 	h->ancount = ntohs(h->ancount);
48 	h->nscount = ntohs(h->nscount);
49 	h->arcount = ntohs(h->arcount);
50 
51 	return (0);
52 }
53 
54 int
unpack_query(struct unpack * p,struct dns_query * q)55 unpack_query(struct unpack *p, struct dns_query *q)
56 {
57 	unpack_dname(p, q->q_dname, sizeof(q->q_dname));
58 	unpack_u16(p, &q->q_type);
59 	unpack_u16(p, &q->q_class);
60 
61 	return (p->err) ? (-1) : (0);
62 }
63 
64 int
unpack_rr(struct unpack * p,struct dns_rr * rr)65 unpack_rr(struct unpack *p, struct dns_rr *rr)
66 {
67 	uint16_t	rdlen;
68 	size_t		save_offset;
69 
70 	unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname));
71 	unpack_u16(p, &rr->rr_type);
72 	unpack_u16(p, &rr->rr_class);
73 	unpack_u32(p, &rr->rr_ttl);
74 	unpack_u16(p, &rdlen);
75 
76 	if (p->err)
77 		return (-1);
78 
79 	if (p->len - p->offset < rdlen) {
80 		p->err = "too short";
81 		return (-1);
82 	}
83 
84 	save_offset = p->offset;
85 
86 	switch (rr->rr_type) {
87 
88 	case T_CNAME:
89 		unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname));
90 		break;
91 
92 	case T_MX:
93 		unpack_u16(p, &rr->rr.mx.preference);
94 		unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange));
95 		break;
96 
97 	case T_NS:
98 		unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname));
99 		break;
100 
101 	case T_PTR:
102 		unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname));
103 		break;
104 
105 	case T_SOA:
106 		unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname));
107 		unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname));
108 		unpack_u32(p, &rr->rr.soa.serial);
109 		unpack_u32(p, &rr->rr.soa.refresh);
110 		unpack_u32(p, &rr->rr.soa.retry);
111 		unpack_u32(p, &rr->rr.soa.expire);
112 		unpack_u32(p, &rr->rr.soa.minimum);
113 		break;
114 
115 	case T_A:
116 		if (rr->rr_class != C_IN)
117 			goto other;
118 		unpack_inaddr(p, &rr->rr.in_a.addr);
119 		break;
120 
121 	case T_AAAA:
122 		if (rr->rr_class != C_IN)
123 			goto other;
124 		unpack_in6addr(p, &rr->rr.in_aaaa.addr6);
125 		break;
126 	default:
127 	other:
128 		rr->rr.other.rdata = p->buf + p->offset;
129 		rr->rr.other.rdlen = rdlen;
130 		p->offset += rdlen;
131 	}
132 
133 	if (p->err)
134 		return (-1);
135 
136 	/* make sure that the advertised rdlen is really ok */
137 	if (p->offset - save_offset != rdlen)
138 		p->err = "bad dlen";
139 
140 	return (p->err) ? (-1) : (0);
141 }
142 
143 ssize_t
dname_expand(const unsigned char * data,size_t len,size_t offset,size_t * newoffset,char * dst,size_t max)144 dname_expand(const unsigned char *data, size_t len, size_t offset,
145     size_t *newoffset, char *dst, size_t max)
146 {
147 	size_t		 n, count, end, ptr, start;
148 	ssize_t		 res;
149 
150 	if (offset >= len)
151 		return (-1);
152 
153 	res = 0;
154 	end = start = offset;
155 
156 	for (; (n = data[offset]); ) {
157 		if ((n & 0xc0) == 0xc0) {
158 			if (offset + 2 > len)
159 				return (-1);
160 			ptr = 256 * (n & ~0xc0) + data[offset + 1];
161 			if (ptr >= start)
162 				return (-1);
163 			if (end < offset + 2)
164 				end = offset + 2;
165 			offset = start = ptr;
166 			continue;
167 		}
168 		if (offset + n + 1 > len)
169 			return (-1);
170 
171 		/* copy n + at offset+1 */
172 		if (dst != NULL && max != 0) {
173 			count = (max < n + 1) ? (max) : (n + 1);
174 			memmove(dst, data + offset, count);
175 			dst += count;
176 			max -= count;
177 		}
178 		res += n + 1;
179 		offset += n + 1;
180 		if (end < offset)
181 			end = offset;
182 	}
183 	if (end < offset + 1)
184 		end = offset + 1;
185 
186 	if (dst != NULL && max != 0)
187 		dst[0] = 0;
188 	if (newoffset)
189 		*newoffset = end;
190 	return (res + 1);
191 }
192 
193 char *
print_dname(const char * _dname,char * buf,size_t max)194 print_dname(const char *_dname, char *buf, size_t max)
195 {
196 	const unsigned char *dname = _dname;
197 	char    *res;
198 	size_t   left, count;
199 
200 	if (_dname[0] == 0) {
201 		(void)strlcpy(buf, ".", max);
202 		return buf;
203 	}
204 
205 	res = buf;
206 	left = max - 1;
207 	while (dname[0] && left) {
208 		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
209 		memmove(buf, dname + 1, count);
210 		dname += dname[0] + 1;
211 		left -= count;
212 		buf += count;
213 		if (left) {
214 			left -= 1;
215 			*buf++ = '.';
216 		}
217 	}
218 	buf[0] = 0;
219 
220 	return (res);
221 }
222 
223 static int
unpack_data(struct unpack * p,void * data,size_t len)224 unpack_data(struct unpack *p, void *data, size_t len)
225 {
226 	if (p->err)
227 		return (-1);
228 
229 	if (p->len - p->offset < len) {
230 		p->err = "too short";
231 		return (-1);
232 	}
233 
234 	memmove(data, p->buf + p->offset, len);
235 	p->offset += len;
236 
237 	return (0);
238 }
239 
240 static int
unpack_u16(struct unpack * p,uint16_t * u16)241 unpack_u16(struct unpack *p, uint16_t *u16)
242 {
243 	if (unpack_data(p, u16, 2) == -1)
244 		return (-1);
245 
246 	*u16 = ntohs(*u16);
247 
248 	return (0);
249 }
250 
251 static int
unpack_u32(struct unpack * p,uint32_t * u32)252 unpack_u32(struct unpack *p, uint32_t *u32)
253 {
254 	if (unpack_data(p, u32, 4) == -1)
255 		return (-1);
256 
257 	*u32 = ntohl(*u32);
258 
259 	return (0);
260 }
261 
262 static int
unpack_inaddr(struct unpack * p,struct in_addr * a)263 unpack_inaddr(struct unpack *p, struct in_addr *a)
264 {
265 	return (unpack_data(p, a, 4));
266 }
267 
268 static int
unpack_in6addr(struct unpack * p,struct in6_addr * a6)269 unpack_in6addr(struct unpack *p, struct in6_addr *a6)
270 {
271 	return (unpack_data(p, a6, 16));
272 }
273 
274 static int
unpack_dname(struct unpack * p,char * dst,size_t max)275 unpack_dname(struct unpack *p, char *dst, size_t max)
276 {
277 	ssize_t e;
278 
279 	if (p->err)
280 		return (-1);
281 
282 	e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max);
283 	if (e == -1) {
284 		p->err = "bad domain name";
285 		return (-1);
286 	}
287 	if (e < 0 || e > MAXDNAME) {
288 		p->err = "domain name too long";
289 		return (-1);
290 	}
291 
292 	return (0);
293 }
294