/* $OpenBSD: asr_utils.c,v 1.22 2023/11/20 12:15:16 florian Exp $ */ /* * Copyright (c) 2009-2012 Eric Faurot * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asr_private.h" static int dname_check_label(const char *, size_t); static ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *, char *, size_t); static int unpack_data(struct asr_unpack *, void *, size_t); static int unpack_u16(struct asr_unpack *, uint16_t *); static int unpack_u32(struct asr_unpack *, uint32_t *); static int unpack_inaddr(struct asr_unpack *, struct in_addr *); static int unpack_in6addr(struct asr_unpack *, struct in6_addr *); static int unpack_dname(struct asr_unpack *, char *, size_t); static int pack_data(struct asr_pack *, const void *, size_t); static int pack_u16(struct asr_pack *, uint16_t); static int pack_dname(struct asr_pack *, const char *); static int dname_check_label(const char *s, size_t l) { if (l == 0 || l > 63) return (-1); return (0); } ssize_t _asr_dname_from_fqdn(const char *str, char *dst, size_t max) { ssize_t res; size_t l, n; char *d; res = 0; /* special case: the root domain */ if (str[0] == '.') { if (str[1] != '\0') return (-1); if (dst && max >= 1) *dst = '\0'; return (1); } for (; *str; str = d + 1) { d = strchr(str, '.'); if (d == NULL || d == str) return (-1); l = (d - str); if (dname_check_label(str, l) == -1) return (-1); res += l + 1; if (dst) { *dst++ = l; max -= 1; n = (l > max) ? max : l; memmove(dst, str, n); max -= n; if (max == 0) dst = NULL; else dst += n; } } if (dst) *dst++ = '\0'; return (res + 1); } static ssize_t dname_expand(const unsigned char *data, size_t len, size_t offset, size_t *newoffset, char *dst, size_t max) { size_t n, count, end, ptr, start; ssize_t res; if (offset >= len) return (-1); res = 0; end = start = offset; for (; (n = data[offset]); ) { if ((n & 0xc0) == 0xc0) { if (offset + 1 >= len) return (-1); ptr = 256 * (n & ~0xc0) + data[offset + 1]; if (ptr >= start) return (-1); if (end < offset + 2) end = offset + 2; offset = start = ptr; continue; } if (offset + n + 1 >= len) return (-1); if (dname_check_label(data + offset + 1, n) == -1) return (-1); /* copy n + at offset+1 */ if (dst != NULL && max != 0) { count = (max < n + 1) ? (max) : (n + 1); memmove(dst, data + offset, count); dst += count; max -= count; } res += n + 1; offset += n + 1; if (end < offset) end = offset; } if (end < offset + 1) end = offset + 1; if (dst != NULL && max != 0) dst[0] = 0; if (newoffset) *newoffset = end; return (res + 1); } void _asr_pack_init(struct asr_pack *pack, char *buf, size_t len) { pack->buf = buf; pack->len = len; pack->offset = 0; pack->err = 0; } void _asr_unpack_init(struct asr_unpack *unpack, const char *buf, size_t len) { unpack->buf = buf; unpack->len = len; unpack->offset = 0; unpack->err = 0; } static int unpack_data(struct asr_unpack *p, void *data, size_t len) { if (p->err) return (-1); if (p->len - p->offset < len) { p->err = EOVERFLOW; return (-1); } memmove(data, p->buf + p->offset, len); p->offset += len; return (0); } static int unpack_u16(struct asr_unpack *p, uint16_t *u16) { if (unpack_data(p, u16, 2) == -1) return (-1); *u16 = ntohs(*u16); return (0); } static int unpack_u32(struct asr_unpack *p, uint32_t *u32) { if (unpack_data(p, u32, 4) == -1) return (-1); *u32 = ntohl(*u32); return (0); } static int unpack_inaddr(struct asr_unpack *p, struct in_addr *a) { return (unpack_data(p, a, 4)); } static int unpack_in6addr(struct asr_unpack *p, struct in6_addr *a6) { return (unpack_data(p, a6, 16)); } static int unpack_dname(struct asr_unpack *p, char *dst, size_t max) { ssize_t e; if (p->err) return (-1); e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max); if (e == -1) { p->err = EINVAL; return (-1); } if (e < 0 || e > MAXDNAME) { p->err = ERANGE; return (-1); } return (0); } int _asr_unpack_header(struct asr_unpack *p, struct asr_dns_header *h) { if (unpack_data(p, h, HFIXEDSZ) == -1) return (-1); h->flags = ntohs(h->flags); h->qdcount = ntohs(h->qdcount); h->ancount = ntohs(h->ancount); h->nscount = ntohs(h->nscount); h->arcount = ntohs(h->arcount); return (0); } int _asr_unpack_query(struct asr_unpack *p, struct asr_dns_query *q) { unpack_dname(p, q->q_dname, sizeof(q->q_dname)); unpack_u16(p, &q->q_type); unpack_u16(p, &q->q_class); return (p->err) ? (-1) : (0); } int _asr_unpack_rr(struct asr_unpack *p, struct asr_dns_rr *rr) { uint16_t rdlen; size_t save_offset; unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname)); unpack_u16(p, &rr->rr_type); unpack_u16(p, &rr->rr_class); unpack_u32(p, &rr->rr_ttl); unpack_u16(p, &rdlen); if (p->err) return (-1); if (p->len - p->offset < rdlen) { p->err = EOVERFLOW; return (-1); } save_offset = p->offset; switch (rr->rr_type) { case T_CNAME: unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname)); break; case T_MX: unpack_u16(p, &rr->rr.mx.preference); unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange)); break; case T_NS: unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname)); break; case T_PTR: unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname)); break; case T_SOA: unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname)); unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname)); unpack_u32(p, &rr->rr.soa.serial); unpack_u32(p, &rr->rr.soa.refresh); unpack_u32(p, &rr->rr.soa.retry); unpack_u32(p, &rr->rr.soa.expire); unpack_u32(p, &rr->rr.soa.minimum); break; case T_A: if (rr->rr_class != C_IN) goto other; unpack_inaddr(p, &rr->rr.in_a.addr); break; case T_AAAA: if (rr->rr_class != C_IN) goto other; unpack_in6addr(p, &rr->rr.in_aaaa.addr6); break; default: other: rr->rr.other.rdata = p->buf + p->offset; rr->rr.other.rdlen = rdlen; p->offset += rdlen; } if (p->err) return (-1); /* make sure that the advertised rdlen is really ok */ if (p->offset - save_offset != rdlen) p->err = EINVAL; return (p->err) ? (-1) : (0); } static int pack_data(struct asr_pack *p, const void *data, size_t len) { if (p->err) return (-1); if (p->len < p->offset + len) { p->err = EOVERFLOW; return (-1); } memmove(p->buf + p->offset, data, len); p->offset += len; return (0); } static int pack_u16(struct asr_pack *p, uint16_t v) { v = htons(v); return (pack_data(p, &v, 2)); } static int pack_dname(struct asr_pack *p, const char *dname) { /* dname compression would be nice to have here. * need additional context. */ return (pack_data(p, dname, strlen(dname) + 1)); } int _asr_pack_header(struct asr_pack *p, const struct asr_dns_header *h) { struct asr_dns_header c; c.id = h->id; c.flags = htons(h->flags); c.qdcount = htons(h->qdcount); c.ancount = htons(h->ancount); c.nscount = htons(h->nscount); c.arcount = htons(h->arcount); return (pack_data(p, &c, HFIXEDSZ)); } int _asr_pack_query(struct asr_pack *p, uint16_t type, uint16_t class, const char *dname) { pack_dname(p, dname); pack_u16(p, type); pack_u16(p, class); return (p->err) ? (-1) : (0); } int _asr_pack_edns0(struct asr_pack *p, uint16_t pktsz, int dnssec_do) { DPRINT("asr EDNS0 pktsz:%hu dnssec:%s\n", pktsz, dnssec_do ? "yes" : "no"); pack_dname(p, ""); /* root */ pack_u16(p, T_OPT); /* OPT */ pack_u16(p, pktsz); /* UDP payload size */ /* extended RCODE and flags */ pack_u16(p, 0); pack_u16(p, dnssec_do ? DNS_MESSAGEEXTFLAG_DO : 0); pack_u16(p, 0); /* RDATA len */ return (p->err) ? (-1) : (0); } int _asr_sockaddr_from_str(struct sockaddr *sa, int family, const char *str) { struct in_addr ina; struct in6_addr in6a; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; char *cp, *str2; const char *errstr; switch (family) { case PF_UNSPEC: if (_asr_sockaddr_from_str(sa, PF_INET, str) == 0) return (0); return _asr_sockaddr_from_str(sa, PF_INET6, str); case PF_INET: if (inet_pton(PF_INET, str, &ina) != 1) return (-1); sin = (struct sockaddr_in *)sa; memset(sin, 0, sizeof *sin); sin->sin_len = sizeof(struct sockaddr_in); sin->sin_family = PF_INET; sin->sin_addr.s_addr = ina.s_addr; return (0); case PF_INET6: cp = strchr(str, SCOPE_DELIMITER); if (cp) { str2 = strdup(str); if (str2 == NULL) return (-1); str2[cp - str] = '\0'; if (inet_pton(PF_INET6, str2, &in6a) != 1) { free(str2); return (-1); } cp++; free(str2); } else if (inet_pton(PF_INET6, str, &in6a) != 1) return (-1); sin6 = (struct sockaddr_in6 *)sa; memset(sin6, 0, sizeof *sin6); sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = PF_INET6; sin6->sin6_addr = in6a; if (cp == NULL) return (0); if (IN6_IS_ADDR_LINKLOCAL(&in6a) || IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || IN6_IS_ADDR_MC_INTFACELOCAL(&in6a)) if ((sin6->sin6_scope_id = if_nametoindex(cp))) return (0); sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); if (errstr) return (-1); return (0); default: break; } return (-1); } ssize_t _asr_addr_as_fqdn(const char *addr, int family, char *dst, size_t max) { const struct in6_addr *in6_addr; in_addr_t in_addr; switch (family) { case AF_INET: in_addr = ntohl(*((const in_addr_t *)addr)); snprintf(dst, max, "%d.%d.%d.%d.in-addr.arpa.", in_addr & 0xff, (in_addr >> 8) & 0xff, (in_addr >> 16) & 0xff, (in_addr >> 24) & 0xff); break; case AF_INET6: in6_addr = (const struct in6_addr *)addr; snprintf(dst, max, "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "ip6.arpa.", in6_addr->s6_addr[15] & 0xf, (in6_addr->s6_addr[15] >> 4) & 0xf, in6_addr->s6_addr[14] & 0xf, (in6_addr->s6_addr[14] >> 4) & 0xf, in6_addr->s6_addr[13] & 0xf, (in6_addr->s6_addr[13] >> 4) & 0xf, in6_addr->s6_addr[12] & 0xf, (in6_addr->s6_addr[12] >> 4) & 0xf, in6_addr->s6_addr[11] & 0xf, (in6_addr->s6_addr[11] >> 4) & 0xf, in6_addr->s6_addr[10] & 0xf, (in6_addr->s6_addr[10] >> 4) & 0xf, in6_addr->s6_addr[9] & 0xf, (in6_addr->s6_addr[9] >> 4) & 0xf, in6_addr->s6_addr[8] & 0xf, (in6_addr->s6_addr[8] >> 4) & 0xf, in6_addr->s6_addr[7] & 0xf, (in6_addr->s6_addr[7] >> 4) & 0xf, in6_addr->s6_addr[6] & 0xf, (in6_addr->s6_addr[6] >> 4) & 0xf, in6_addr->s6_addr[5] & 0xf, (in6_addr->s6_addr[5] >> 4) & 0xf, in6_addr->s6_addr[4] & 0xf, (in6_addr->s6_addr[4] >> 4) & 0xf, in6_addr->s6_addr[3] & 0xf, (in6_addr->s6_addr[3] >> 4) & 0xf, in6_addr->s6_addr[2] & 0xf, (in6_addr->s6_addr[2] >> 4) & 0xf, in6_addr->s6_addr[1] & 0xf, (in6_addr->s6_addr[1] >> 4) & 0xf, in6_addr->s6_addr[0] & 0xf, (in6_addr->s6_addr[0] >> 4) & 0xf); break; default: return (-1); } return (0); } int hnok_lenient(const char *dn) { int pch = '\0', ch = *dn++; while (ch != '\0') { /* can't start with . or - */ if (pch == '\0' && (ch == '.' || ch == '-')) return 0; if (pch == '.' && ch == '.') return 0; if (!(isalpha((unsigned char)ch) || isdigit((unsigned char)ch) || ch == '.' || ch == '-' || ch == '_')) return 0; pch = ch; ch = *dn++; } return 1; } /* Check if the hostname is localhost or if it's in the localhost domain */ int _asr_is_localhost(const char *hn) { size_t hnlen, localhostlen; if (hn == NULL) return 0; if (strcasecmp(hn, "localhost") == 0 || strcasecmp(hn, "localhost.") == 0) return 1; hnlen = strlen(hn); localhostlen = strlen(".localhost"); if (hnlen < localhostlen) return 0; if (strcasecmp(hn + hnlen - localhostlen, ".localhost") == 0) return 1; localhostlen++; if (hnlen < localhostlen) return 0; if (strcasecmp(hn + hnlen - localhostlen, ".localhost.") == 0) return 1; return 0; }