xref: /openbsd-src/usr.sbin/smtpd/dns.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: dns.c,v 1.83 2015/10/28 07:28:13 gilles Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
5  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6  * Copyright (c) 2011-2014 Eric Faurot <eric@faurot.net>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/socket.h>
23 #include <sys/tree.h>
24 #include <sys/queue.h>
25 #include <sys/uio.h>
26 
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include <arpa/nameser.h>
30 #include <netdb.h>
31 
32 #include <asr.h>
33 #include <event.h>
34 #include <imsg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <limits.h>
39 
40 #include "smtpd.h"
41 #include "log.h"
42 
43 struct dns_lookup {
44 	struct dns_session	*session;
45 	int			 preference;
46 };
47 
48 struct dns_session {
49 	struct mproc		*p;
50 	uint64_t		 reqid;
51 	int			 type;
52 	char			 name[HOST_NAME_MAX+1];
53 	size_t			 mxfound;
54 	int			 error;
55 	int			 refcount;
56 };
57 
58 static void dns_lookup_host(struct dns_session *, const char *, int);
59 static void dns_dispatch_host(struct asr_result *, void *);
60 static void dns_dispatch_ptr(struct asr_result *, void *);
61 static void dns_dispatch_mx(struct asr_result *, void *);
62 static void dns_dispatch_mx_preference(struct asr_result *, void *);
63 
64 struct unpack {
65 	const char	*buf;
66 	size_t		 len;
67 	size_t		 offset;
68 	const char	*err;
69 };
70 
71 struct dns_header {
72 	uint16_t	id;
73 	uint16_t	flags;
74 	uint16_t	qdcount;
75 	uint16_t	ancount;
76 	uint16_t	nscount;
77 	uint16_t	arcount;
78 };
79 
80 struct dns_query {
81 	char		q_dname[MAXDNAME];
82 	uint16_t	q_type;
83 	uint16_t	q_class;
84 };
85 
86 struct dns_rr {
87 	char		rr_dname[MAXDNAME];
88 	uint16_t	rr_type;
89 	uint16_t	rr_class;
90 	uint32_t	rr_ttl;
91 	union {
92 		struct {
93 			char	cname[MAXDNAME];
94 		} cname;
95 		struct {
96 			uint16_t	preference;
97 			char		exchange[MAXDNAME];
98 		} mx;
99 		struct {
100 			char	nsname[MAXDNAME];
101 		} ns;
102 		struct {
103 			char	ptrname[MAXDNAME];
104 		} ptr;
105 		struct {
106 			char		mname[MAXDNAME];
107 			char		rname[MAXDNAME];
108 			uint32_t	serial;
109 			uint32_t	refresh;
110 			uint32_t	retry;
111 			uint32_t	expire;
112 			uint32_t	minimum;
113 		} soa;
114 		struct {
115 			struct in_addr	addr;
116 		} in_a;
117 		struct {
118 			struct in6_addr	addr6;
119 		} in_aaaa;
120 		struct {
121 			uint16_t	 rdlen;
122 			const void	*rdata;
123 		} other;
124 	} rr;
125 };
126 
127 static char *print_dname(const char *, char *, size_t);
128 static ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *,
129     char *, size_t);
130 static int unpack_data(struct unpack *, void *, size_t);
131 static int unpack_u16(struct unpack *, uint16_t *);
132 static int unpack_u32(struct unpack *, uint32_t *);
133 static int unpack_inaddr(struct unpack *, struct in_addr *);
134 static int unpack_in6addr(struct unpack *, struct in6_addr *);
135 static int unpack_dname(struct unpack *, char *, size_t);
136 static void unpack_init(struct unpack *, const char *, size_t);
137 static int unpack_header(struct unpack *, struct dns_header *);
138 static int unpack_query(struct unpack *, struct dns_query *);
139 static int unpack_rr(struct unpack *, struct dns_rr *);
140 
141 
142 static int
143 domainname_is_addr(const char *s, struct sockaddr *sa, socklen_t *sl)
144 {
145 	struct addrinfo	hints, *res;
146 	socklen_t	sl2;
147 	size_t		l;
148 	char		buf[SMTPD_MAXDOMAINPARTSIZE];
149 	int		i6, error;
150 
151 	if (*s != '[')
152 		return (0);
153 
154 	i6 = (strncasecmp("[IPv6:", s, 6) == 0);
155 	s += i6 ? 6 : 1;
156 
157 	l = strlcpy(buf, s, sizeof(buf));
158 	if (l >= sizeof(buf) || l == 0 || buf[l - 1] != ']')
159 		return (0);
160 
161 	buf[l - 1] = '\0';
162 	memset(&hints, 0, sizeof(hints));
163 	hints.ai_flags = AI_NUMERICHOST;
164 	hints.ai_socktype = SOCK_STREAM;
165 	if (i6)
166 		hints.ai_family = AF_INET6;
167 
168 	res = NULL;
169 	if ((error = getaddrinfo(buf, NULL, &hints, &res))) {
170 		log_warnx("getaddrinfo: %s", gai_strerror(error));
171 	}
172 
173 	if (!res)
174 		return (0);
175 
176 	if (sa && sl) {
177 		sl2 = *sl;
178 		if (sl2 > res->ai_addrlen)
179 			sl2 = res->ai_addrlen;
180 		memmove(sa, res->ai_addr, sl2);
181 		*sl = res->ai_addrlen;
182 	}
183 
184 	freeaddrinfo(res);
185 	return (1);
186 }
187 
188 void
189 dns_imsg(struct mproc *p, struct imsg *imsg)
190 {
191 	struct sockaddr_storage	 ss;
192 	struct dns_session	*s;
193 	struct sockaddr		*sa;
194 	struct asr_query	*as;
195 	struct msg		 m;
196 	const char		*domain, *mx, *host;
197 	socklen_t		 sl;
198 
199 	s = xcalloc(1, sizeof *s, "dns_imsg");
200 	s->type = imsg->hdr.type;
201 	s->p = p;
202 
203 	m_msg(&m, imsg);
204 	m_get_id(&m, &s->reqid);
205 
206 	switch (s->type) {
207 
208 	case IMSG_MTA_DNS_HOST:
209 		m_get_string(&m, &host);
210 		m_end(&m);
211 		dns_lookup_host(s, host, -1);
212 		return;
213 
214 	case IMSG_MTA_DNS_PTR:
215 	case IMSG_SMTP_DNS_PTR:
216 		sa = (struct sockaddr *)&ss;
217 		m_get_sockaddr(&m, sa);
218 		m_end(&m);
219 		as = getnameinfo_async(sa, sa->sa_len, s->name, sizeof(s->name),
220 		    NULL, 0, 0, NULL);
221 		event_asr_run(as, dns_dispatch_ptr, s);
222 		return;
223 
224 	case IMSG_MTA_DNS_MX:
225 		m_get_string(&m, &domain);
226 		m_end(&m);
227 		(void)strlcpy(s->name, domain, sizeof(s->name));
228 
229 		sa = (struct sockaddr *)&ss;
230 		sl = sizeof(ss);
231 
232 		if (domainname_is_addr(domain, sa, &sl)) {
233 			m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1);
234 			m_add_id(s->p, s->reqid);
235 			m_add_sockaddr(s->p, sa);
236 			m_add_int(s->p, -1);
237 			m_close(s->p);
238 
239 			m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
240 			m_add_id(s->p, s->reqid);
241 			m_add_int(s->p, DNS_OK);
242 			m_close(s->p);
243 			free(s);
244 			return;
245 		}
246 
247 		as = res_query_async(s->name, C_IN, T_MX, NULL);
248 		if (as == NULL) {
249 			log_warn("warn: req_query_async: %s", s->name);
250 			m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
251 			m_add_id(s->p, s->reqid);
252 			m_add_int(s->p, DNS_EINVAL);
253 			m_close(s->p);
254 			free(s);
255 			return;
256 		}
257 
258 		event_asr_run(as, dns_dispatch_mx, s);
259 		return;
260 
261 	case IMSG_MTA_DNS_MX_PREFERENCE:
262 		m_get_string(&m, &domain);
263 		m_get_string(&m, &mx);
264 		m_end(&m);
265 		(void)strlcpy(s->name, mx, sizeof(s->name));
266 
267 		as = res_query_async(domain, C_IN, T_MX, NULL);
268 		if (as == NULL) {
269 			m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1);
270 			m_add_id(s->p, s->reqid);
271 			m_add_int(s->p, DNS_ENOTFOUND);
272 			m_close(s->p);
273 			free(s);
274 			return;
275 		}
276 
277 		event_asr_run(as, dns_dispatch_mx_preference, s);
278 		return;
279 
280 	default:
281 		log_warnx("warn: bad dns request %d", s->type);
282 		fatal(NULL);
283 	}
284 }
285 
286 static void
287 dns_dispatch_host(struct asr_result *ar, void *arg)
288 {
289 	struct dns_session	*s;
290 	struct dns_lookup	*lookup = arg;
291 	struct addrinfo		*ai;
292 
293 	s = lookup->session;
294 
295 	for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) {
296 		s->mxfound++;
297 		m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1);
298 		m_add_id(s->p, s->reqid);
299 		m_add_sockaddr(s->p, ai->ai_addr);
300 		m_add_int(s->p, lookup->preference);
301 		m_close(s->p);
302 	}
303 	free(lookup);
304 	if (ar->ar_addrinfo)
305 		freeaddrinfo(ar->ar_addrinfo);
306 
307 	if (ar->ar_gai_errno)
308 		s->error = ar->ar_gai_errno;
309 
310 	if (--s->refcount)
311 		return;
312 
313 	m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
314 	m_add_id(s->p, s->reqid);
315 	m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND);
316 	m_close(s->p);
317 	free(s);
318 }
319 
320 static void
321 dns_dispatch_ptr(struct asr_result *ar, void *arg)
322 {
323 	struct dns_session	*s = arg;
324 
325 	/* The error code could be more precise, but we don't currently care */
326 	m_create(s->p,  s->type, 0, 0, -1);
327 	m_add_id(s->p, s->reqid);
328 	m_add_int(s->p, ar->ar_gai_errno ? DNS_ENOTFOUND : DNS_OK);
329 	if (ar->ar_gai_errno == 0)
330 		m_add_string(s->p, s->name);
331 	m_close(s->p);
332 	free(s);
333 }
334 
335 static void
336 dns_dispatch_mx(struct asr_result *ar, void *arg)
337 {
338 	struct dns_session	*s = arg;
339 	struct unpack		 pack;
340 	struct dns_header	 h;
341 	struct dns_query	 q;
342 	struct dns_rr		 rr;
343 	char			 buf[512];
344 	size_t			 found;
345 
346 	if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA) {
347 
348 		m_create(s->p,  IMSG_MTA_DNS_HOST_END, 0, 0, -1);
349 		m_add_id(s->p, s->reqid);
350 		if (ar->ar_rcode == NXDOMAIN)
351 			m_add_int(s->p, DNS_ENONAME);
352 		else if (ar->ar_h_errno == NO_RECOVERY)
353 			m_add_int(s->p, DNS_EINVAL);
354 		else
355 			m_add_int(s->p, DNS_RETRY);
356 		m_close(s->p);
357 		free(s);
358 		free(ar->ar_data);
359 		return;
360 	}
361 
362 	unpack_init(&pack, ar->ar_data, ar->ar_datalen);
363 	unpack_header(&pack, &h);
364 	unpack_query(&pack, &q);
365 
366 	found = 0;
367 	for (; h.ancount; h.ancount--) {
368 		unpack_rr(&pack, &rr);
369 		if (rr.rr_type != T_MX)
370 			continue;
371 		print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
372 		buf[strlen(buf) - 1] = '\0';
373 		dns_lookup_host(s, buf, rr.rr.mx.preference);
374 		found++;
375 	}
376 	free(ar->ar_data);
377 
378 	/* fallback to host if no MX is found. */
379 	if (found == 0)
380 		dns_lookup_host(s, s->name, 0);
381 }
382 
383 static void
384 dns_dispatch_mx_preference(struct asr_result *ar, void *arg)
385 {
386 	struct dns_session	*s = arg;
387 	struct unpack		 pack;
388 	struct dns_header	 h;
389 	struct dns_query	 q;
390 	struct dns_rr		 rr;
391 	char			 buf[512];
392 	int			 error;
393 
394 	if (ar->ar_h_errno) {
395 		if (ar->ar_rcode == NXDOMAIN)
396 			error = DNS_ENONAME;
397 		else if (ar->ar_h_errno == NO_RECOVERY
398 		    || ar->ar_h_errno == NO_DATA)
399 			error = DNS_EINVAL;
400 		else
401 			error = DNS_RETRY;
402 	}
403 	else {
404 		error = DNS_ENOTFOUND;
405 		unpack_init(&pack, ar->ar_data, ar->ar_datalen);
406 		unpack_header(&pack, &h);
407 		unpack_query(&pack, &q);
408 		for (; h.ancount; h.ancount--) {
409 			unpack_rr(&pack, &rr);
410 			if (rr.rr_type != T_MX)
411 				continue;
412 			print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
413 			buf[strlen(buf) - 1] = '\0';
414 			if (!strcasecmp(s->name, buf)) {
415 				error = DNS_OK;
416 				break;
417 			}
418 		}
419 	}
420 
421 	free(ar->ar_data);
422 
423 	m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1);
424 	m_add_id(s->p, s->reqid);
425 	m_add_int(s->p, error);
426 	if (error == DNS_OK)
427 		m_add_int(s->p, rr.rr.mx.preference);
428 	m_close(s->p);
429 	free(s);
430 }
431 
432 static void
433 dns_lookup_host(struct dns_session *s, const char *host, int preference)
434 {
435 	struct dns_lookup	*lookup;
436 	struct addrinfo		 hints;
437 	char			 hostcopy[HOST_NAME_MAX+1];
438 	char			*p;
439 	void			*as;
440 
441 	lookup = xcalloc(1, sizeof *lookup, "dns_lookup_host");
442 	lookup->preference = preference;
443 	lookup->session = s;
444 	s->refcount++;
445 
446 	if (*host == '[') {
447 		if (strncasecmp("[IPv6:", host, 6) == 0)
448 			host += 6;
449 		else
450 			host += 1;
451 		(void)strlcpy(hostcopy, host, sizeof hostcopy);
452 		p = strchr(hostcopy, ']');
453 		if (p)
454 			*p = 0;
455 		host = hostcopy;
456 	}
457 
458 	memset(&hints, 0, sizeof(hints));
459 	hints.ai_flags = AI_ADDRCONFIG;
460 	hints.ai_family = PF_UNSPEC;
461 	hints.ai_socktype = SOCK_STREAM;
462 	as = getaddrinfo_async(host, NULL, &hints, NULL);
463 	event_asr_run(as, dns_dispatch_host, lookup);
464 }
465 
466 static char *
467 print_dname(const char *_dname, char *buf, size_t max)
468 {
469 	const unsigned char *dname = _dname;
470 	char    *res;
471 	size_t   left, n, count;
472 
473 	if (_dname[0] == 0) {
474 		(void)strlcpy(buf, ".", max);
475 		return buf;
476 	}
477 
478 	res = buf;
479 	left = max - 1;
480 	for (n = 0; dname[0] && left; n += dname[0]) {
481 		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
482 		memmove(buf, dname + 1, count);
483 		dname += dname[0] + 1;
484 		left -= count;
485 		buf += count;
486 		if (left) {
487 			left -= 1;
488 			*buf++ = '.';
489 		}
490 	}
491 	buf[0] = 0;
492 
493 	return (res);
494 }
495 
496 static ssize_t
497 dname_expand(const unsigned char *data, size_t len, size_t offset,
498     size_t *newoffset, char *dst, size_t max)
499 {
500 	size_t		 n, count, end, ptr, start;
501 	ssize_t		 res;
502 
503 	if (offset >= len)
504 		return (-1);
505 
506 	res = 0;
507 	end = start = offset;
508 
509 	for (; (n = data[offset]); ) {
510 		if ((n & 0xc0) == 0xc0) {
511 			if (offset + 2 > len)
512 				return (-1);
513 			ptr = 256 * (n & ~0xc0) + data[offset + 1];
514 			if (ptr >= start)
515 				return (-1);
516 			if (end < offset + 2)
517 				end = offset + 2;
518 			offset = start = ptr;
519 			continue;
520 		}
521 		if (offset + n + 1 > len)
522 			return (-1);
523 
524 		/* copy n + at offset+1 */
525 		if (dst != NULL && max != 0) {
526 			count = (max < n + 1) ? (max) : (n + 1);
527 			memmove(dst, data + offset, count);
528 			dst += count;
529 			max -= count;
530 		}
531 		res += n + 1;
532 		offset += n + 1;
533 		if (end < offset)
534 			end = offset;
535 	}
536 	if (end < offset + 1)
537 		end = offset + 1;
538 
539 	if (dst != NULL && max != 0)
540 		dst[0] = 0;
541 	if (newoffset)
542 		*newoffset = end;
543 	return (res + 1);
544 }
545 
546 void
547 unpack_init(struct unpack *unpack, const char *buf, size_t len)
548 {
549 	unpack->buf = buf;
550 	unpack->len = len;
551 	unpack->offset = 0;
552 	unpack->err = NULL;
553 }
554 
555 static int
556 unpack_data(struct unpack *p, void *data, size_t len)
557 {
558 	if (p->err)
559 		return (-1);
560 
561 	if (p->len - p->offset < len) {
562 		p->err = "too short";
563 		return (-1);
564 	}
565 
566 	memmove(data, p->buf + p->offset, len);
567 	p->offset += len;
568 
569 	return (0);
570 }
571 
572 static int
573 unpack_u16(struct unpack *p, uint16_t *u16)
574 {
575 	if (unpack_data(p, u16, 2) == -1)
576 		return (-1);
577 
578 	*u16 = ntohs(*u16);
579 
580 	return (0);
581 }
582 
583 static int
584 unpack_u32(struct unpack *p, uint32_t *u32)
585 {
586 	if (unpack_data(p, u32, 4) == -1)
587 		return (-1);
588 
589 	*u32 = ntohl(*u32);
590 
591 	return (0);
592 }
593 
594 static int
595 unpack_inaddr(struct unpack *p, struct in_addr *a)
596 {
597 	return (unpack_data(p, a, 4));
598 }
599 
600 static int
601 unpack_in6addr(struct unpack *p, struct in6_addr *a6)
602 {
603 	return (unpack_data(p, a6, 16));
604 }
605 
606 static int
607 unpack_dname(struct unpack *p, char *dst, size_t max)
608 {
609 	ssize_t e;
610 
611 	if (p->err)
612 		return (-1);
613 
614 	e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max);
615 	if (e == -1) {
616 		p->err = "bad domain name";
617 		return (-1);
618 	}
619 	if (e < 0 || e > MAXDNAME) {
620 		p->err = "domain name too long";
621 		return (-1);
622 	}
623 
624 	return (0);
625 }
626 
627 static int
628 unpack_header(struct unpack *p, struct dns_header *h)
629 {
630 	if (unpack_data(p, h, HFIXEDSZ) == -1)
631 		return (-1);
632 
633 	h->flags = ntohs(h->flags);
634 	h->qdcount = ntohs(h->qdcount);
635 	h->ancount = ntohs(h->ancount);
636 	h->nscount = ntohs(h->nscount);
637 	h->arcount = ntohs(h->arcount);
638 
639 	return (0);
640 }
641 
642 static int
643 unpack_query(struct unpack *p, struct dns_query *q)
644 {
645 	unpack_dname(p, q->q_dname, sizeof(q->q_dname));
646 	unpack_u16(p, &q->q_type);
647 	unpack_u16(p, &q->q_class);
648 
649 	return (p->err) ? (-1) : (0);
650 }
651 
652 static int
653 unpack_rr(struct unpack *p, struct dns_rr *rr)
654 {
655 	uint16_t	rdlen;
656 	size_t		save_offset;
657 
658 	unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname));
659 	unpack_u16(p, &rr->rr_type);
660 	unpack_u16(p, &rr->rr_class);
661 	unpack_u32(p, &rr->rr_ttl);
662 	unpack_u16(p, &rdlen);
663 
664 	if (p->err)
665 		return (-1);
666 
667 	if (p->len - p->offset < rdlen) {
668 		p->err = "too short";
669 		return (-1);
670 	}
671 
672 	save_offset = p->offset;
673 
674 	switch (rr->rr_type) {
675 
676 	case T_CNAME:
677 		unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname));
678 		break;
679 
680 	case T_MX:
681 		unpack_u16(p, &rr->rr.mx.preference);
682 		unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange));
683 		break;
684 
685 	case T_NS:
686 		unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname));
687 		break;
688 
689 	case T_PTR:
690 		unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname));
691 		break;
692 
693 	case T_SOA:
694 		unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname));
695 		unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname));
696 		unpack_u32(p, &rr->rr.soa.serial);
697 		unpack_u32(p, &rr->rr.soa.refresh);
698 		unpack_u32(p, &rr->rr.soa.retry);
699 		unpack_u32(p, &rr->rr.soa.expire);
700 		unpack_u32(p, &rr->rr.soa.minimum);
701 		break;
702 
703 	case T_A:
704 		if (rr->rr_class != C_IN)
705 			goto other;
706 		unpack_inaddr(p, &rr->rr.in_a.addr);
707 		break;
708 
709 	case T_AAAA:
710 		if (rr->rr_class != C_IN)
711 			goto other;
712 		unpack_in6addr(p, &rr->rr.in_aaaa.addr6);
713 		break;
714 	default:
715 	other:
716 		rr->rr.other.rdata = p->buf + p->offset;
717 		rr->rr.other.rdlen = rdlen;
718 		p->offset += rdlen;
719 	}
720 
721 	if (p->err)
722 		return (-1);
723 
724 	/* make sure that the advertised rdlen is really ok */
725 	if (p->offset - save_offset != rdlen)
726 		p->err = "bad dlen";
727 
728 	return (p->err) ? (-1) : (0);
729 }
730