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