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 (at) 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
43 static const char digits[] = "0123456789";
44
45 /* Forward. */
46
47 static int special(int);
48 static int printable(int);
49 static 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 */
64 int
65 MRns_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
145 int
146 MRns_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
255 int
256 MRns_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 */
300 int
301 MRns_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 */
389 int
390 MRns_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) {
464 cleanup:
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 */
481 static int
482 MRns_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 */
509 int
510 MRns_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 */
527 int
528 MRns_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 */
566 static int
567 special(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 */
589 static int
590 printable(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 */
598 static int
599 mklower(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 */
614 static int
615 dn_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 */
688 int 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 */
757 int 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