1 /**************************************************************************************************
2 	$Id: reply.c,v 1.65 2006/01/18 20:46:47 bboy Exp $
3 
4 	Copyright (C) 2002-2005  Don Moore <bboy@bboy.net>
5 
6 	This program is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at Your option) any later version.
10 
11 	This program is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with this program; if not, write to the Free Software
18 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 **************************************************************************************************/
20 
21 #include "named.h"
22 
23 /* Make this nonzero to enable debugging for this source file */
24 #define	DEBUG_REPLY	1
25 
26 #if DEBUG_ENABLED && DEBUG_REPLY
27 /* Strings describing the datasections */
28 char *reply_datasection_str[] = { "QUESTION", "ANSWER", "AUTHORITY", "ADDITIONAL" };
29 #endif
30 
31 
32 /**************************************************************************************************
33 	REPLY_INIT
34 	Examines the question data, storing the name offsets (from DNS_HEADERSIZE) for compression.
35 **************************************************************************************************/
36 int
reply_init(TASK * t)37 reply_init(TASK *t)
38 {
39 	register char *c;												/* Current character in name */
40 
41 	/* Examine question data, save labels found therein. The question data should begin with
42 		the name we've already parsed into t->qname.  I believe it is safe to assume that no
43 		compression will be possible in the question. */
44 	for (c = t->qname; *c; c++)
45 		if ((c == t->qname || *c == '.') && c[1])
46 			if (name_remember(t, (c == t->qname) ? c : (c+1),
47 					(((c == t->qname) ? c : (c+1)) - t->qname) + DNS_HEADERSIZE) < -1)
48 				return (-1);
49 	return (0);
50 }
51 /*--- reply_init() ------------------------------------------------------------------------------*/
52 
53 
54 /**************************************************************************************************
55 	REPLY_ADD_ADDITIONAL
56 	Add ADDITIONAL for each item in the provided list.
57 **************************************************************************************************/
58 static void
reply_add_additional(TASK * t,RRLIST * rrlist,datasection_t section)59 reply_add_additional(TASK *t, RRLIST *rrlist, datasection_t section)
60 {
61 	register RR *p;
62 
63 	if (!rrlist)
64 		return;
65 
66 	/* Examine each RR in the rrlist */
67 	for (p = rrlist->head; p; p = p->next)
68 	{
69 		if (p->rrtype == DNS_RRTYPE_RR)
70 		{
71 			MYDNS_RR *rr = (MYDNS_RR *)p->rr;
72 			if (rr->type == DNS_QTYPE_NS || rr->type == DNS_QTYPE_MX || rr->type == DNS_QTYPE_SRV)
73 			{
74 #if DEBUG_ENABLED && DEBUG_REPLY
75 				Debug("%s: resolving `%s' (A) for ADDITIONAL data", desctask(t), rr->data);
76 #endif
77 				(void)resolve(t, ADDITIONAL, DNS_QTYPE_A, rr->data, 0);
78 			}
79 			else if (rr->type == DNS_QTYPE_CNAME)
80 			{
81 #if DEBUG_ENABLED && DEBUG_REPLY
82 				Debug("%s: resolving `%s' (CNAME) for ADDITIONAL data", desctask(t), rr->data);
83 #endif
84 				/* Don't do this */
85 				(void)resolve(t, ADDITIONAL, DNS_QTYPE_CNAME, rr->data, 0);
86 			}
87 		}
88 		t->sort_level++;
89 	}
90 }
91 /*--- reply_add_additional() --------------------------------------------------------------------*/
92 
93 
94 /**************************************************************************************************
95 	RDATA_ENLARGE
96 	Expands t->rdata by `size' bytes.  Returns a pointer to the destination.
97 **************************************************************************************************/
98 static inline char *
rdata_enlarge(TASK * t,size_t size)99 rdata_enlarge(TASK *t, size_t size)
100 {
101 	if (!size)
102 		return (NULL);
103 
104 	t->rdlen += size;
105 	if (!t->rdata)
106 	{
107 		if (!(t->rdata = malloc(t->rdlen)))
108 			Err(_("out of memory"));
109 	}
110 	else
111 	{
112 		if (!(t->rdata = realloc(t->rdata, t->rdlen)))
113 			Err(_("out of memory"));
114 	}
115 	return (t->rdata + t->rdlen - size);
116 }
117 /*--- rdata_enlarge() ---------------------------------------------------------------------------*/
118 
119 
120 /**************************************************************************************************
121 	REPLY_START_RR
122 	Begins an RR.  Appends to t->rdata all the header fields prior to rdlength.
123 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
124 **************************************************************************************************/
125 static inline int
reply_start_rr(TASK * t,RR * r,char * name,dns_qtype_t type,uint32_t ttl,char * desc)126 reply_start_rr(TASK *t, RR *r, char *name, dns_qtype_t type, uint32_t ttl, char *desc)
127 {
128 	char	enc[DNS_MAXNAMELEN+1];
129 	char	*dest;
130 	int	enclen;
131 
132 	/* name_encode returns dnserror() */
133 	if ((enclen = name_encode(t, enc, name, t->replylen + t->rdlen, 1)) < 0)
134 		return rr_error(r->id, "rr %u: %s (%s %s) (name=\"%s\")", r->id,
135 							 _("invalid name in \"name\""), desc, _("record"), name);
136 
137 	r->length = enclen + SIZE16 + SIZE16 + SIZE32;
138 
139 	if (!(dest = rdata_enlarge(t, r->length)))
140 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
141 
142 	r->offset = dest - t->rdata + DNS_HEADERSIZE + t->qdlen;
143 
144 	DNS_PUT(dest, enc, enclen);
145 	DNS_PUT16(dest, type);
146 #if STATUS_ENABLED
147 	if (r->rrtype == DNS_RRTYPE_RR && r->rr)
148 		DNS_PUT16(dest, ((MYDNS_RR *)(r->rr))->class)
149 	else
150 #endif
151 		DNS_PUT16(dest, DNS_CLASS_IN);
152 	DNS_PUT32(dest, ttl);
153 	return (0);
154 }
155 /*--- reply_start_rr() --------------------------------------------------------------------------*/
156 
157 
158 /**************************************************************************************************
159 	REPLY_ADD_GENERIC_RR
160 	Adds a generic resource record whose sole piece of data is a domain-name,
161 	or a 16-bit value plus a domain-name.
162 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
163 **************************************************************************************************/
164 static inline int
reply_add_generic_rr(TASK * t,RR * r,char * desc)165 reply_add_generic_rr(TASK *t, RR *r, char *desc)
166 {
167 	char		enc[DNS_MAXNAMELEN+1], *dest;
168 	int		size, enclen;
169 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
170 
171 #if DEBUG_ENABLED && DEBUG_REPLY
172 	Debug("%s: REPLY_ADD: `%s' IN %s `%s'", desctask(t), r->name, mydns_qtype_str(rr->type), rr->data);
173 #endif
174 
175 	if (reply_start_rr(t, r, r->name, rr->type, rr->ttl, desc) < 0)
176 		return (-1);
177 
178 	if ((enclen = name_encode(t, enc, rr->data, CUROFFSET(t), 1)) < 0)
179 		return rr_error(r->id, "rr %u: %s (%s) (data=\"%s\")", r->id,
180 							 _("invalid name in \"data\""), desc, rr->data);
181 
182 	size = enclen;
183 	r->length += SIZE16 + size;
184 
185 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
186 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
187 
188 	DNS_PUT16(dest, size);
189 	DNS_PUT(dest, enc, enclen);
190 	return (0);
191 }
192 /*--- reply_add_generic_rr() --------------------------------------------------------------------*/
193 
194 
195 /**************************************************************************************************
196 	REPLY_ADD_A
197 	Adds an A record to the reply.
198 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
199 **************************************************************************************************/
200 static inline int
reply_add_a(TASK * t,RR * r)201 reply_add_a(TASK *t, RR *r)
202 {
203 	char		*dest;
204 	int		size;
205 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
206 	struct in_addr addr;
207 	uint32_t	ip;
208 
209 	if (inet_pton(AF_INET, rr->data, (void *)&addr) <= 0)
210 	{
211 		dnserror(t, DNS_RCODE_SERVFAIL, ERR_INVALID_ADDRESS);
212 		return rr_error(r->id, "rr %u: %s (A %s) (address=\"%s\")", r->id,
213 							 _("invalid address in \"data\""), _("record"), rr->data);
214 	}
215 	ip = ntohl(addr.s_addr);
216 
217 #if DEBUG_ENABLED && DEBUG_REPLY
218 	Debug("%s: REPLY_ADD: `%s' IN A %s", desctask(t), r->name, inet_ntoa(addr));
219 #endif
220 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_A, rr->ttl, "A") < 0)
221 		return (-1);
222 
223 	size = SIZE32;
224 	r->length += SIZE16 + size;
225 
226 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
227 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
228 
229 	DNS_PUT16(dest, size);
230 	DNS_PUT32(dest, ip);
231 
232 	return (0);
233 }
234 /*--- reply_add_a() -----------------------------------------------------------------------------*/
235 
236 
237 /**************************************************************************************************
238 	REPLY_ADD_AAAA
239 	Adds an AAAA record to the reply.
240 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
241 **************************************************************************************************/
242 static inline int
reply_add_aaaa(TASK * t,RR * r)243 reply_add_aaaa(TASK *t, RR *r)
244 {
245 	char		*dest;
246 	int		size;
247 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
248 	uint8_t	addr[16];
249 
250 #if DEBUG_ENABLED && DEBUG_REPLY
251 	Debug("%s: REPLY_ADD: `%s' IN AAAA %s", desctask(t), r->name, rr->data);
252 #endif
253 
254 	if (inet_pton(AF_INET6, rr->data, (void *)&addr) <= 0)
255 	{
256 		dnserror(t, DNS_RCODE_SERVFAIL, ERR_INVALID_ADDRESS);
257 		return rr_error(r->id, "rr %u: %s (AAAA %s) (address=\"%s\")", r->id,
258 							 _("invalid address in \"data\""), _("record"), rr->data);
259 	}
260 
261 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_AAAA, rr->ttl, "AAAA") < 0)
262 		return (-1);
263 
264 	size = sizeof(uint8_t) * 16;
265 	r->length += SIZE16 + size;
266 
267 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
268 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
269 
270 	DNS_PUT16(dest, size);
271 	memcpy(dest, &addr, size);
272 	dest += size;
273 
274 	return (0);
275 }
276 /*--- reply_add_aaaa() --------------------------------------------------------------------------*/
277 
278 
279 /**************************************************************************************************
280 	REPLY_ADD_HINFO
281 	Adds an HINFO record to the reply.
282 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
283 **************************************************************************************************/
284 static inline int
reply_add_hinfo(TASK * t,RR * r)285 reply_add_hinfo(TASK *t, RR *r)
286 {
287 	char		*dest;
288 	size_t	oslen, cpulen;
289 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
290 	char		os[DNS_MAXNAMELEN + 1] = "", cpu[DNS_MAXNAMELEN + 1] = "";
291 
292 	if (hinfo_parse(rr->data, cpu, os, DNS_MAXNAMELEN) < 0)
293 	{
294 		dnserror(t, DNS_RCODE_SERVFAIL, ERR_RR_NAME_TOO_LONG);
295 		return rr_error(r->id, "rr %u: %s (HINFO %s) (data=\"%s\")", r->id,
296 							 _("name too long in \"data\""), _("record"), rr->data);
297 	}
298 
299 #if DEBUG_ENABLED && DEBUG_REPLY
300 	Debug("%s: REPLY_ADD: `%s' IN HINFO `%s %s'", desctask(t), r->name, cpu, os);
301 #endif
302 	cpulen = strlen(cpu);
303 	oslen = strlen(os);
304 
305 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_HINFO, rr->ttl, "HINFO") < 0)
306 		return (-1);
307 
308 	r->length += SIZE16 + cpulen + oslen + 2;
309 
310 	if (!(dest = rdata_enlarge(t, SIZE16 + cpulen + SIZE16 + oslen)))
311 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
312 
313 	DNS_PUT16(dest, cpulen + oslen + 2);
314 
315 	*dest++ = cpulen;
316 	memcpy(dest, cpu, cpulen);
317 	dest += cpulen;
318 
319 	*dest++ = oslen;
320 	memcpy(dest, os, oslen);
321 	dest += oslen;
322 
323 	return (0);
324 }
325 /*--- reply_add_hinfo() -------------------------------------------------------------------------*/
326 
327 
328 /**************************************************************************************************
329 	REPLY_ADD_MX
330 	Adds an MX record to the reply.
331 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
332 **************************************************************************************************/
333 static inline int
reply_add_mx(TASK * t,RR * r)334 reply_add_mx(TASK *t, RR *r)
335 {
336 	char		enc[DNS_MAXNAMELEN+1], *dest;
337 	int		size, enclen;
338 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
339 
340 #if DEBUG_ENABLED && DEBUG_REPLY
341 	Debug("%s: REPLY_ADD: `%s' IN MX `%u %s'", desctask(t), r->name, (uint16_t)rr->aux, rr->data);
342 #endif
343 
344 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_MX, rr->ttl, "MX") < 0)
345 		return (-1);
346 
347 	if ((enclen = name_encode(t, enc, rr->data, CUROFFSET(t) + SIZE16, 1)) < 0)
348 		return rr_error(r->id, "rr %u: %s (MX %s) (data=\"%s\")", r->id,
349 							 _("invalid name in \"data\""), _("record"), rr->data);
350 
351 	size = SIZE16 + enclen;
352 	r->length += SIZE16 + size;
353 
354 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
355 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
356 
357 	DNS_PUT16(dest, size);
358 	DNS_PUT16(dest, (uint16_t)rr->aux);
359 	DNS_PUT(dest, enc, enclen);
360 	return (0);
361 }
362 /*--- reply_add_mx() ----------------------------------------------------------------------------*/
363 
364 
365 /**************************************************************************************************
366 	REPLY_ADD_NAPTR
367 	Adds an NAPTR record to the reply.
368 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
369 **************************************************************************************************/
370 static inline int
reply_add_naptr(TASK * t,RR * r)371 reply_add_naptr(TASK *t, RR *r)
372 {
373 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
374 	size_t	flags_len, service_len, regex_len;
375 	char		enc[DNS_MAXNAMELEN+1], *dest;
376 	int		size, enclen, offset;
377 
378 #if DEBUG_ENABLED && DEBUG_REPLY
379 	Debug("%s: REPLY_ADD: `%s' IN NAPTR `%u %u \"%s\" \"%s\" \"%s\" \"%s\"'", desctask(t),
380 			r->name, rr->naptr_order, rr->naptr_pref, rr->naptr_flags, rr->naptr_service,
381 			rr->naptr_regex, rr->naptr_replacement);
382 #endif
383 
384 	flags_len = strlen(rr->naptr_flags);
385 	service_len = strlen(rr->naptr_service);
386 	regex_len = strlen(rr->naptr_regex);
387 
388 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_NAPTR, rr->ttl, "NAPTR") < 0)
389 		return (-1);
390 
391 	/* We are going to write "something else" and then a name, just like an MX record or something.
392 		In this case, though, the "something else" is lots of data.  Calculate the size of
393 		"something else" in 'offset' */
394 	offset = SIZE16 + SIZE16 + 1 + flags_len + 1 + service_len + 1 + regex_len;
395 
396 	/* Encode the name at the offset */
397 	if ((enclen = name_encode(t, enc, rr->naptr_replacement, CUROFFSET(t) + offset, 1)) < 0)
398 		return rr_error(r->id, "rr %u: %s (NAPTR %s) (%s=\"%s\")", r->id,
399 							 _("invalid name in \"replacement\""), _("record"), _("replacement"),
400 							 rr->naptr_replacement);
401 
402 	size = offset + enclen;
403 	r->length += SIZE16 + size;
404 
405 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
406 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
407 
408 	DNS_PUT16(dest, size);
409 	DNS_PUT16(dest, (uint16_t)rr->naptr_order);
410 	DNS_PUT16(dest, (uint16_t)rr->naptr_pref);
411 
412 	*dest++ = flags_len;
413 	memcpy(dest, rr->naptr_flags, flags_len);
414 	dest += flags_len;
415 
416 	*dest++ = service_len;
417 	memcpy(dest, rr->naptr_service, service_len);
418 	dest += service_len;
419 
420 	*dest++ = regex_len;
421 	memcpy(dest, rr->naptr_regex, regex_len);
422 	dest += regex_len;
423 
424 	DNS_PUT(dest, enc, enclen);
425 
426 	return (0);
427 }
428 /*--- reply_add_naptr() -------------------------------------------------------------------------*/
429 
430 
431 /**************************************************************************************************
432 	REPLY_ADD_RP
433 	Adds an RP record to the reply.
434 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
435 **************************************************************************************************/
436 static inline int
reply_add_rp(TASK * t,RR * r)437 reply_add_rp(TASK *t, RR *r)
438 {
439 	char		*mbox, *txt, *dest;
440 	char		encmbox[DNS_MAXNAMELEN+1], enctxt[DNS_MAXNAMELEN+1];
441 	int		size, mboxlen, txtlen;
442 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
443 
444 	mbox = rr->data;
445 	txt = rr->rp_txt;
446 
447 #if DEBUG_ENABLED && DEBUG_REPLY
448 	Debug("%s: REPLY_ADD: `%s' IN RP `%s %s'", desctask(t), r->name, mbox, txt);
449 #endif
450 
451 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_RP, rr->ttl, "RP") < 0)
452 		return (-1);
453 
454 	if ((mboxlen = name_encode(t, encmbox, mbox, CUROFFSET(t), 1)) < 0)
455 		return rr_error(r->id, "rr %u: %s (RP %s) (mbox=\"%s\")", r->id,
456 							 _("invalid name in \"mbox\""), _("record"), mbox);
457 
458 	if ((txtlen = name_encode(t, enctxt, txt, CUROFFSET(t) + mboxlen, 1)) < 0)
459 		return rr_error(r->id, "rr %u: %s (RP %s) (txt=\"%s\")", r->id,
460 							 _("invalid name in \"txt\""), _("record"), txt);
461 
462 	size = mboxlen + txtlen;
463 	r->length += SIZE16 + size;
464 
465 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
466 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
467 
468 	DNS_PUT16(dest, size);
469 	DNS_PUT(dest, encmbox, mboxlen);
470 	DNS_PUT(dest, enctxt, txtlen);
471 	return (0);
472 }
473 /*--- reply_add_rp() ----------------------------------------------------------------------------*/
474 
475 
476 /**************************************************************************************************
477 	REPLY_ADD_SOA
478 	Add a SOA record to the reply.
479 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
480 **************************************************************************************************/
481 static inline int
reply_add_soa(TASK * t,RR * r)482 reply_add_soa(TASK *t, RR *r)
483 {
484 	char			*dest, ns[DNS_MAXNAMELEN+1], mbox[DNS_MAXNAMELEN+1];
485 	int			size, nslen, mboxlen;
486 	MYDNS_SOA	*soa = (MYDNS_SOA *)r->rr;
487 
488 #if DEBUG_ENABLED && DEBUG_REPLY
489 	Debug("%s: REPLY_ADD: `%s' IN SOA (mbox=[%s])", desctask(t), soa->origin, soa->mbox);
490 #endif
491 
492 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_SOA, soa->ttl, "SOA") < 0)
493 		return (-1);
494 
495 	if ((nslen = name_encode(t, ns, soa->ns, CUROFFSET(t), 1)) < 0)
496 		return rr_error(r->id, "rr %u: %s (SOA %s) (ns=\"%s\")", r->id,
497 							 _("invalid name in \"ns\""), _("record"), soa->ns);
498 
499 	if ((mboxlen = name_encode(t, mbox, soa->mbox, CUROFFSET(t) + nslen, 1)) < 0)
500 		return rr_error(r->id, "rr %u: %s (SOA %s) (mbox=\"%s\")", r->id,
501 							 _("invalid name in \"mbox\""), _("record"), soa->mbox);
502 
503 	size = nslen + mboxlen + (SIZE32 * 5);
504 	r->length += SIZE16 + size;
505 
506 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
507 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
508 
509 	DNS_PUT16(dest, size);
510 	DNS_PUT(dest, ns, nslen);
511 	DNS_PUT(dest, mbox, mboxlen);
512 	DNS_PUT32(dest, soa->serial);
513 	DNS_PUT32(dest, soa->refresh);
514 	DNS_PUT32(dest, soa->retry);
515 	DNS_PUT32(dest, soa->expire);
516 	DNS_PUT32(dest, soa->minimum);
517 	return (0);
518 }
519 /*--- reply_add_soa() ---------------------------------------------------------------------------*/
520 
521 
522 /**************************************************************************************************
523 	REPLY_ADD_SRV
524 	Adds a SRV record to the reply.
525 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
526 **************************************************************************************************/
527 static inline int
reply_add_srv(TASK * t,RR * r)528 reply_add_srv(TASK *t, RR *r)
529 {
530 	char		enc[DNS_MAXNAMELEN+1], *dest;
531 	int		size, enclen;
532 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
533 
534 #if DEBUG_ENABLED && DEBUG_REPLY
535 	Debug("%s: REPLY_ADD: `%s' IN SRV `%u %u %u %s'",
536 			desctask(t), r->name, (uint16_t)rr->aux, rr->srv_weight, rr->srv_port, rr->data);
537 #endif
538 
539 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_SRV, rr->ttl, "SRV") < 0)
540 		return (-1);
541 
542 	/* RFC 2782 says that we can't use name compression on this field... */
543 	/* Arnt Gulbrandsen advises against using compression in the SRV target, although
544 		most clients should support it */
545 	if ((enclen = name_encode(t, enc, rr->data, CUROFFSET(t) + SIZE16 + SIZE16 + SIZE16, 0)) < 0)
546 		return rr_error(r->id, "rr %u: %s (SRV %s) (data=\"%s\")", r->id,
547 							 _("invalid name in \"data\""), _("record"), rr->data);
548 
549 	size = SIZE16 + SIZE16 + SIZE16 + enclen;
550 	r->length += SIZE16 + size;
551 
552 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
553 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
554 
555 	DNS_PUT16(dest, size);
556 	DNS_PUT16(dest, (uint16_t)rr->aux);
557 	DNS_PUT16(dest, (uint16_t)rr->srv_weight);
558 	DNS_PUT16(dest, (uint16_t)rr->srv_port);
559 	DNS_PUT(dest, enc, enclen);
560 	return (0);
561 }
562 /*--- reply_add_srv() ---------------------------------------------------------------------------*/
563 
564 
565 /**************************************************************************************************
566 	REPLY_ADD_TXT
567 	Adds a TXT record to the reply.
568 	Returns the numeric offset of the start of this record within the reply, or -1 on error.
569 **************************************************************************************************/
570 static inline int
reply_add_txt(TASK * t,RR * r)571 reply_add_txt(TASK *t, RR *r)
572 {
573 	char		*dest;
574 	char		size;
575 	size_t	len;
576 	MYDNS_RR	*rr = (MYDNS_RR *)r->rr;
577 
578 #if DEBUG_ENABLED && DEBUG_REPLY
579 	Debug("%s: REPLY_ADD: `%s' IN TXT", desctask(t), r->name);
580 #endif
581 	len = strlen(rr->data);
582 
583 	if (reply_start_rr(t, r, r->name, DNS_QTYPE_TXT, rr->ttl, "TXT") < 0)
584 		return (-1);
585 
586 	size = len + 1;
587 	r->length += SIZE16 + size;
588 
589 	if (!(dest = rdata_enlarge(t, SIZE16 + size)))
590 		return dnserror(t, DNS_RCODE_SERVFAIL, ERR_INTERNAL);
591 
592 	DNS_PUT16(dest, size);
593 	*dest++ = len;
594 	memcpy(dest, rr->data, len);
595 	dest += len;
596 	return (0);
597 }
598 /*--- reply_add_txt() ---------------------------------------------------------------------------*/
599 
600 
601 /**************************************************************************************************
602 	REPLY_PROCESS_RRLIST
603 	Adds each resource record found in `rrlist' to the reply.
604 **************************************************************************************************/
605 static int
reply_process_rrlist(TASK * t,RRLIST * rrlist)606 reply_process_rrlist(TASK *t, RRLIST *rrlist)
607 {
608 	register RR *r;
609 
610 	if (!rrlist)
611 		return (0);
612 
613 	for (r = rrlist->head; r; r = r->next)
614 	{
615 		switch (r->rrtype)
616 		{
617 			case DNS_RRTYPE_SOA:
618 				if (reply_add_soa(t, r) < 0)
619 					return (-1);
620 				break;
621 
622 			case DNS_RRTYPE_RR:
623 				{
624 					MYDNS_RR *rr = (MYDNS_RR *)r->rr;
625 
626 					if (!rr)
627 						break;
628 
629 					switch (rr->type)
630 					{
631 						case DNS_QTYPE_A:
632 							if (reply_add_a(t, r) < 0)
633 								return (-1);
634 							break;
635 
636 						case DNS_QTYPE_AAAA:
637 							if (reply_add_aaaa(t, r) < 0)
638 								return (-1);
639 							break;
640 
641 						case DNS_QTYPE_CNAME:
642 							if (reply_add_generic_rr(t, r, "CNAME") < 0)
643 								return (-1);
644 							break;
645 
646 						case DNS_QTYPE_HINFO:
647 							if (reply_add_hinfo(t, r) < 0)
648 								return (-1);
649 							break;
650 
651 						case DNS_QTYPE_MX:
652 							if (reply_add_mx(t, r) < 0)
653 								return (-1);
654 							break;
655 
656 						case DNS_QTYPE_NAPTR:
657 							if (reply_add_naptr(t, r) < 0)
658 								return (-1);
659 							break;
660 
661 						case DNS_QTYPE_NS:
662 							if (reply_add_generic_rr(t, r, "NS") < 0)
663 								return (-1);
664 							break;
665 
666 						case DNS_QTYPE_PTR:
667 							if (reply_add_generic_rr(t, r, "PTR") < 0)
668 								return (-1);
669 							break;
670 
671 						case DNS_QTYPE_RP:
672 							if (reply_add_rp(t, r) < 0)
673 								return (-1);
674 							break;
675 
676 						case DNS_QTYPE_SRV:
677 							if (reply_add_srv(t, r) < 0)
678 								return (-1);
679 							break;
680 
681 						case DNS_QTYPE_TXT:
682 							if (reply_add_txt(t, r) < 0)
683 								return (-1);
684 							break;
685 
686 						default:
687 							Warnx("%s: %s: %s", desctask(t), mydns_qtype_str(rr->type),
688 									_("unsupported resource record type"));
689 					}
690 				}
691 				break;
692 		}
693 	}
694 	return (0);
695 }
696 /*--- reply_process_rrlist() --------------------------------------------------------------------*/
697 
698 
699 /**************************************************************************************************
700 	TRUNCATE_RRLIST
701 	Returns new count of items in this list.
702 	The TC flag is _not_ set if data was truncated from the ADDITIONAL section.
703 **************************************************************************************************/
704 static int
truncate_rrlist(TASK * t,off_t maxpkt,RRLIST * rrlist,datasection_t ds)705 truncate_rrlist(TASK *t, off_t maxpkt, RRLIST *rrlist, datasection_t ds)
706 {
707 	register RR *rr;
708 	register int recs;
709 #if DEBUG_ENABLED && DEBUG_REPLY
710 	int orig_recs = rrlist->size;
711 #endif
712 
713 	/* Warn about truncated packets, but only if TCP is not enabled.  Most resolvers will try
714 		TCP if a UDP packet is truncated. */
715 	if (!tcp_enabled)
716 		Verbose("%s: %s", desctask(t), _("query truncated"));
717 
718 	recs = rrlist->size;
719 	for (rr = rrlist->head; rr; rr = rr->next)
720 	{
721 		if (rr->offset + rr->length >= maxpkt)
722 		{
723 			recs--;
724 			if (ds != ADDITIONAL)
725 				t->hdr.tc = 1;
726 		}
727 		else
728 			t->rdlen += rr->length;
729 	}
730 #if DEBUG_ENABLED && DEBUG_REPLY
731 	Debug("%s section truncated from %d records to %d records",
732 			reply_datasection_str[ds], orig_recs, recs);
733 #endif
734 	return (recs);
735 }
736 /*--- truncate_rrlist() -------------------------------------------------------------------------*/
737 
738 
739 /**************************************************************************************************
740 	REPLY_CHECK_TRUNCATION
741 	If this reply would be truncated, removes any RR's that won't fit and sets the truncation flag.
742 **************************************************************************************************/
743 static void
reply_check_truncation(TASK * t,int * ancount,int * nscount,int * arcount)744 reply_check_truncation(TASK *t, int *ancount, int *nscount, int *arcount)
745 {
746 	size_t maxpkt = (t->protocol == SOCK_STREAM ? DNS_MAXPACKETLEN_TCP : DNS_MAXPACKETLEN_UDP);
747 	size_t maxrd = maxpkt - (DNS_HEADERSIZE + t->qdlen);
748 
749 	if (t->rdlen <= maxrd)
750 		return;
751 
752 #if DEBUG_ENABLED && DEBUG_REPLY
753 	Debug("reply_check_truncation() needs to truncate reply (%d) to fit packet max (%d)",
754 			t->rdlen, maxrd);
755 #endif
756 
757 	/* Loop through an/ns/ar sections, truncating as necessary, and updating counts */
758 	t->rdlen = 0;
759 	*ancount = truncate_rrlist(t, maxpkt, &t->an, ANSWER);
760 	*nscount = truncate_rrlist(t, maxpkt, &t->ns, AUTHORITY);
761 	*arcount = truncate_rrlist(t, maxpkt, &t->ar, ADDITIONAL);
762 }
763 /*--- reply_check_truncation() ------------------------------------------------------------------*/
764 
765 
766 /**************************************************************************************************
767 	BUILD_CACHE_REPLY
768 	Builds reply data from cached answer.
769 **************************************************************************************************/
770 void
build_cache_reply(TASK * t)771 build_cache_reply(TASK *t)
772 {
773 	char *dest = t->reply;
774 
775 	DNS_PUT16(dest, t->id);										/* Query ID */
776 	DNS_PUT(dest, &t->hdr, SIZE16);							/* Header */
777 }
778 /*--- build_cache_reply() -----------------------------------------------------------------------*/
779 
780 
781 /**************************************************************************************************
782 	BUILD_REPLY
783 	Given a task, constructs the reply data.
784 **************************************************************************************************/
785 void
build_reply(TASK * t,int want_additional)786 build_reply(TASK *t, int want_additional)
787 {
788 	char	*dest;
789 	int	ancount, nscount, arcount;
790 
791 	/* Add data to ADDITIONAL section */
792 	if (want_additional)
793 	{
794 		reply_add_additional(t, &t->an, ANSWER);
795 		reply_add_additional(t, &t->ns, AUTHORITY);
796 	}
797 
798 	/* Sort records where necessary */
799 	if (t->an.a_records > 1)									/* ANSWER section: Sort A/AAAA records */
800 		sort_a_recs(t, &t->an, ANSWER);
801 	if (t->an.mx_records > 1)									/* ANSWER section: Sort MX records */
802 		sort_mx_recs(t, &t->an, ANSWER);
803 	if (t->an.srv_records > 1)									/* ANSWER section: Sort SRV records */
804 		sort_srv_recs(t, &t->an, ANSWER);
805 	if (t->ar.a_records > 1)									/* AUTHORITY section: Sort A/AAAA records */
806 		sort_a_recs(t, &t->ar, AUTHORITY);
807 
808 	/* Build `rdata' containing resource records in ANSWER, AUTHORITY, and ADDITIONAL */
809 	t->replylen = DNS_HEADERSIZE + t->qdlen + t->rdlen;
810 	if (reply_process_rrlist(t, &t->an) || reply_process_rrlist(t, &t->ns) || reply_process_rrlist(t, &t->ar))
811 	{
812 		/* Empty RR lists */
813 		rrlist_free(&t->an);
814 		rrlist_free(&t->ns);
815 		rrlist_free(&t->ar);
816 
817 		/* Make sure reply is empty */
818 		t->replylen = 0;
819 		t->rdlen = 0;
820 		Free(t->rdata);
821 	}
822 
823 	ancount = t->an.size;
824 	nscount = t->ns.size;
825 	arcount = t->ar.size;
826 
827 	/* Verify reply length */
828 	reply_check_truncation(t, &ancount, &nscount, &arcount);
829 
830 	/* Make sure header bits are set correctly */
831 	t->hdr.qr = 1;
832 	t->hdr.cd = 0;
833 
834 	/* Construct the reply */
835 	t->replylen = DNS_HEADERSIZE + t->qdlen + t->rdlen;
836 	dest = t->reply = malloc(t->replylen);
837 	if (!t->reply)
838 		Err(_("out of memory"));
839 
840 	DNS_PUT16(dest, t->id);										/* Query ID */
841 	DNS_PUT(dest, &t->hdr, SIZE16);							/* Header */
842 	DNS_PUT16(dest, t->qdcount);								/* QUESTION count */
843 	DNS_PUT16(dest, ancount);									/* ANSWER count */
844 	DNS_PUT16(dest, nscount);									/* AUTHORITY count */
845 	DNS_PUT16(dest, arcount);									/* ADDITIONAL count */
846 	if (t->qdlen && t->qd)
847 		DNS_PUT(dest, t->qd, t->qdlen);						/* Data for QUESTION section */
848 	DNS_PUT(dest, t->rdata, t->rdlen);						/* Resource record data */
849 
850 #if DEBUG_ENABLED && DEBUG_REPLY
851 	Debug("%s: reply:     id = %u", desctask(t), t->id);
852 	Debug("%s: reply:     qr = %u (message is a %s)", desctask(t), t->hdr.qr, t->hdr.qr ? "response" : "query");
853 	Debug("%s: reply: opcode = %u (%s)", desctask(t), t->hdr.opcode, mydns_opcode_str(t->hdr.opcode));
854 	Debug("%s: reply:     aa = %u (answer %s)", desctask(t), t->hdr.aa, t->hdr.aa ? "is authoritative" : "not authoritative");
855 	Debug("%s: reply:     tc = %u (message %s)", desctask(t), t->hdr.tc, t->hdr.tc ? "truncated" : "not truncated");
856 	Debug("%s: reply:     rd = %u (%s)", desctask(t), t->hdr.rd, t->hdr.rd ? "recursion desired" : "no recursion");
857 	Debug("%s: reply:     ra = %u (recursion %s)", desctask(t), t->hdr.ra, t->hdr.ra ? "available" : "unavailable");
858 	Debug("%s: reply:  rcode = %u (%s)", desctask(t), t->hdr.rcode, mydns_rcode_str(t->hdr.rcode));
859 	/* escdata(t->reply, t->replylen); */
860 #endif
861 }
862 /*--- build_reply() -----------------------------------------------------------------------------*/
863 
864 /* vi:set ts=3: */
865 /* NEED_PO */
866