/* $NetBSD: ns_name.c,v 1.2.6.1 2024/02/29 11:39:17 martin Exp $ */ /* * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1996-2003 by Internet Software Consortium * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Internet Systems Consortium, Inc. * PO Box 360 * Newmarket, NH 03857 USA * * http://www.isc.org/ */ #include __RCSID("$NetBSD: ns_name.c,v 1.2.6.1 2024/02/29 11:39:17 martin Exp $"); #include #include #include #include #include #include #include "ns_name.h" #include "arpa/nameser.h" /* Data. */ static const char digits[] = "0123456789"; /* Forward. */ static int special(int); static int printable(int); static int dn_find(const u_char *, const u_char *, const u_char * const *, const u_char * const *); /* Public. */ /* * MRns_name_len(eom, src) * Compute the length of encoded uncompressed domain name. * return: * -1 if it fails, or to be consumed octets if it succeeds. */ int MRns_name_len(const u_char *eom, const u_char *src) { const u_char *srcp; unsigned n; int len; len = -1; srcp = src; if (srcp >= eom) { errno = EMSGSIZE; return (-1); } /* Fetch next label in domain name. */ while ((n = *srcp++) != 0) { /* Limit checks. */ if (srcp + n >= eom) { errno = EMSGSIZE; return (-1); } srcp += n; } if (len < 0) len = srcp - src; return (len); } /* * MRns_name_ntop(src, dst, dstsiz) * Convert an encoded domain name to printable ascii as per RFC1035. * return: * Number of bytes written to buffer, or -1 (with errno set) * notes: * The root is returned as "." * All other domains are returned in non absolute form */ int MRns_name_ntop(const u_char *src, char *dst, size_t dstsiz) { const u_char *cp; char *dn, *eom; u_char c; u_int n; cp = src; dn = dst; eom = dst + dstsiz; while ((n = *cp++) != 0) { if ((n & NS_CMPRSFLGS) != 0) { /* Some kind of compression pointer. */ errno = EMSGSIZE; return (-1); } if (dn != dst) { if (dn >= eom) { errno = EMSGSIZE; return (-1); } *dn++ = '.'; } if (dn + n >= eom) { errno = EMSGSIZE; return (-1); } for ((void)NULL; n > 0; n--) { c = *cp++; if (special(c)) { if (dn + 1 >= eom) { errno = EMSGSIZE; return (-1); } *dn++ = '\\'; *dn++ = (char)c; } else if (!printable(c)) { if (dn + 3 >= eom) { errno = EMSGSIZE; return (-1); } *dn++ = '\\'; *dn++ = digits[c / 100]; *dn++ = digits[(c % 100) / 10]; *dn++ = digits[c % 10]; } else { if (dn >= eom) { errno = EMSGSIZE; return (-1); } *dn++ = (char)c; } } } if (dn == dst) { if (dn >= eom) { errno = EMSGSIZE; return (-1); } *dn++ = '.'; } if (dn >= eom) { errno = EMSGSIZE; return (-1); } *dn++ = '\0'; return (dn - dst); } /* * MRns_name_pton(src, dst, dstsiz) * Convert a ascii string into an encoded domain name as per RFC1035. * return: * -1 if it fails * 1 if string was fully qualified * 0 is string was not fully qualified * notes: * Enforces label and domain length limits. */ int MRns_name_pton(const char *src, u_char *dst, size_t dstsiz) { u_char *label, *bp, *eom; int c, n, escaped; char *cp; escaped = 0; bp = dst; eom = dst + dstsiz; label = bp++; while ((c = *src++) != 0) { if (escaped) { if ((cp = strchr(digits, c)) != NULL) { n = (cp - digits) * 100; if ((c = *src++) == 0 || (cp = strchr(digits, c)) == NULL) { errno = EMSGSIZE; return (-1); } n += (cp - digits) * 10; if ((c = *src++) == 0 || (cp = strchr(digits, c)) == NULL) { errno = EMSGSIZE; return (-1); } n += (cp - digits); if (n > 255) { errno = EMSGSIZE; return (-1); } c = n; } escaped = 0; } else if (c == '\\') { escaped = 1; continue; } else if (c == '.') { c = (bp - label - 1); if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */ errno = EMSGSIZE; return (-1); } if (label >= eom) { errno = EMSGSIZE; return (-1); } *label = c; /* Fully qualified ? */ if (*src == '\0') { if (c != 0) { if (bp >= eom) { errno = EMSGSIZE; return (-1); } *bp++ = '\0'; } if ((bp - dst) > MAXCDNAME) { errno = EMSGSIZE; return (-1); } return (1); } if (c == 0 || *src == '.') { errno = EMSGSIZE; return (-1); } label = bp++; continue; } if (bp >= eom) { errno = EMSGSIZE; return (-1); } *bp++ = (u_char)c; } c = (bp - label - 1); if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */ errno = EMSGSIZE; return (-1); } if (label >= eom) { errno = EMSGSIZE; return (-1); } *label = c; if (c != 0) { if (bp >= eom) { errno = EMSGSIZE; return (-1); } *bp++ = 0; } if ((bp - dst) > MAXCDNAME) { /* src too big */ errno = EMSGSIZE; return (-1); } return (0); } #ifdef notdef /* * MRns_name_ntol(src, dst, dstsiz) * Convert a network strings labels into all lowercase. * return: * Number of bytes written to buffer, or -1 (with errno set) * notes: * Enforces label and domain length limits. */ int MRns_name_ntol(const u_char *src, u_char *dst, size_t dstsiz) { const u_char *cp; u_char *dn, *eom; u_char c; u_int n; cp = src; dn = dst; eom = dst + dstsiz; if (dn >= eom) { errno = EMSGSIZE; return (-1); } while ((n = *cp++) != 0) { if ((n & NS_CMPRSFLGS) != 0) { /* Some kind of compression pointer. */ errno = EMSGSIZE; return (-1); } *dn++ = n; if (dn + n >= eom) { errno = EMSGSIZE; return (-1); } for ((void)NULL; n > 0; n--) { c = *cp++; if (isupper(c)) *dn++ = tolower(c); else *dn++ = c; } } *dn++ = '\0'; return (dn - dst); } #endif /* * MRns_name_unpack(msg, eom, src, dst, dstsiz) * Unpack a domain name from a message, source may be compressed. * return: * -1 if it fails, or consumed octets if it succeeds. */ int MRns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src, u_char *dst, size_t dstsiz) { const u_char *srcp, *dstlim; u_char *dstp; unsigned n; int len; int checked; len = -1; checked = 0; dstp = dst; srcp = src; dstlim = dst + dstsiz; if (srcp < msg || srcp >= eom) { errno = EMSGSIZE; return (-1); } /* Fetch next label in domain name. */ while ((n = *srcp++) != 0) { /* Check for indirection. */ switch (n & NS_CMPRSFLGS) { case 0: /* Limit checks. */ if (dstp + n + 1 >= dstlim || srcp + n >= eom) { errno = EMSGSIZE; return (-1); } checked += n + 1; *dstp++ = n; memcpy(dstp, srcp, n); dstp += n; srcp += n; break; case NS_CMPRSFLGS: if (srcp >= eom) { errno = EMSGSIZE; return (-1); } if (len < 0) len = srcp - src + 1; n = ((n & 0x3f) << 8) | (*srcp & 0xff); if (n >= eom - msg) { /* Out of range. */ errno = EMSGSIZE; return (-1); } srcp = msg + n; checked += 2; /* * Check for loops in the compressed name; * if we've looked at the whole message, * there must be a loop. */ if (checked >= eom - msg) { errno = EMSGSIZE; return (-1); } break; default: errno = EMSGSIZE; return (-1); /* flag error */ } } *dstp = '\0'; if (len < 0) len = srcp - src; return (len); } /* * MRns_name_pack(src, dst, dstsiz, dnptrs, lastdnptr) * Pack domain name 'domain' into 'comp_dn'. * return: * Size of the compressed name, or -1. * notes: * 'dnptrs' is an array of pointers to previous compressed names. * dnptrs[0] is a pointer to the beginning of the message. The array * ends with NULL. * 'lastdnptr' is a pointer to the end of the array pointed to * by 'dnptrs'. * Side effects: * The list of pointers in dnptrs is updated for labels inserted into * the message as we compress the name. If 'dnptr' is NULL, we don't * try to compress names. If 'lastdnptr' is NULL, we don't update the * list. */ int MRns_name_pack(const u_char *src, u_char *dst, unsigned dstsiz, const u_char **dnptrs, const u_char **lastdnptr) { u_char *dstp; const u_char **cpp, **lpp, *eob, *msg; const u_char *srcp; unsigned n; int l; srcp = src; dstp = dst; eob = dstp + dstsiz; lpp = cpp = NULL; if (dnptrs != NULL) { if ((msg = *dnptrs++) != NULL) { for (cpp = dnptrs; *cpp != NULL; cpp++) (void)NULL; lpp = cpp; /* end of list to search */ } } else msg = NULL; /* make sure the domain we are about to add is legal */ l = 0; do { n = *srcp; if ((n & NS_CMPRSFLGS) != 0) { errno = EMSGSIZE; return (-1); } l += n + 1; if (l > MAXCDNAME) { errno = EMSGSIZE; return (-1); } srcp += n + 1; } while (n != 0); /* from here on we need to reset compression pointer array on error */ srcp = src; do { /* Look to see if we can use pointers. */ n = *srcp; if (n != 0 && msg != NULL) { l = dn_find(srcp, msg, (const u_char * const *)dnptrs, (const u_char * const *)lpp); if (l >= 0) { if (dstp + 1 >= eob) { goto cleanup; } *dstp++ = (l >> 8) | NS_CMPRSFLGS; *dstp++ = l % 256; return (dstp - dst); } /* Not found, save it. */ if (lastdnptr != NULL && cpp < lastdnptr - 1 && (dstp - msg) < 0x4000) { *cpp++ = dstp; *cpp = NULL; } } /* copy label to buffer */ if (n & NS_CMPRSFLGS) { /* Should not happen. */ goto cleanup; } if (dstp + 1 + n >= eob) { goto cleanup; } memcpy(dstp, srcp, n + 1); srcp += n + 1; dstp += n + 1; } while (n != 0); if (dstp > eob) { cleanup: if (msg != NULL) *lpp = NULL; errno = EMSGSIZE; return (-1); } return (dstp - dst); } /* * MRns_name_uncompress(msg, eom, src, dst, dstsiz) * Expand compressed domain name to presentation format. * return: * Number of bytes read out of `src', or -1 (with errno set). * note: * Root domain returns as "." not "". */ static int MRns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src, char *dst, size_t dstsiz) { u_char tmp[NS_MAXCDNAME]; int n; if ((n = MRns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1) return (-1); if (MRns_name_ntop(tmp, dst, dstsiz) == -1) return (-1); return (n); } /* * MRns_name_compress(src, dst, dstsiz, dnptrs, lastdnptr) * Compress a domain name into wire format, using compression pointers. * return: * Number of bytes consumed in `dst' or -1 (with errno set). * notes: * 'dnptrs' is an array of pointers to previous compressed names. * dnptrs[0] is a pointer to the beginning of the message. * The list ends with NULL. 'lastdnptr' is a pointer to the end of the * array pointed to by 'dnptrs'. Side effect is to update the list of * pointers for labels inserted into the message as we compress the name. * If 'dnptr' is NULL, we don't try to compress names. If 'lastdnptr' * is NULL, we don't update the list. */ int MRns_name_compress(const char *src, u_char *dst, size_t dstsiz, const u_char **dnptrs, const u_char **lastdnptr) { u_char tmp[NS_MAXCDNAME]; if (MRns_name_pton(src, tmp, sizeof tmp) == -1) return (-1); return (MRns_name_pack(tmp, dst, dstsiz, dnptrs, lastdnptr)); } #ifdef notdef /* * MRns_name_skip(ptrptr, eom) * Advance *ptrptr to skip over the compressed name it points at. * return: * 0 on success, -1 (with errno set) on failure. */ int MRns_name_skip(const u_char **ptrptr, const u_char *eom) { const u_char *cp; u_int n; cp = *ptrptr; while (cp < eom && (n = *cp++) != 0) { /* Check for indirection. */ switch (n & NS_CMPRSFLGS) { case 0: /* normal case, n == len */ cp += n; continue; case NS_CMPRSFLGS: /* indirection */ cp++; break; default: /* illegal type */ errno = EMSGSIZE; return (-1); } break; } if (cp > eom) { errno = EMSGSIZE; return (-1); } *ptrptr = cp; return (0); } #endif /* Private. */ /* * special(ch) * Thinking in noninternationalized USASCII (per the DNS spec), * is this characted special ("in need of quoting") ? * return: * boolean. */ static int special(int ch) { switch (ch) { case 0x22: /* '"' */ case 0x2E: /* '.' */ case 0x3B: /* ';' */ case 0x5C: /* '\\' */ /* Special modifiers in zone files. */ case 0x40: /* '@' */ case 0x24: /* '$' */ return (1); default: return (0); } } /* * printable(ch) * Thinking in noninternationalized USASCII (per the DNS spec), * is this character visible and not a space when printed ? * return: * boolean. */ static int printable(int ch) { return (ch > 0x20 && ch < 0x7f); } /* * Thinking in noninternationalized USASCII (per the DNS spec), * convert this character to lower case if it's upper case. */ static int mklower(int ch) { if (ch >= 0x41 && ch <= 0x5A) return (ch + 0x20); return (ch); } /* * dn_find(domain, msg, dnptrs, lastdnptr) * Search for the counted-label name in an array of compressed names. * return: * offset from msg if found, or -1. * notes: * dnptrs is the pointer to the first name on the list, * not the pointer to the start of the message. */ static int dn_find(const u_char *domain, const u_char *msg, const u_char * const *dnptrs, const u_char * const *lastdnptr) { const u_char *dn, *cp, *sp; const u_char * const *cpp; u_int n; for (cpp = dnptrs; cpp < lastdnptr; cpp++) { dn = domain; sp = cp = *cpp; while ((n = *cp++) != 0) { /* * check for indirection */ switch (n & NS_CMPRSFLGS) { case 0: /* normal case, n == len */ if (n != *dn++) goto next; for ((void)NULL; n > 0; n--) if (mklower(*dn++) != mklower(*cp++)) goto next; /* Is next root for both ? */ if (*dn == '\0' && *cp == '\0') return (sp - msg); if (*dn) continue; goto next; case NS_CMPRSFLGS: /* indirection */ cp = msg + (((n & 0x3f) << 8) | *cp); break; default: /* illegal type */ errno = EMSGSIZE; return (-1); } } next: ; } errno = ENOENT; return (-1); } /*! * \brief Creates a string of comma-separated domain-names from a * compressed list * * Produces a null-terminated string of comma-separated domain-names from * a buffer containing a compressed list of domain-names. The names will * be dotted and without enclosing quotes. For example: * If a compressed list contains the follwoing two domain names: * * a. one.two.com * b. three.four.com * * The compressed data will look like this: * * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68 * 72 65 65 04 66 6f 75 72 c0 08 * * and will decompress into: * * one.two.com,three.four.com * * \param buf - buffer containing the compressed list of domain-names * \param buflen - length of compressed list of domain-names * \param dst_buf - buffer to receive the decompressed list * \param dst_size - size of the destination buffer * * \return the length of the decompressed string when successful, -1 on * error. */ int MRns_name_uncompress_list(const unsigned char* buf, int buflen, char* dst_buf, size_t dst_size) { const unsigned char* src = buf; char* dst = dst_buf; int consumed = 1; int dst_remaining = dst_size; int added_len = 0; int first_pass = 1; if (!buf || buflen == 0 || *buf == 0x00) { /* nothing to do */ *dst = 0; return (0); } while ((consumed > 0) && (src < (buf + buflen))) { if (dst_remaining <= 0) { errno = EMSGSIZE; return (-1); } if (!first_pass) { *dst++ = ','; *dst = '\0'; dst_remaining--; } consumed = MRns_name_uncompress(buf, buf + buflen, src, dst, dst_remaining); if (consumed < 0) { return (-1); } src += consumed; added_len = strlen(dst); dst_remaining -= added_len; dst += added_len; first_pass = 0; } *dst='\0'; /* return the length of the uncompressed list string */ return (strlen(dst_buf)); } /*! * \brief Creates a compressed list from a string of comma-separated * domain-names * * Produces a buffer containing a compressed data version of a list of * domain-names extracted from a comma-separated string. Given a string * containing: * * one.two.com,three.four.com * * It will compress this into: * * 03 6f 6e 65 03 74 77 6f 03 63 6f 6d 00 05 74 68 * 72 65 65 04 66 6f 75 72 c0 08 * * \param buf - buffer containing the uncompressed string of domain-names * \param buflen - length of uncompressed string of domain-names * \param compbuf - buffer to receive the compressed list * \param compbuf_size - size of the compression buffer * * \return the length of the compressed data when successful, -1 on error. */ int MRns_name_compress_list(const char* buf, int buflen, unsigned char* compbuf, size_t compbuf_size) { char cur_name[NS_MAXCDNAME]; const unsigned char *dnptrs[256], **lastdnptr; const char* src; const char* src_end; unsigned clen = 0; int result = 0; memset(compbuf, 0, compbuf_size); memset(dnptrs, 0, sizeof(dnptrs)); dnptrs[0] = compbuf; lastdnptr = &dnptrs[255]; src = buf; src_end = buf + buflen; while (src < src_end) { char *comma = strchr(src, ','); int copylen = ((comma != NULL) ? comma - src : strlen(src)); if (copylen > (sizeof(cur_name) - 1)) { errno = EMSGSIZE; return (-1); } memcpy(cur_name, src, copylen); cur_name[copylen] = '\0'; src += copylen + 1; result = MRns_name_compress(cur_name, compbuf + clen, compbuf_size - clen, dnptrs, lastdnptr); if (result < 0) { return (-1); } clen += result; } /* return size of compressed list */ return(clen); }