ns_name.c revision 1.1 1 /* $NetBSD: ns_name.c,v 1.1 2018/04/07 22:34:26 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.1 2018/04/07 22:34:26 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 /*
246 * MRns_name_ntol(src, dst, dstsiz)
247 * Convert a network strings labels into all lowercase.
248 * return:
249 * Number of bytes written to buffer, or -1 (with errno set)
250 * notes:
251 * Enforces label and domain length limits.
252 */
253
254 int
255 MRns_name_ntol(const u_char *src, u_char *dst, size_t dstsiz) {
256 const u_char *cp;
257 u_char *dn, *eom;
258 u_char c;
259 u_int n;
260
261 cp = src;
262 dn = dst;
263 eom = dst + dstsiz;
264
265 if (dn >= eom) {
266 errno = EMSGSIZE;
267 return (-1);
268 }
269 while ((n = *cp++) != 0) {
270 if ((n & NS_CMPRSFLGS) != 0) {
271 /* Some kind of compression pointer. */
272 errno = EMSGSIZE;
273 return (-1);
274 }
275 *dn++ = n;
276 if (dn + n >= eom) {
277 errno = EMSGSIZE;
278 return (-1);
279 }
280 for ((void)NULL; n > 0; n--) {
281 c = *cp++;
282 if (isupper(c))
283 *dn++ = tolower(c);
284 else
285 *dn++ = c;
286 }
287 }
288 *dn++ = '\0';
289 return (dn - dst);
290 }
291
292 /*
293 * MRns_name_unpack(msg, eom, src, dst, dstsiz)
294 * Unpack a domain name from a message, source may be compressed.
295 * return:
296 * -1 if it fails, or consumed octets if it succeeds.
297 */
298 int
299 MRns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
300 u_char *dst, size_t dstsiz)
301 {
302 const u_char *srcp, *dstlim;
303 u_char *dstp;
304 unsigned n;
305 int len;
306 int checked;
307
308 len = -1;
309 checked = 0;
310 dstp = dst;
311 srcp = src;
312 dstlim = dst + dstsiz;
313 if (srcp < msg || srcp >= eom) {
314 errno = EMSGSIZE;
315 return (-1);
316 }
317 /* Fetch next label in domain name. */
318 while ((n = *srcp++) != 0) {
319 /* Check for indirection. */
320 switch (n & NS_CMPRSFLGS) {
321 case 0:
322 /* Limit checks. */
323 if (dstp + n + 1 >= dstlim || srcp + n >= eom) {
324 errno = EMSGSIZE;
325 return (-1);
326 }
327 checked += n + 1;
328 *dstp++ = n;
329 memcpy(dstp, srcp, n);
330 dstp += n;
331 srcp += n;
332 break;
333
334 case NS_CMPRSFLGS:
335 if (srcp >= eom) {
336 errno = EMSGSIZE;
337 return (-1);
338 }
339 if (len < 0)
340 len = srcp - src + 1;
341 srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff));
342 if (srcp < msg || srcp >= eom) { /* Out of range. */
343 errno = EMSGSIZE;
344 return (-1);
345 }
346 checked += 2;
347 /*
348 * Check for loops in the compressed name;
349 * if we've looked at the whole message,
350 * there must be a loop.
351 */
352 if (checked >= eom - msg) {
353 errno = EMSGSIZE;
354 return (-1);
355 }
356 break;
357
358 default:
359 errno = EMSGSIZE;
360 return (-1); /* flag error */
361 }
362 }
363 *dstp = '\0';
364 if (len < 0)
365 len = srcp - src;
366 return (len);
367 }
368
369 /*
370 * MRns_name_pack(src, dst, dstsiz, dnptrs, lastdnptr)
371 * Pack domain name 'domain' into 'comp_dn'.
372 * return:
373 * Size of the compressed name, or -1.
374 * notes:
375 * 'dnptrs' is an array of pointers to previous compressed names.
376 * dnptrs[0] is a pointer to the beginning of the message. The array
377 * ends with NULL.
378 * 'lastdnptr' is a pointer to the end of the array pointed to
379 * by 'dnptrs'.
380 * Side effects:
381 * The list of pointers in dnptrs is updated for labels inserted into
382 * the message as we compress the name. If 'dnptr' is NULL, we don't
383 * try to compress names. If 'lastdnptr' is NULL, we don't update the
384 * list.
385 */
386 int
387 MRns_name_pack(const u_char *src, u_char *dst, unsigned dstsiz,
388 const u_char **dnptrs, const u_char **lastdnptr)
389 {
390 u_char *dstp;
391 const u_char **cpp, **lpp, *eob, *msg;
392 const u_char *srcp;
393 unsigned n;
394 int l;
395
396 srcp = src;
397 dstp = dst;
398 eob = dstp + dstsiz;
399 lpp = cpp = NULL;
400 if (dnptrs != NULL) {
401 if ((msg = *dnptrs++) != NULL) {
402 for (cpp = dnptrs; *cpp != NULL; cpp++)
403 (void)NULL;
404 lpp = cpp; /* end of list to search */
405 }
406 } else
407 msg = NULL;
408
409 /* make sure the domain we are about to add is legal */
410 l = 0;
411 do {
412 n = *srcp;
413 if ((n & NS_CMPRSFLGS) != 0) {
414 errno = EMSGSIZE;
415 return (-1);
416 }
417 l += n + 1;
418 if (l > MAXCDNAME) {
419 errno = EMSGSIZE;
420 return (-1);
421 }
422 srcp += n + 1;
423 } while (n != 0);
424
425 /* from here on we need to reset compression pointer array on error */
426 srcp = src;
427 do {
428 /* Look to see if we can use pointers. */
429 n = *srcp;
430 if (n != 0 && msg != NULL) {
431 l = dn_find(srcp, msg, (const u_char * const *)dnptrs,
432 (const u_char * const *)lpp);
433 if (l >= 0) {
434 if (dstp + 1 >= eob) {
435 goto cleanup;
436 }
437 *dstp++ = (l >> 8) | NS_CMPRSFLGS;
438 *dstp++ = l % 256;
439 return (dstp - dst);
440 }
441 /* Not found, save it. */
442 if (lastdnptr != NULL && cpp < lastdnptr - 1 &&
443 (dstp - msg) < 0x4000) {
444 *cpp++ = dstp;
445 *cpp = NULL;
446 }
447 }
448 /* copy label to buffer */
449 if (n & NS_CMPRSFLGS) { /* Should not happen. */
450 goto cleanup;
451 }
452 if (dstp + 1 + n >= eob) {
453 goto cleanup;
454 }
455 memcpy(dstp, srcp, n + 1);
456 srcp += n + 1;
457 dstp += n + 1;
458 } while (n != 0);
459
460 if (dstp > eob) {
461 cleanup:
462 if (msg != NULL)
463 *lpp = NULL;
464 errno = EMSGSIZE;
465 return (-1);
466 }
467 return (dstp - dst);
468 }
469
470 /*
471 * MRns_name_uncompress(msg, eom, src, dst, dstsiz)
472 * Expand compressed domain name to presentation format.
473 * return:
474 * Number of bytes read out of `src', or -1 (with errno set).
475 * note:
476 * Root domain returns as "." not "".
477 */
478 int
479 MRns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src,
480 char *dst, size_t dstsiz)
481 {
482 u_char tmp[NS_MAXCDNAME];
483 int n;
484
485 if ((n = MRns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1)
486 return (-1);
487 if (MRns_name_ntop(tmp, dst, dstsiz) == -1)
488 return (-1);
489 return (n);
490 }
491
492 /*
493 * MRns_name_compress(src, dst, dstsiz, dnptrs, lastdnptr)
494 * Compress a domain name into wire format, using compression pointers.
495 * return:
496 * Number of bytes consumed in `dst' or -1 (with errno set).
497 * notes:
498 * 'dnptrs' is an array of pointers to previous compressed names.
499 * dnptrs[0] is a pointer to the beginning of the message.
500 * The list ends with NULL. 'lastdnptr' is a pointer to the end of the
501 * array pointed to by 'dnptrs'. Side effect is to update the list of
502 * pointers for labels inserted into the message as we compress the name.
503 * If 'dnptr' is NULL, we don't try to compress names. If 'lastdnptr'
504 * is NULL, we don't update the list.
505 */
506 int
507 MRns_name_compress(const char *src, u_char *dst, size_t dstsiz,
508 const u_char **dnptrs, const u_char **lastdnptr)
509 {
510 u_char tmp[NS_MAXCDNAME];
511
512 if (MRns_name_pton(src, tmp, sizeof tmp) == -1)
513 return (-1);
514 return (MRns_name_pack(tmp, dst, dstsiz, dnptrs, lastdnptr));
515 }
516
517 /*
518 * MRns_name_skip(ptrptr, eom)
519 * Advance *ptrptr to skip over the compressed name it points at.
520 * return:
521 * 0 on success, -1 (with errno set) on failure.
522 */
523 int
524 MRns_name_skip(const u_char **ptrptr, const u_char *eom) {
525 const u_char *cp;
526 u_int n;
527
528 cp = *ptrptr;
529 while (cp < eom && (n = *cp++) != 0) {
530 /* Check for indirection. */
531 switch (n & NS_CMPRSFLGS) {
532 case 0: /* normal case, n == len */
533 cp += n;
534 continue;
535 case NS_CMPRSFLGS: /* indirection */
536 cp++;
537 break;
538 default: /* illegal type */
539 errno = EMSGSIZE;
540 return (-1);
541 }
542 break;
543 }
544 if (cp > eom) {
545 errno = EMSGSIZE;
546 return (-1);
547 }
548 *ptrptr = cp;
549 return (0);
550 }
551
552 /* Private. */
553
554 /*
555 * special(ch)
556 * Thinking in noninternationalized USASCII (per the DNS spec),
557 * is this characted special ("in need of quoting") ?
558 * return:
559 * boolean.
560 */
561 static int
562 special(int ch) {
563 switch (ch) {
564 case 0x22: /* '"' */
565 case 0x2E: /* '.' */
566 case 0x3B: /* ';' */
567 case 0x5C: /* '\\' */
568 /* Special modifiers in zone files. */
569 case 0x40: /* '@' */
570 case 0x24: /* '$' */
571 return (1);
572 default:
573 return (0);
574 }
575 }
576
577 /*
578 * printable(ch)
579 * Thinking in noninternationalized USASCII (per the DNS spec),
580 * is this character visible and not a space when printed ?
581 * return:
582 * boolean.
583 */
584 static int
585 printable(int ch) {
586 return (ch > 0x20 && ch < 0x7f);
587 }
588
589 /*
590 * Thinking in noninternationalized USASCII (per the DNS spec),
591 * convert this character to lower case if it's upper case.
592 */
593 static int
594 mklower(int ch) {
595 if (ch >= 0x41 && ch <= 0x5A)
596 return (ch + 0x20);
597 return (ch);
598 }
599
600 /*
601 * dn_find(domain, msg, dnptrs, lastdnptr)
602 * Search for the counted-label name in an array of compressed names.
603 * return:
604 * offset from msg if found, or -1.
605 * notes:
606 * dnptrs is the pointer to the first name on the list,
607 * not the pointer to the start of the message.
608 */
609 static int
610 dn_find(const u_char *domain, const u_char *msg,
611 const u_char * const *dnptrs,
612 const u_char * const *lastdnptr)
613 {
614 const u_char *dn, *cp, *sp;
615 const u_char * const *cpp;
616 u_int n;
617
618 for (cpp = dnptrs; cpp < lastdnptr; cpp++) {
619 dn = domain;
620 sp = cp = *cpp;
621 while ((n = *cp++) != 0) {
622 /*
623 * check for indirection
624 */
625 switch (n & NS_CMPRSFLGS) {
626 case 0: /* normal case, n == len */
627 if (n != *dn++)
628 goto next;
629 for ((void)NULL; n > 0; n--)
630 if (mklower(*dn++) != mklower(*cp++))
631 goto next;
632 /* Is next root for both ? */
633 if (*dn == '\0' && *cp == '\0')
634 return (sp - msg);
635 if (*dn)
636 continue;
637 goto next;
638
639 case NS_CMPRSFLGS: /* indirection */
640 cp = msg + (((n & 0x3f) << 8) | *cp);
641 break;
642
643 default: /* illegal type */
644 errno = EMSGSIZE;
645 return (-1);
646 }
647 }
648 next: ;
649 }
650 errno = ENOENT;
651 return (-1);
652 }
653
654 /*!
655 * \brief Creates a string of comma-separated domain-names from a
656 * compressed list
657 *
658 * Produces a null-terminated string of comma-separated domain-names from
659 * a buffer containing a compressed list of domain-names. The names will
660 * be dotted and without enclosing quotes. For example:
661 * If a compressed list contains the follwoing two domain names:
662 *
663 * a. one.two.com
664 * b. three.four.com
665 *
666 * The compressed data will look like this:
667 *
668 * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68
669 * 72 65 65 04 66 6f 75 72 c0 08
670 *
671 * and will decompress into:
672 *
673 * one.two.com,three.four.com
674 *
675 * \param buf - buffer containing the compressed list of domain-names
676 * \param buflen - length of compressed list of domain-names
677 * \param dst_buf - buffer to receive the decompressed list
678 * \param dst_size - size of the destination buffer
679 *
680 * \return the length of the decompressed string when successful, -1 on
681 * error.
682 */
683 int MRns_name_uncompress_list(const unsigned char* buf, int buflen,
684 char* dst_buf, size_t dst_size)
685 {
686 const unsigned char* src = buf;
687 char* dst = dst_buf;
688 int consumed = 1;
689 int dst_remaining = dst_size;
690 int added_len = 0;
691 int first_pass = 1;
692
693 if (!buf || buflen == 0 || *buf == 0x00) {
694 /* nothing to do */
695 *dst = 0;
696 return (0);
697 }
698
699 while ((consumed > 0) && (src < (buf + buflen)))
700 {
701 if (dst_remaining <= 0) {
702 errno = EMSGSIZE;
703 return (-1);
704 }
705
706 if (!first_pass) {
707 *dst++ = ',';
708 *dst = '\0';
709 dst_remaining--;
710 }
711
712 consumed = MRns_name_uncompress(buf, buf + buflen, src,
713 dst, dst_remaining);
714 if (consumed < 0) {
715 return (-1);
716 }
717
718 src += consumed;
719 added_len = strlen(dst);
720 dst_remaining -= added_len;
721 dst += added_len;
722 first_pass = 0;
723 }
724 *dst='\0';
725
726 /* return the length of the uncompressed list string */
727 return (strlen(dst_buf));
728 }
729
730 /*!
731 * \brief Creates a compressed list from a string of comma-separated
732 * domain-names
733 *
734 * Produces a buffer containing a compressed data version of a list of
735 * domain-names extracted from a comma-separated string. Given a string
736 * containing:
737 *
738 * one.two.com,three.four.com
739 *
740 * It will compress this into:
741 *
742 * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68
743 * 72 65 65 04 66 6f 75 72 c0 08
744 *
745 * \param buf - buffer containing the uncompressed string of domain-names
746 * \param buflen - length of uncompressed string of domain-names
747 * \param compbuf - buffer to receive the compressed list
748 * \param compbuf_size - size of the compression buffer
749 *
750 * \return the length of the compressed data when successful, -1 on error.
751 */
752 int MRns_name_compress_list(const char* buf, int buflen,
753 unsigned char* compbuf, size_t compbuf_size)
754 {
755 char cur_name[NS_MAXCDNAME];
756 const unsigned char *dnptrs[256], **lastdnptr;
757 const char* src;
758 const char* src_end;
759 unsigned clen = 0;
760 int result = 0;
761
762 memset(compbuf, 0, compbuf_size);
763 memset(dnptrs, 0, sizeof(dnptrs));
764 dnptrs[0] = compbuf;
765 lastdnptr = &dnptrs[255];
766
767 src = buf;
768 src_end = buf + buflen;
769 while (src < src_end) {
770 char *comma = strchr(src, ',');
771 int copylen = ((comma != NULL) ? comma - src : strlen(src));
772 if (copylen > (sizeof(cur_name) - 1)) {
773 errno = EMSGSIZE;
774 return (-1);
775 }
776
777 memcpy(cur_name, src, copylen);
778 cur_name[copylen] = '\0';
779 src += copylen + 1;
780
781 result = MRns_name_compress(cur_name, compbuf + clen,
782 compbuf_size - clen,
783 dnptrs, lastdnptr);
784
785 if (result < 0) {
786 return (-1);
787 }
788
789 clen += result;
790 }
791
792 /* return size of compressed list */
793 return(clen);
794 }
795