ns_name.c revision 1.2
1/*	$NetBSD: ns_name.c,v 1.2 2018/04/07 22:37:29 christos Exp $	*/
2
3/*
4 * Copyright (c) 2004-2017 by Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (c) 1996-2003 by Internet Software Consortium
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
17 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 *
19 *   Internet Systems Consortium, Inc.
20 *   950 Charter Street
21 *   Redwood City, CA 94063
22 *   <info@isc.org>
23 *   http://www.isc.org/
24 */
25
26#include <sys/cdefs.h>
27__RCSID("$NetBSD: ns_name.c,v 1.2 2018/04/07 22:37:29 christos Exp $");
28
29#include <sys/types.h>
30
31#include <netinet/in.h>
32#include <sys/socket.h>
33
34#include <errno.h>
35#include <string.h>
36#include <ctype.h>
37
38#include "ns_name.h"
39#include "arpa/nameser.h"
40
41/* Data. */
42
43static const char	digits[] = "0123456789";
44
45/* Forward. */
46
47static int		special(int);
48static int		printable(int);
49static int		dn_find(const u_char *, const u_char *,
50				const u_char * const *,
51				const u_char * const *);
52
53/* Public. */
54
55/*
56 * MRns_name_ntop(src, dst, dstsiz)
57 *	Convert an encoded domain name to printable ascii as per RFC1035.
58 * return:
59 *	Number of bytes written to buffer, or -1 (with errno set)
60 * notes:
61 *	The root is returned as "."
62 *	All other domains are returned in non absolute form
63 */
64int
65MRns_name_ntop(const u_char *src, char *dst, size_t dstsiz) {
66	const u_char *cp;
67	char *dn, *eom;
68	u_char c;
69	u_int n;
70
71	cp = src;
72	dn = dst;
73	eom = dst + dstsiz;
74
75	while ((n = *cp++) != 0) {
76		if ((n & NS_CMPRSFLGS) != 0) {
77			/* Some kind of compression pointer. */
78			errno = EMSGSIZE;
79			return (-1);
80		}
81		if (dn != dst) {
82			if (dn >= eom) {
83				errno = EMSGSIZE;
84				return (-1);
85			}
86			*dn++ = '.';
87		}
88		if (dn + n >= eom) {
89			errno = EMSGSIZE;
90			return (-1);
91		}
92		for ((void)NULL; n > 0; n--) {
93			c = *cp++;
94			if (special(c)) {
95				if (dn + 1 >= eom) {
96					errno = EMSGSIZE;
97					return (-1);
98				}
99				*dn++ = '\\';
100				*dn++ = (char)c;
101			} else if (!printable(c)) {
102				if (dn + 3 >= eom) {
103					errno = EMSGSIZE;
104					return (-1);
105				}
106				*dn++ = '\\';
107				*dn++ = digits[c / 100];
108				*dn++ = digits[(c % 100) / 10];
109				*dn++ = digits[c % 10];
110			} else {
111				if (dn >= eom) {
112					errno = EMSGSIZE;
113					return (-1);
114				}
115				*dn++ = (char)c;
116			}
117		}
118	}
119	if (dn == dst) {
120		if (dn >= eom) {
121			errno = EMSGSIZE;
122			return (-1);
123		}
124		*dn++ = '.';
125	}
126	if (dn >= eom) {
127		errno = EMSGSIZE;
128		return (-1);
129	}
130	*dn++ = '\0';
131	return (dn - dst);
132}
133
134/*
135 * MRns_name_pton(src, dst, dstsiz)
136 *	Convert a ascii string into an encoded domain name as per RFC1035.
137 * return:
138 *	-1 if it fails
139 *	1 if string was fully qualified
140 *	0 is string was not fully qualified
141 * notes:
142 *	Enforces label and domain length limits.
143 */
144
145int
146MRns_name_pton(const char *src, u_char *dst, size_t dstsiz) {
147	u_char *label, *bp, *eom;
148	int c, n, escaped;
149	char *cp;
150
151	escaped = 0;
152	bp = dst;
153	eom = dst + dstsiz;
154	label = bp++;
155
156	while ((c = *src++) != 0) {
157		if (escaped) {
158			if ((cp = strchr(digits, c)) != NULL) {
159				n = (cp - digits) * 100;
160				if ((c = *src++) == 0 ||
161				    (cp = strchr(digits, c)) == NULL) {
162					errno = EMSGSIZE;
163					return (-1);
164				}
165				n += (cp - digits) * 10;
166				if ((c = *src++) == 0 ||
167				    (cp = strchr(digits, c)) == NULL) {
168					errno = EMSGSIZE;
169					return (-1);
170				}
171				n += (cp - digits);
172				if (n > 255) {
173					errno = EMSGSIZE;
174					return (-1);
175				}
176				c = n;
177			}
178			escaped = 0;
179		} else if (c == '\\') {
180			escaped = 1;
181			continue;
182		} else if (c == '.') {
183			c = (bp - label - 1);
184			if ((c & NS_CMPRSFLGS) != 0) {	/* Label too big. */
185				errno = EMSGSIZE;
186				return (-1);
187			}
188			if (label >= eom) {
189				errno = EMSGSIZE;
190				return (-1);
191			}
192			*label = c;
193			/* Fully qualified ? */
194			if (*src == '\0') {
195				if (c != 0) {
196					if (bp >= eom) {
197						errno = EMSGSIZE;
198						return (-1);
199					}
200					*bp++ = '\0';
201				}
202				if ((bp - dst) > MAXCDNAME) {
203					errno = EMSGSIZE;
204					return (-1);
205				}
206				return (1);
207			}
208			if (c == 0 || *src == '.') {
209				errno = EMSGSIZE;
210				return (-1);
211			}
212			label = bp++;
213			continue;
214		}
215		if (bp >= eom) {
216			errno = EMSGSIZE;
217			return (-1);
218		}
219		*bp++ = (u_char)c;
220	}
221	c = (bp - label - 1);
222	if ((c & NS_CMPRSFLGS) != 0) {		/* Label too big. */
223		errno = EMSGSIZE;
224		return (-1);
225	}
226	if (label >= eom) {
227		errno = EMSGSIZE;
228		return (-1);
229	}
230	*label = c;
231	if (c != 0) {
232		if (bp >= eom) {
233			errno = EMSGSIZE;
234			return (-1);
235		}
236		*bp++ = 0;
237	}
238	if ((bp - dst) > MAXCDNAME) {	/* src too big */
239		errno = EMSGSIZE;
240		return (-1);
241	}
242	return (0);
243}
244
245#ifdef notdef
246/*
247 * MRns_name_ntol(src, dst, dstsiz)
248 *	Convert a network strings labels into all lowercase.
249 * return:
250 *	Number of bytes written to buffer, or -1 (with errno set)
251 * notes:
252 *	Enforces label and domain length limits.
253 */
254
255int
256MRns_name_ntol(const u_char *src, u_char *dst, size_t dstsiz) {
257	const u_char *cp;
258	u_char *dn, *eom;
259	u_char c;
260	u_int n;
261
262	cp = src;
263	dn = dst;
264	eom = dst + dstsiz;
265
266	if (dn >= eom) {
267		errno = EMSGSIZE;
268		return (-1);
269	}
270	while ((n = *cp++) != 0) {
271		if ((n & NS_CMPRSFLGS) != 0) {
272			/* Some kind of compression pointer. */
273			errno = EMSGSIZE;
274			return (-1);
275		}
276		*dn++ = n;
277		if (dn + n >= eom) {
278			errno = EMSGSIZE;
279			return (-1);
280		}
281		for ((void)NULL; n > 0; n--) {
282			c = *cp++;
283			if (isupper(c))
284				*dn++ = tolower(c);
285			else
286				*dn++ = c;
287		}
288	}
289	*dn++ = '\0';
290	return (dn - dst);
291}
292#endif
293
294/*
295 * MRns_name_unpack(msg, eom, src, dst, dstsiz)
296 *	Unpack a domain name from a message, source may be compressed.
297 * return:
298 *	-1 if it fails, or consumed octets if it succeeds.
299 */
300int
301MRns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
302	         u_char *dst, size_t dstsiz)
303{
304	const u_char *srcp, *dstlim;
305	u_char *dstp;
306	unsigned n;
307	int len;
308	int checked;
309
310	len = -1;
311	checked = 0;
312	dstp = dst;
313	srcp = src;
314	dstlim = dst + dstsiz;
315	if (srcp < msg || srcp >= eom) {
316		errno = EMSGSIZE;
317		return (-1);
318	}
319	/* Fetch next label in domain name. */
320	while ((n = *srcp++) != 0) {
321		/* Check for indirection. */
322		switch (n & NS_CMPRSFLGS) {
323		case 0:
324			/* Limit checks. */
325			if (dstp + n + 1 >= dstlim || srcp + n >= eom) {
326				errno = EMSGSIZE;
327				return (-1);
328			}
329			checked += n + 1;
330			*dstp++ = n;
331			memcpy(dstp, srcp, n);
332			dstp += n;
333			srcp += n;
334			break;
335
336		case NS_CMPRSFLGS:
337			if (srcp >= eom) {
338				errno = EMSGSIZE;
339				return (-1);
340			}
341			if (len < 0)
342				len = srcp - src + 1;
343			n = ((n & 0x3f) << 8) | (*srcp & 0xff);
344			if (n >= eom - msg) {  /* Out of range. */
345				errno = EMSGSIZE;
346				return (-1);
347			}
348			srcp = msg + n;
349			checked += 2;
350			/*
351			 * Check for loops in the compressed name;
352			 * if we've looked at the whole message,
353			 * there must be a loop.
354			 */
355			if (checked >= eom - msg) {
356				errno = EMSGSIZE;
357				return (-1);
358			}
359			break;
360
361		default:
362			errno = EMSGSIZE;
363			return (-1);			/* flag error */
364		}
365	}
366	*dstp = '\0';
367	if (len < 0)
368		len = srcp - src;
369	return (len);
370}
371
372/*
373 * MRns_name_pack(src, dst, dstsiz, dnptrs, lastdnptr)
374 *	Pack domain name 'domain' into 'comp_dn'.
375 * return:
376 *	Size of the compressed name, or -1.
377 * notes:
378 *	'dnptrs' is an array of pointers to previous compressed names.
379 *	dnptrs[0] is a pointer to the beginning of the message. The array
380 *	ends with NULL.
381 *	'lastdnptr' is a pointer to the end of the array pointed to
382 *	by 'dnptrs'.
383 * Side effects:
384 *	The list of pointers in dnptrs is updated for labels inserted into
385 *	the message as we compress the name.  If 'dnptr' is NULL, we don't
386 *	try to compress names. If 'lastdnptr' is NULL, we don't update the
387 *	list.
388 */
389int
390MRns_name_pack(const u_char *src, u_char *dst, unsigned dstsiz,
391	       const u_char **dnptrs, const u_char **lastdnptr)
392{
393	u_char *dstp;
394	const u_char **cpp, **lpp, *eob, *msg;
395	const u_char *srcp;
396	unsigned n;
397	int l;
398
399	srcp = src;
400	dstp = dst;
401	eob = dstp + dstsiz;
402	lpp = cpp = NULL;
403	if (dnptrs != NULL) {
404		if ((msg = *dnptrs++) != NULL) {
405			for (cpp = dnptrs; *cpp != NULL; cpp++)
406				(void)NULL;
407			lpp = cpp;	/* end of list to search */
408		}
409	} else
410		msg = NULL;
411
412	/* make sure the domain we are about to add is legal */
413	l = 0;
414	do {
415		n = *srcp;
416		if ((n & NS_CMPRSFLGS) != 0) {
417			errno = EMSGSIZE;
418			return (-1);
419		}
420		l += n + 1;
421		if (l > MAXCDNAME) {
422			errno = EMSGSIZE;
423			return (-1);
424		}
425		srcp += n + 1;
426	} while (n != 0);
427
428	/* from here on we need to reset compression pointer array on error */
429	srcp = src;
430	do {
431		/* Look to see if we can use pointers. */
432		n = *srcp;
433		if (n != 0 && msg != NULL) {
434			l = dn_find(srcp, msg, (const u_char * const *)dnptrs,
435				    (const u_char * const *)lpp);
436			if (l >= 0) {
437				if (dstp + 1 >= eob) {
438					goto cleanup;
439				}
440				*dstp++ = (l >> 8) | NS_CMPRSFLGS;
441				*dstp++ = l % 256;
442				return (dstp - dst);
443			}
444			/* Not found, save it. */
445			if (lastdnptr != NULL && cpp < lastdnptr - 1 &&
446			    (dstp - msg) < 0x4000) {
447				*cpp++ = dstp;
448				*cpp = NULL;
449			}
450		}
451		/* copy label to buffer */
452		if (n & NS_CMPRSFLGS) {		/* Should not happen. */
453			goto cleanup;
454		}
455		if (dstp + 1 + n >= eob) {
456			goto cleanup;
457		}
458		memcpy(dstp, srcp, n + 1);
459		srcp += n + 1;
460		dstp += n + 1;
461	} while (n != 0);
462
463	if (dstp > eob) {
464cleanup:
465		if (msg != NULL)
466			*lpp = NULL;
467		errno = EMSGSIZE;
468		return (-1);
469	}
470	return (dstp - dst);
471}
472
473/*
474 * MRns_name_uncompress(msg, eom, src, dst, dstsiz)
475 *	Expand compressed domain name to presentation format.
476 * return:
477 *	Number of bytes read out of `src', or -1 (with errno set).
478 * note:
479 *	Root domain returns as "." not "".
480 */
481static int
482MRns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src,
483		     char *dst, size_t dstsiz)
484{
485	u_char tmp[NS_MAXCDNAME];
486	int n;
487
488	if ((n = MRns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1)
489		return (-1);
490	if (MRns_name_ntop(tmp, dst, dstsiz) == -1)
491		return (-1);
492	return (n);
493}
494
495/*
496 * MRns_name_compress(src, dst, dstsiz, dnptrs, lastdnptr)
497 *	Compress a domain name into wire format, using compression pointers.
498 * return:
499 *	Number of bytes consumed in `dst' or -1 (with errno set).
500 * notes:
501 *	'dnptrs' is an array of pointers to previous compressed names.
502 *	dnptrs[0] is a pointer to the beginning of the message.
503 *	The list ends with NULL.  'lastdnptr' is a pointer to the end of the
504 *	array pointed to by 'dnptrs'. Side effect is to update the list of
505 *	pointers for labels inserted into the message as we compress the name.
506 *	If 'dnptr' is NULL, we don't try to compress names. If 'lastdnptr'
507 *	is NULL, we don't update the list.
508 */
509int
510MRns_name_compress(const char *src, u_char *dst, size_t dstsiz,
511		 const u_char **dnptrs, const u_char **lastdnptr)
512{
513	u_char tmp[NS_MAXCDNAME];
514
515	if (MRns_name_pton(src, tmp, sizeof tmp) == -1)
516		return (-1);
517	return (MRns_name_pack(tmp, dst, dstsiz, dnptrs, lastdnptr));
518}
519
520#ifdef notdef
521/*
522 * MRns_name_skip(ptrptr, eom)
523 *	Advance *ptrptr to skip over the compressed name it points at.
524 * return:
525 *	0 on success, -1 (with errno set) on failure.
526 */
527int
528MRns_name_skip(const u_char **ptrptr, const u_char *eom) {
529	const u_char *cp;
530	u_int n;
531
532	cp = *ptrptr;
533	while (cp < eom && (n = *cp++) != 0) {
534		/* Check for indirection. */
535		switch (n & NS_CMPRSFLGS) {
536		case 0:			/* normal case, n == len */
537			cp += n;
538			continue;
539		case NS_CMPRSFLGS:	/* indirection */
540			cp++;
541			break;
542		default:		/* illegal type */
543			errno = EMSGSIZE;
544			return (-1);
545		}
546		break;
547	}
548	if (cp > eom) {
549		errno = EMSGSIZE;
550		return (-1);
551	}
552	*ptrptr = cp;
553	return (0);
554}
555#endif
556
557/* Private. */
558
559/*
560 * special(ch)
561 *	Thinking in noninternationalized USASCII (per the DNS spec),
562 *	is this characted special ("in need of quoting") ?
563 * return:
564 *	boolean.
565 */
566static int
567special(int ch) {
568	switch (ch) {
569	case 0x22: /* '"' */
570	case 0x2E: /* '.' */
571	case 0x3B: /* ';' */
572	case 0x5C: /* '\\' */
573	/* Special modifiers in zone files. */
574	case 0x40: /* '@' */
575	case 0x24: /* '$' */
576		return (1);
577	default:
578		return (0);
579	}
580}
581
582/*
583 * printable(ch)
584 *	Thinking in noninternationalized USASCII (per the DNS spec),
585 *	is this character visible and not a space when printed ?
586 * return:
587 *	boolean.
588 */
589static int
590printable(int ch) {
591	return (ch > 0x20 && ch < 0x7f);
592}
593
594/*
595 *	Thinking in noninternationalized USASCII (per the DNS spec),
596 *	convert this character to lower case if it's upper case.
597 */
598static int
599mklower(int ch) {
600	if (ch >= 0x41 && ch <= 0x5A)
601		return (ch + 0x20);
602	return (ch);
603}
604
605/*
606 * dn_find(domain, msg, dnptrs, lastdnptr)
607 *	Search for the counted-label name in an array of compressed names.
608 * return:
609 *	offset from msg if found, or -1.
610 * notes:
611 *	dnptrs is the pointer to the first name on the list,
612 *	not the pointer to the start of the message.
613 */
614static int
615dn_find(const u_char *domain, const u_char *msg,
616	const u_char * const *dnptrs,
617	const u_char * const *lastdnptr)
618{
619	const u_char *dn, *cp, *sp;
620	const u_char * const *cpp;
621	u_int n;
622
623	for (cpp = dnptrs; cpp < lastdnptr; cpp++) {
624		dn = domain;
625		sp = cp = *cpp;
626		while ((n = *cp++) != 0) {
627			/*
628			 * check for indirection
629			 */
630			switch (n & NS_CMPRSFLGS) {
631			case 0:			/* normal case, n == len */
632				if (n != *dn++)
633					goto next;
634				for ((void)NULL; n > 0; n--)
635					if (mklower(*dn++) != mklower(*cp++))
636						goto next;
637				/* Is next root for both ? */
638				if (*dn == '\0' && *cp == '\0')
639					return (sp - msg);
640				if (*dn)
641					continue;
642				goto next;
643
644			case NS_CMPRSFLGS:	/* indirection */
645				cp = msg + (((n & 0x3f) << 8) | *cp);
646				break;
647
648			default:	/* illegal type */
649				errno = EMSGSIZE;
650				return (-1);
651			}
652		}
653 next: ;
654	}
655	errno = ENOENT;
656	return (-1);
657}
658
659/*!
660 * \brief Creates a string of comma-separated domain-names from a
661 * compressed list
662 *
663 * Produces a null-terminated string of comma-separated domain-names from
664 * a buffer containing a compressed list of domain-names. The names will
665 * be dotted and without enclosing quotes. For example:
666 * If a compressed list contains the follwoing two domain names:
667 *
668 *  a. one.two.com
669 *  b. three.four.com
670 *
671 * The compressed data will look like this:
672 *
673 *  03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68
674 *  72 65 65 04 66 6f 75 72 c0 08
675 *
676 * and will decompress into:
677 *
678 *  one.two.com,three.four.com
679 *
680 * \param  buf - buffer containing the compressed list of domain-names
681 * \param  buflen - length of compressed list of domain-names
682 * \param  dst_buf - buffer to receive the decompressed list
683 * \param  dst_size - size of the destination buffer
684 *
685 * \return the length of the decompressed string when successful, -1 on
686 * error.
687 */
688int MRns_name_uncompress_list(const unsigned char* buf, int buflen,
689			      char* dst_buf, size_t dst_size)
690{
691	const unsigned char* src = buf;
692	char* dst = dst_buf;
693	int consumed = 1;
694	int dst_remaining = dst_size;
695	int added_len = 0;
696	int first_pass = 1;
697
698	if (!buf || buflen == 0 || *buf == 0x00) {
699		/* nothing to do */
700		*dst = 0;
701		return (0);
702	}
703
704	while ((consumed > 0) && (src < (buf + buflen)))
705	{
706		if (dst_remaining <= 0) {
707			errno = EMSGSIZE;
708			return (-1);
709		}
710
711		if (!first_pass) {
712			*dst++ = ',';
713			*dst = '\0';
714			dst_remaining--;
715		}
716
717		consumed = MRns_name_uncompress(buf, buf + buflen, src,
718						dst, dst_remaining);
719		if (consumed < 0) {
720			return (-1);
721		}
722
723		src += consumed;
724		added_len = strlen(dst);
725		dst_remaining -= added_len;
726		dst += added_len;
727		first_pass = 0;
728	}
729	*dst='\0';
730
731	/* return the length of the uncompressed list string */
732	return (strlen(dst_buf));
733}
734
735/*!
736 * \brief Creates a compressed list from a string of comma-separated
737 * domain-names
738 *
739 * Produces a buffer containing a compressed data version of a list of
740 * domain-names extracted from a comma-separated string. Given a string
741 * containing:
742 *
743 *  one.two.com,three.four.com
744 *
745 * It will compress this into:
746 *
747 *  03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68
748 *  72 65 65 04 66 6f 75 72 c0 08
749 *
750 * \param  buf - buffer containing the uncompressed string of domain-names
751 * \param  buflen - length of uncompressed string of domain-names
752 * \param  compbuf - buffer to receive the compressed list
753 * \param  compbuf_size - size of the compression buffer
754 *
755 * \return the length of the compressed data when successful, -1 on error.
756 */
757int MRns_name_compress_list(const char* buf, int buflen,
758	unsigned char* compbuf, size_t compbuf_size)
759{
760	char cur_name[NS_MAXCDNAME];
761	const unsigned char *dnptrs[256], **lastdnptr;
762	const char* src;
763	const char* src_end;
764	unsigned clen = 0;
765	int result = 0;
766
767	memset(compbuf, 0, compbuf_size);
768	memset(dnptrs, 0, sizeof(dnptrs));
769	dnptrs[0] = compbuf;
770	lastdnptr = &dnptrs[255];
771
772	src = buf;
773	src_end = buf + buflen;
774	while (src < src_end) {
775		char *comma = strchr(src, ',');
776		int copylen = ((comma != NULL) ? comma - src : strlen(src));
777		if (copylen > (sizeof(cur_name) - 1)) {
778			errno = EMSGSIZE;
779			return (-1);
780		}
781
782		memcpy(cur_name, src, copylen);
783		cur_name[copylen] = '\0';
784		src += copylen + 1;
785
786		result = MRns_name_compress(cur_name, compbuf + clen,
787					    compbuf_size - clen,
788					    dnptrs, lastdnptr);
789
790		if (result < 0) {
791			return (-1);
792		}
793
794		clen += result;
795	}
796
797	/* return size of compressed list */
798	return(clen);
799}
800