%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ares_dns_name.c |
/* MIT License
*
* Copyright (c) 2023 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#include "ares.h"
#include "ares_private.h"
typedef struct {
char *name;
size_t name_len;
size_t idx;
} ares_nameoffset_t;
static void ares__nameoffset_free(void *arg)
{
ares_nameoffset_t *off = arg;
if (off == NULL) {
return;
}
ares_free(off->name);
ares_free(off);
}
static ares_status_t ares__nameoffset_create(ares__llist_t **list,
const char *name, size_t idx)
{
ares_status_t status;
ares_nameoffset_t *off = NULL;
if (list == NULL || name == NULL || ares_strlen(name) == 0 ||
ares_strlen(name) > 255) {
return ARES_EFORMERR;
}
if (*list == NULL) {
*list = ares__llist_create(ares__nameoffset_free);
}
if (*list == NULL) {
status = ARES_ENOMEM;
goto fail;
}
off = ares_malloc_zero(sizeof(*off));
if (off == NULL) {
return ARES_ENOMEM;
}
off->name = ares_strdup(name);
off->name_len = ares_strlen(off->name);
off->idx = idx;
if (ares__llist_insert_last(*list, off) == NULL) {
status = ARES_ENOMEM;
goto fail;
}
return ARES_SUCCESS;
fail:
ares__nameoffset_free(off);
return status;
}
static const ares_nameoffset_t *ares__nameoffset_find(ares__llist_t *list,
const char *name)
{
size_t name_len = ares_strlen(name);
ares__llist_node_t *node;
const ares_nameoffset_t *longest_match = NULL;
if (list == NULL || name == NULL || name_len == 0) {
return NULL;
}
for (node = ares__llist_node_first(list); node != NULL;
node = ares__llist_node_next(node)) {
const ares_nameoffset_t *val = ares__llist_node_val(node);
size_t prefix_len;
/* Can't be a match if the stored name is longer */
if (val->name_len > name_len) {
continue;
}
/* Can't be the longest match if our existing longest match is longer */
if (longest_match != NULL && longest_match->name_len > val->name_len) {
continue;
}
prefix_len = name_len - val->name_len;
if (strcasecmp(val->name, name + prefix_len) != 0) {
continue;
}
/* We need to make sure if `val->name` is "example.com" that name is
* is separated by a label, e.g. "myexample.com" is not ok, however
* "my.example.com" is, so we look for the preceding "." */
if (prefix_len != 0 && name[prefix_len - 1] != '.') {
continue;
}
longest_match = val;
}
return longest_match;
}
typedef struct {
ares__buf_t **label;
size_t num;
} ares_dns_labels_t;
static void ares_dns_labels_free(ares_dns_labels_t *labels)
{
size_t i;
if (labels == NULL) {
return;
}
for (i = 0; i < labels->num; i++) {
ares__buf_destroy(labels->label[i]);
labels->label[i] = NULL;
}
ares_free(labels->label);
labels->label = NULL;
labels->num = 0;
}
static ares__buf_t *ares_dns_labels_add(ares_dns_labels_t *labels)
{
void *temp;
if (labels == NULL) {
return NULL;
}
temp = ares_realloc_zero(labels->label, sizeof(*labels->label) * labels->num,
sizeof(*labels->label) * (labels->num + 1));
if (temp == NULL) {
return NULL;
}
labels->label = temp;
labels->label[labels->num] = ares__buf_create();
if (labels->label[labels->num] == NULL) {
return NULL;
}
labels->num++;
return labels->label[labels->num - 1];
}
static const ares__buf_t *
ares_dns_labels_get_last(const ares_dns_labels_t *labels)
{
if (labels == NULL || labels->num == 0) {
return NULL;
}
return labels->label[labels->num - 1];
}
static void ares_dns_name_labels_del_last(ares_dns_labels_t *labels)
{
if (labels == NULL || labels->num == 0) {
return;
}
ares__buf_destroy(labels->label[labels->num - 1]);
labels->label[labels->num - 1] = NULL;
labels->num--;
}
static ares_status_t ares_parse_dns_name_escape(ares__buf_t *namebuf,
ares__buf_t *label,
ares_bool_t validate_hostname)
{
ares_status_t status;
unsigned char c;
status = ares__buf_fetch_bytes(namebuf, &c, 1);
if (status != ARES_SUCCESS) {
return ARES_EBADNAME;
}
/* If next character is a digit, read 2 more digits */
if (isdigit(c)) {
size_t i;
unsigned int val = 0;
val = c - '0';
for (i = 0; i < 2; i++) {
status = ares__buf_fetch_bytes(namebuf, &c, 1);
if (status != ARES_SUCCESS) {
return ARES_EBADNAME;
}
if (!isdigit(c)) {
return ARES_EBADNAME;
}
val *= 10;
val += c - '0';
}
/* Out of range */
if (val > 255) {
return ARES_EBADNAME;
}
if (validate_hostname && !ares__is_hostnamech((unsigned char)val)) {
return ARES_EBADNAME;
}
return ares__buf_append_byte(label, (unsigned char)val);
}
/* We can just output the character */
if (validate_hostname && !ares__is_hostnamech(c)) {
return ARES_EBADNAME;
}
return ares__buf_append_byte(label, c);
}
static ares_status_t ares_split_dns_name(ares_dns_labels_t *labels,
ares_bool_t validate_hostname,
const char *name)
{
ares_status_t status;
ares__buf_t *label = NULL;
ares__buf_t *namebuf = NULL;
size_t i;
size_t total_len = 0;
unsigned char c;
if (name == NULL || labels == NULL) {
return ARES_EFORMERR;
}
/* Put name into a buffer for parsing */
namebuf = ares__buf_create();
if (namebuf == NULL) {
status = ARES_ENOMEM;
goto done;
}
if (*name != '\0') {
status =
ares__buf_append(namebuf, (const unsigned char *)name, ares_strlen(name));
if (status != ARES_SUCCESS) {
goto done;
}
}
/* Start with 1 label */
label = ares_dns_labels_add(labels);
if (label == NULL) {
status = ARES_ENOMEM;
goto done;
}
while (ares__buf_fetch_bytes(namebuf, &c, 1) == ARES_SUCCESS) {
/* New label */
if (c == '.') {
label = ares_dns_labels_add(labels);
if (label == NULL) {
status = ARES_ENOMEM;
goto done;
}
continue;
}
/* Escape */
if (c == '\\') {
status = ares_parse_dns_name_escape(namebuf, label, validate_hostname);
if (status != ARES_SUCCESS) {
goto done;
}
continue;
}
/* Output direct character */
if (validate_hostname && !ares__is_hostnamech(c)) {
status = ARES_EBADNAME;
goto done;
}
status = ares__buf_append_byte(label, c);
if (status != ARES_SUCCESS) {
goto done;
}
}
/* Remove trailing blank label */
if (ares__buf_len(ares_dns_labels_get_last(labels)) == 0) {
ares_dns_name_labels_del_last(labels);
}
/* If someone passed in "." there could have been 2 blank labels, check for
* that */
if (labels->num == 1 &&
ares__buf_len(ares_dns_labels_get_last(labels)) == 0) {
ares_dns_name_labels_del_last(labels);
}
/* Scan to make sure label lengths are valid */
for (i = 0; i < labels->num; i++) {
size_t len = ares__buf_len(labels->label[i]);
/* No 0-length labels, and no labels over 63 bytes */
if (len == 0 || len > 63) {
status = ARES_EBADNAME;
goto done;
}
total_len += len;
}
/* Can't exceed maximum (unescaped) length */
if (labels->num && total_len + labels->num - 1 > 255) {
status = ARES_EBADNAME;
goto done;
}
status = ARES_SUCCESS;
done:
ares__buf_destroy(namebuf);
if (status != ARES_SUCCESS) {
ares_dns_labels_free(labels);
}
return status;
}
ares_status_t ares__dns_name_write(ares__buf_t *buf, ares__llist_t **list,
ares_bool_t validate_hostname,
const char *name)
{
const ares_nameoffset_t *off = NULL;
size_t name_len;
size_t pos = ares__buf_len(buf);
ares_dns_labels_t labels;
char name_copy[512];
ares_status_t status;
if (buf == NULL || name == NULL) {
return ARES_EFORMERR;
}
memset(&labels, 0, sizeof(labels));
/* NOTE: due to possible escaping, name_copy buffer is > 256 to allow for
* this */
name_len = ares_strcpy(name_copy, name, sizeof(name_copy));
/* Find longest match */
if (list != NULL) {
off = ares__nameoffset_find(*list, name_copy);
if (off != NULL && off->name_len != name_len) {
/* truncate */
name_len -= (off->name_len + 1);
name_copy[name_len] = 0;
}
}
/* Output labels */
if (off == NULL || off->name_len != name_len) {
size_t i;
status = ares_split_dns_name(&labels, validate_hostname, name_copy);
if (status != ARES_SUCCESS) {
goto done;
}
for (i = 0; i < labels.num; i++) {
size_t len = 0;
const unsigned char *ptr = ares__buf_peek(labels.label[i], &len);
status = ares__buf_append_byte(buf, (unsigned char)(len & 0xFF));
if (status != ARES_SUCCESS) {
goto done;
}
status = ares__buf_append(buf, ptr, len);
if (status != ARES_SUCCESS) {
goto done;
}
}
/* If we are NOT jumping to another label, output terminator */
if (off == NULL) {
status = ares__buf_append_byte(buf, 0);
if (status != ARES_SUCCESS) {
goto done;
}
}
}
/* Output name compression offset jump */
if (off != NULL) {
unsigned short u16 =
(unsigned short)0xC000 | (unsigned short)(off->idx & 0x3FFF);
status = ares__buf_append_be16(buf, u16);
if (status != ARES_SUCCESS) {
goto done;
}
}
/* Store pointer for future jumps as long as its not an exact match for
* a prior entry */
if (list != NULL && (off == NULL || off->name_len != name_len) &&
name_len > 0) {
status = ares__nameoffset_create(list, name /* not truncated copy! */, pos);
if (status != ARES_SUCCESS) {
goto done;
}
}
status = ARES_SUCCESS;
done:
ares_dns_labels_free(&labels);
return status;
}
/* Reserved characters for names that need to be escaped */
static ares_bool_t is_reservedch(int ch)
{
switch (ch) {
case '"':
case '.':
case ';':
case '\\':
case '(':
case ')':
case '@':
case '$':
return ARES_TRUE;
default:
break;
}
return ARES_FALSE;
}
static ares_status_t ares__fetch_dnsname_into_buf(ares__buf_t *buf,
ares__buf_t *dest, size_t len,
ares_bool_t is_hostname)
{
size_t remaining_len;
const unsigned char *ptr = ares__buf_peek(buf, &remaining_len);
ares_status_t status;
size_t i;
if (buf == NULL || len == 0 || remaining_len < len) {
return ARES_EBADRESP;
}
for (i = 0; i < len; i++) {
unsigned char c = ptr[i];
/* Hostnames have a very specific allowed character set. Anything outside
* of that (non-printable and reserved included) are disallowed */
if (is_hostname && !ares__is_hostnamech(c)) {
status = ARES_EBADRESP;
goto fail;
}
/* NOTE: dest may be NULL if the user is trying to skip the name. validation
* still occurs above. */
if (dest == NULL) {
continue;
}
/* Non-printable characters need to be output as \DDD */
if (!ares__isprint(c)) {
unsigned char escape[4];
escape[0] = '\\';
escape[1] = '0' + (c / 100);
escape[2] = '0' + ((c % 100) / 10);
escape[3] = '0' + (c % 10);
status = ares__buf_append(dest, escape, sizeof(escape));
if (status != ARES_SUCCESS) {
goto fail;
}
continue;
}
/* Reserved characters need to be escaped, otherwise normal */
if (is_reservedch(c)) {
status = ares__buf_append_byte(dest, '\\');
if (status != ARES_SUCCESS) {
goto fail;
}
}
status = ares__buf_append_byte(dest, c);
if (status != ARES_SUCCESS) {
return status;
}
}
return ares__buf_consume(buf, len);
fail:
return status;
}
ares_status_t ares__dns_name_parse(ares__buf_t *buf, char **name,
ares_bool_t is_hostname)
{
size_t save_offset = 0;
unsigned char c;
ares_status_t status;
ares__buf_t *namebuf = NULL;
size_t label_start = ares__buf_get_position(buf);
if (buf == NULL) {
return ARES_EFORMERR;
}
if (name != NULL) {
namebuf = ares__buf_create();
if (namebuf == NULL) {
status = ARES_ENOMEM;
goto fail;
}
}
/* The compression scheme allows a domain name in a message to be
* represented as either:
*
* - a sequence of labels ending in a zero octet
* - a pointer
* - a sequence of labels ending with a pointer
*/
while (1) {
/* Keep track of the minimum label starting position to prevent forward
* jumping */
if (label_start > ares__buf_get_position(buf)) {
label_start = ares__buf_get_position(buf);
}
status = ares__buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
goto fail;
}
/* Pointer/Redirect */
if ((c & 0xc0) == 0xc0) {
/* The pointer takes the form of a two octet sequence:
*
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | 1 1| OFFSET |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*
* The first two bits are ones. This allows a pointer to be distinguished
* from a label, since the label must begin with two zero bits because
* labels are restricted to 63 octets or less. (The 10 and 01
* combinations are reserved for future use.) The OFFSET field specifies
* an offset from the start of the message (i.e., the first octet of the
* ID field in the domain header). A zero offset specifies the first byte
* of the ID field, etc.
*/
size_t offset = (size_t)((c & 0x3F) << 8);
/* Fetch second byte of the redirect length */
status = ares__buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
goto fail;
}
offset |= ((size_t)c);
/* According to RFC 1035 4.1.4:
* In this scheme, an entire domain name or a list of labels at
* the end of a domain name is replaced with a pointer to a prior
* occurrence of the same name.
* Note the word "prior", meaning it must go backwards. This was
* confirmed via the ISC BIND code that it also prevents forward
* pointers.
*/
if (offset >= label_start) {
status = ARES_EBADNAME;
goto fail;
}
/* First time we make a jump, save the current position */
if (save_offset == 0) {
save_offset = ares__buf_get_position(buf);
}
status = ares__buf_set_position(buf, offset);
if (status != ARES_SUCCESS) {
status = ARES_EBADNAME;
goto fail;
}
continue;
} else if ((c & 0xc0) != 0) {
/* 10 and 01 are reserved */
status = ARES_EBADNAME;
goto fail;
} else if (c == 0) {
/* termination via zero octet*/
break;
}
/* New label */
/* Labels are separated by periods */
if (ares__buf_len(namebuf) != 0 && name != NULL) {
status = ares__buf_append_byte(namebuf, '.');
if (status != ARES_SUCCESS) {
goto fail;
}
}
status = ares__fetch_dnsname_into_buf(buf, namebuf, c, is_hostname);
if (status != ARES_SUCCESS) {
goto fail;
}
}
/* Restore offset read after first redirect/pointer as this is where the DNS
* message continues */
if (save_offset) {
ares__buf_set_position(buf, save_offset);
}
if (name != NULL) {
*name = ares__buf_finish_str(namebuf, NULL);
if (*name == NULL) {
status = ARES_ENOMEM;
goto fail;
}
}
return ARES_SUCCESS;
fail:
/* We want badname response if we couldn't parse */
if (status == ARES_EBADRESP) {
status = ARES_EBADNAME;
}
ares__buf_destroy(namebuf);
return status;
}