DNS RR TXT strings should not be automatically concatenated (#801)

As per #738, there are usecases where the DNS TXT record strings should
not be concatenated like RFC 7208 indicates. We cannot break ABI with
those using the new API, so we need to support retrieving the
concatenated version as well as a new API to retrieve the individual
strings which will be used by `ares_parse_text_reply_ext()` to restore
the old behavior prior to c-ares 1.20.

Fixes Issue: #738
Fix By: Brad House (@bradh352)
pull/804/head
Brad House 5 months ago committed by GitHub
parent 64df444e6f
commit 378d26144d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      docs/Makefile.inc
  2. 7
      docs/ares_dns_mapping.3
  3. 78
      docs/ares_dns_rr.3
  4. 3
      docs/ares_dns_rr_add_abin.3
  5. 3
      docs/ares_dns_rr_del_abin.3
  6. 3
      docs/ares_dns_rr_get_abin.3
  7. 3
      docs/ares_dns_rr_get_abin_cnt.3
  8. 92
      include/ares_dns_record.h
  9. 2
      src/lib/Makefile.inc
  10. 107
      src/lib/ares__buf.c
  11. 32
      src/lib/ares__buf.h
  12. 30
      src/lib/ares__llist.c
  13. 15
      src/lib/ares__llist.h
  14. 4
      src/lib/ares_dns_mapping.c
  15. 207
      src/lib/ares_dns_multistring.c
  16. 49
      src/lib/ares_dns_multistring.h
  17. 44
      src/lib/ares_dns_parse.c
  18. 6
      src/lib/ares_dns_private.h
  19. 179
      src/lib/ares_dns_record.c
  20. 41
      src/lib/ares_dns_write.c
  21. 2
      src/lib/ares_expand_string.c
  22. 72
      src/lib/ares_parse_txt_reply.c
  23. 1
      src/lib/ares_private.h
  24. 32
      src/tools/adig.c
  25. 25
      test/ares-test-internal.cc
  26. 19
      test/ares-test-parse-txt.cc

@ -40,6 +40,10 @@ MANPAGES = ares_cancel.3 \
ares_dns_rec_type_tostr.3 \
ares_dns_rec_type_t.3 \
ares_dns_rr.3 \
ares_dns_rr_add_abin.3 \
ares_dns_rr_del_abin.3 \
ares_dns_rr_get_abin.3 \
ares_dns_rr_get_abin_cnt.3 \
ares_dns_rr_get_addr.3 \
ares_dns_rr_get_addr6.3 \
ares_dns_rr_get_bin.3 \

@ -89,6 +89,13 @@ set and \fIares_dns_rr_get_bin(3)\fP to get.
- Array of options. 16bit identifier, Binary data. Use \fIares_dns_rr_set_opt(3)\fP to
set and \fIares_dns_rr_get_opt(3)\fP to get.
.br
.B ARES_DATATYPE_ABINP
- Array of binary data, but likely printable. Guaranteed to have a NULL terminator
for convenience (not included in length). Use \fIares_dns_rr_add_abin(3)\fP to
set and \fIares_dns_rr_get_abin(3)\fP to get. Can also use
\fIares_dns_rr_set_bin(3)\fP and \fIares_dns_rr_get_bin(3)\fP if only concatenated
strings are desired.
.br
.RE
.B ares_dns_opt_datatype_t -

@ -5,12 +5,14 @@
.SH NAME
ares_dns_record_rr_add, ares_dns_record_rr_cnt, ares_dns_record_rr_del,
ares_dns_record_rr_get, ares_dns_record_rr_get_const, ares_dns_rr_get_addr,
ares_dns_rr_get_addr6, ares_dns_rr_get_bin, ares_dns_rr_get_class,
ares_dns_rr_get_addr6, ares_dns_rr_get_bin, ares_dns_rr_get_abin_cnt,
ares_dns_rr_get_abin, ares_dns_rr_get_class,
ares_dns_rr_get_name, ares_dns_rr_get_opt, ares_dns_rr_get_opt_byid,
ares_dns_rr_get_opt_cnt, ares_dns_rr_get_str, ares_dns_rr_get_ttl,
ares_dns_rr_get_type, ares_dns_rr_get_u16, ares_dns_rr_get_u32,
ares_dns_rr_get_u8, ares_dns_rr_key_t, ares_dns_rr_set_addr,
ares_dns_rr_set_addr6, ares_dns_rr_set_bin, ares_dns_rr_set_opt,
ares_dns_rr_set_addr6, ares_dns_rr_set_bin, ares_dns_rr_add_abin,
ares_dns_rr_del_abin, ares_dns_rr_set_opt,
ares_dns_rr_set_str, ares_dns_rr_set_u16, ares_dns_rr_set_u32,
ares_dns_rr_set_u8, ares_dns_section_t, ares_tlsa_match_t,
ares_tlsa_selector_t, ares_tlsa_usage_t \-
@ -79,6 +81,15 @@ ares_status_t ares_dns_rr_set_bin(ares_dns_rr_t *dns_rr,
const unsigned char *val,
size_t len);
ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
const unsigned char *val,
size_t len);
ares_status_t ares_dns_rr_del_abin(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
size_t idx);
ares_status_t ares_dns_rr_set_opt(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
unsigned short opt,
@ -107,6 +118,13 @@ const unsigned char *ares_dns_rr_get_bin(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
size_t *len);
size_t ares_dns_rr_get_abin_cnt(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key);
const unsigned char *ares_dns_rr_get_abin(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, size_t idx,
size_t *len);
size_t ares_dns_rr_get_opt_cnt(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key);
@ -188,7 +206,7 @@ Keys used for handling RR record parameters:
- MX Record. Exchange, domain. Datatype: \fIARES_DATATYPE_NAME\fP
.br
.B ARES_RR_TXT_DATA
- TXT Record. Data. Datatype: \fIARES_DATATYPE_BINP\fP
- TXT Record. Data. Datatype: \fIARES_DATATYPE_ABINP\fP
.br
.B ARES_RR_SIG_TYPE_COVERED
- SIG Record. Type Covered. Datatype: \fIARES_DATATYPE_U16\fP
@ -497,7 +515,7 @@ parameter, and the value is provided in the
.IR val
parameter.
The \fIares_dns_rr_set_bin(3)\fP function is used to set an binary value for the
The \fIares_dns_rr_set_bin(3)\fP function is used to set a binary value for the
associated resource record key/parameter when the datatype is \fIARES_DATATYPE_BIN\fP
or \fIARES_DATATYPE_BINP\fP.
The resource record to be modified is provided in the
@ -510,6 +528,30 @@ parameter. And the associated value length is provided in the
.IR len
parameter.
The \fIares_dns_rr_add_abin(3)\fP function is used to append a binary value in
the array for the associated resource record key/parameter when the datatype
is \fIARES_DATATYPE_ABINP\fP.
The resource record to be modified is provided in the
.IR dns_rr
parameter, the key/parameter is provided in the
.IR key
parameter, and the value is provided in the
.IR val
parameter. And the associated value length is provided in the
.IR len
parameter.
The \fIares_dns_rr_del_abin(3)\fP function is used to delete a binary value in
the array for the associated resource record key/parameter when the datatype
is \fIARES_DATATYPE_ABINP\fP.
The resource record to be modified is provided in the
.IR dns_rr
parameter, the key/parameter is provided in the
.IR key
parameter, and the index to remove is provided in the
.IR idx
parameter.
The \fIares_dns_rr_set_opt(3)\fP function is used to set option/parameter keys and
values for the resource record when the datatype if \fIARES_DATATYPE_OPT\fP. The
resource record to be modified is provided in the
@ -584,6 +626,26 @@ parameter and the key/parameter to retrieve is provided in the
parameter, and length is stored into the variable pointed to by
.IR len.
The \fIares_dns_rr_get_abin_cnt(3)\fP function is used to retrieve the count
of the array of stored binary values from the resource record when the datatype
is \fIARES_DATATYPE_ABINP\fP.
The resource record is provided in the
.IR dns_rr
parameter and the key/parameter to retrieve is provided in the
.IR key
parameter.
The \fIares_dns_rr_get_abin(3)\fP function is used to retrive binary data from
the resource record array when the datatype is \fIARES_DATATYPE_ABINP\fP.
The resource record is provided in the
.IR dns_rr
parameter and the key/parameter to retrieve is provided in the
.IR key
parameter, and the index to retrieve from the array is provided by the
.IR idx
parameter, and length is stored into the variable pointed to by
.IR len.
The \fIares_dns_rr_get_opt_cnt(3)\fP function is used to retrieve the count
of options/parameters associated with the resource record when the datatype
is \fIARES_DATATYPE_OPT\fP.
@ -630,8 +692,9 @@ respective counts.
\fIares_dns_rr_set_addr(3)\fP, \fIares_dns_rr_set_addr6(3)\fP,
\fIares_dns_rr_set_str(3)\fP, \fIares_dns_rr_set_u8(3)\fP,
\fIares_dns_rr_set_u16(3)\fP, \fIares_dns_rr_set_u32(3)\fP,
\fIares_dns_rr_set_bin(3)\fP, and \fIares_dns_rr_set_opt(3)\fP all
return an \fIares_status_t\fP error code.
\fIares_dns_rr_set_bin(3)\fP, \fIares_dns_rr_add_abin(3)\fP,
\fIares_dns_rr_del_abin(3)\fP,
and \fIares_dns_rr_set_opt(3)\fP all return an \fIares_status_t\fP error code.
.B ARES_SUCCESS
is returned on success,
.B ARES_ENOMEM
@ -645,7 +708,8 @@ is returned on misuse.
\fIares_dns_rr_get_addr(3)\fP, \fIares_dns_rr_get_addr6(3)\fP,
\fIares_dns_rr_get_str(3)\fP, \fIares_dns_rr_get_u8(3)\fP,
\fIares_dns_rr_get_u16(3)\fP, \fIares_dns_rr_get_u32(3)\fP,
\fIares_dns_rr_get_bin(3)\fP, \fIares_dns_rr_get_opt(3)\fP all return their
\fIares_dns_rr_get_bin(3)\fP, \fIares_dns_rr_get_abin_cnt(3)\fP,
\fIares_dns_rr_get_abin(3)\fP, \fIares_dns_rr_get_opt(3)\fP all return their
prescribed datatype values and in general can't fail except for misuse cases,
in which a 0 (or NULL) may be returned, however 0 can also be a valid return
value for most of these functions.

@ -0,0 +1,3 @@
.\" Copyright (C) 2023 The c-ares project and its contributors.
.\" SPDX-License-Identifier: MIT
.so man3/ares_dns_rr.3

@ -0,0 +1,3 @@
.\" Copyright (C) 2023 The c-ares project and its contributors.
.\" SPDX-License-Identifier: MIT
.so man3/ares_dns_rr.3

@ -0,0 +1,3 @@
.\" Copyright (C) 2023 The c-ares project and its contributors.
.\" SPDX-License-Identifier: MIT
.so man3/ares_dns_rr.3

@ -0,0 +1,3 @@
.\" Copyright (C) 2023 The c-ares project and its contributors.
.\" SPDX-License-Identifier: MIT
.so man3/ares_dns_rr.3

@ -157,20 +157,23 @@ typedef enum {
/*! Data types used */
typedef enum {
ARES_DATATYPE_INADDR = 1, /*!< struct in_addr * type */
ARES_DATATYPE_INADDR6 = 2, /*!< struct ares_in6_addr * type */
ARES_DATATYPE_U8 = 3, /*!< 8bit unsigned integer */
ARES_DATATYPE_U16 = 4, /*!< 16bit unsigned integer */
ARES_DATATYPE_U32 = 5, /*!< 32bit unsigned integer */
ARES_DATATYPE_NAME = 6, /*!< Null-terminated string of a domain name */
ARES_DATATYPE_STR = 7, /*!< Null-terminated string */
ARES_DATATYPE_BIN = 8, /*!< Binary data */
ARES_DATATYPE_BINP = 9, /*!< Officially defined as binary data, but likely
* printable. Guaranteed to have a NULL
* terminator for convenience (not included in
* length) */
ARES_DATATYPE_OPT = 10 /*!< Array of options. 16bit identifier, BIN
* data. */
ARES_DATATYPE_INADDR = 1, /*!< struct in_addr * type */
ARES_DATATYPE_INADDR6 = 2, /*!< struct ares_in6_addr * type */
ARES_DATATYPE_U8 = 3, /*!< 8bit unsigned integer */
ARES_DATATYPE_U16 = 4, /*!< 16bit unsigned integer */
ARES_DATATYPE_U32 = 5, /*!< 32bit unsigned integer */
ARES_DATATYPE_NAME = 6, /*!< Null-terminated string of a domain name */
ARES_DATATYPE_STR = 7, /*!< Null-terminated string */
ARES_DATATYPE_BIN = 8, /*!< Binary data */
ARES_DATATYPE_BINP = 9, /*!< Officially defined as binary data, but likely
* printable. Guaranteed to have a NULL
* terminator for convenience (not included in
* length) */
ARES_DATATYPE_OPT = 10, /*!< Array of options. 16bit identifier, BIN
* data. */
ARES_DATATYPE_ABINP = 11 /*!< Array of binary data, likely printable.
* Guaranteed to have a NULL terminator for
* convenience (not included in length) */
} ares_dns_datatype_t;
/*! Keys used for all RR Types. We take the record type and multiply by 100
@ -207,7 +210,7 @@ typedef enum {
ARES_RR_MX_PREFERENCE = (ARES_REC_TYPE_MX * 100) + 1,
/*! MX Record. Exchange, domain. Datatype: NAME */
ARES_RR_MX_EXCHANGE = (ARES_REC_TYPE_MX * 100) + 2,
/*! TXT Record. Data. Datatype: BINP */
/*! TXT Record. Data. Datatype: ABINP */
ARES_RR_TXT_DATA = (ARES_REC_TYPE_TXT * 100) + 1,
/*! SIG Record. Type Covered. Datatype: U16 */
ARES_RR_SIG_TYPE_COVERED = (ARES_REC_TYPE_SIG * 100) + 1,
@ -872,6 +875,34 @@ CARES_EXTERN ares_status_t ares_dns_rr_set_bin(ares_dns_rr_t *dns_rr,
const unsigned char *val,
size_t len);
/*! Add binary array value (ABINP) data for specified resource record and key.
* Can only be used on keys with datatype ARES_DATATYPE_ABINP. The value will
* Be added as the last element in the array.
*
* \param[in] dns_rr Pointer to resource record
* \param[in] key DNS Resource Record Key
* \param[in] val Pointer to binary data.
* \param[in] len Length of binary data
* \return ARES_SUCCESS on success
*/
CARES_EXTERN ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
const unsigned char *val,
size_t len);
/*! Delete binary array value (ABINP) data for specified resource record and
* key by specified index. Can only be used on keys with datatype
* ARES_DATATYPE_ABINP. The value at the index will be deleted.
*
* \param[in] dns_rr Pointer to resource record
* \param[in] key DNS Resource Record Key
* \param[in] idx Index to delete
* \return ARES_SUCCESS on success
*/
CARES_EXTERN ares_status_t ares_dns_rr_del_abin(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
size_t idx);
/*! Set the option for the RR
*
* \param[in] dns_rr Pointer to resource record
@ -948,8 +979,9 @@ CARES_EXTERN unsigned int ares_dns_rr_get_u32(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key);
/*! Retrieve a pointer to the binary data. Can only be used on keys with
* datatype ARES_DATATYPE_BIN or ARES_DATATYPE_BINP. If BINP, the data is
* guaranteed to have a NULL terminator which is NOT included in the length.
* datatype ARES_DATATYPE_BIN, ARES_DATATYPE_BINP, or ARES_DATATYPE_ABINP.
* If BINP or ABINP, the data is guaranteed to have a NULL terminator which
* is NOT included in the length.
*
* \param[in] dns_rr Pointer to resource record
* \param[in] key DNS Resource Record Key
@ -960,6 +992,32 @@ CARES_EXTERN const unsigned char *
ares_dns_rr_get_bin(const ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
size_t *len);
/*! Retrieve the count of the array of stored binary values. Can only be used on
* keys with datatype ARES_DATATYPE_ABINP.
*
* \param[in] dns_rr Pointer to resource record
* \param[in] key DNS Resource Record Key
* \return count of values
*/
CARES_EXTERN size_t ares_dns_rr_get_abin_cnt(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key);
/*! Retrieve a pointer to the binary array data from the specified index. Can
* only be used on keys with datatype ARES_DATATYPE_ABINP. If ABINP, the data
* is guaranteed to have a NULL terminator which is NOT included in the length.
* If want all array membersconcatenated, may use ares_dns_rr_get_bin()
* instead.
*
* \param[in] dns_rr Pointer to resource record
* \param[in] key DNS Resource Record Key
* \param[in] idx Index of value to retrieve
* \param[out] len Length of binary data returned
* \return pointer binary data or NULL on error
*/
CARES_EXTERN const unsigned char *ares_dns_rr_get_abin(
const ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key, size_t idx, size_t *len);
/*! Retrieve the number of options stored for the RR.
*
* \param[in] dns_rr Pointer to resource record

@ -24,6 +24,7 @@ CSOURCES = ares__addrinfo2hostent.c \
ares_data.c \
ares_destroy.c \
ares_dns_mapping.c \
ares_dns_multistring.c \
ares_dns_name.c \
ares_dns_parse.c \
ares_dns_record.c \
@ -99,6 +100,7 @@ HHEADERS = ares__buf.h \
ares__threads.h \
ares_android.h \
ares_data.h \
ares_dns_multistring.h \
ares_dns_private.h \
ares_event.h \
ares_event_win32.h \

@ -937,13 +937,13 @@ ares_status_t ares__buf_set_position(ares__buf_t *buf, size_t idx)
return ARES_SUCCESS;
}
static ares_status_t ares__buf_parse_dns_binstr_int(
ares__buf_t *buf, size_t remaining_len, unsigned char **bin, size_t *bin_len,
ares_bool_t allow_multiple, ares_bool_t validate_printable)
ares_status_t ares__buf_parse_dns_abinstr(ares__buf_t *buf,
size_t remaining_len,
ares__dns_multistring_t **strs,
ares_bool_t validate_printable)
{
unsigned char len;
ares_status_t status = ARES_EBADRESP;
ares__buf_t *binbuf = NULL;
size_t orig_len = ares__buf_len(buf);
if (buf == NULL) {
@ -954,9 +954,11 @@ static ares_status_t ares__buf_parse_dns_binstr_int(
return ARES_EBADRESP;
}
binbuf = ares__buf_create();
if (binbuf == NULL) {
return ARES_ENOMEM;
if (strs != NULL) {
*strs = ares__dns_multistring_create();
if (*strs == NULL) {
return ARES_ENOMEM;
}
}
while (orig_len - ares__buf_len(buf) < remaining_len) {
@ -977,22 +979,88 @@ static ares_status_t ares__buf_parse_dns_binstr_int(
}
}
if (bin != NULL) {
status = ares__buf_fetch_bytes_into_buf(buf, binbuf, len);
if (strs != NULL) {
unsigned char *data = NULL;
status = ares__buf_fetch_bytes_dup(buf, len, ARES_TRUE, &data);
if (status != ARES_SUCCESS) {
break;
}
status = ares__dns_multistring_add_own(*strs, data, len);
if (status != ARES_SUCCESS) {
ares_free(data);
break;
}
} else {
status = ares__buf_consume(buf, len);
if (status != ARES_SUCCESS) {
break;
}
}
if (status != ARES_SUCCESS) {
break;
}
}
if (status != ARES_SUCCESS && strs != NULL) {
ares__dns_multistring_destroy(*strs);
*strs = NULL;
}
return status;
}
static ares_status_t
ares__buf_parse_dns_binstr_int(ares__buf_t *buf, size_t remaining_len,
unsigned char **bin, size_t *bin_len,
ares_bool_t validate_printable)
{
unsigned char len;
ares_status_t status = ARES_EBADRESP;
ares__buf_t *binbuf = NULL;
if (buf == NULL) {
return ARES_EFORMERR;
}
if (remaining_len == 0) {
return ARES_EBADRESP;
}
binbuf = ares__buf_create();
if (binbuf == NULL) {
return ARES_ENOMEM;
}
status = ares__buf_fetch_bytes(buf, &len, 1);
if (status != ARES_SUCCESS) {
goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
}
remaining_len--;
if (len > remaining_len) {
status = ARES_EBADRESP;
goto done;
}
if (len) {
/* When used by the _str() parser, it really needs to be validated to
* be a valid printable ascii string. Do that here */
if (validate_printable && ares__buf_len(buf) >= len) {
size_t mylen;
const char *data = (const char *)ares__buf_peek(buf, &mylen);
if (!ares__str_isprint(data, len)) {
status = ARES_EBADSTR;
goto done;
}
}
if (!allow_multiple) {
break;
if (bin != NULL) {
status = ares__buf_fetch_bytes_into_buf(buf, binbuf, len);
} else {
status = ares__buf_consume(buf, len);
}
}
done:
if (status != ARES_SUCCESS) {
ares__buf_destroy(binbuf);
} else {
@ -1010,20 +1078,19 @@ static ares_status_t ares__buf_parse_dns_binstr_int(
}
ares_status_t ares__buf_parse_dns_binstr(ares__buf_t *buf, size_t remaining_len,
unsigned char **bin, size_t *bin_len,
ares_bool_t allow_multiple)
unsigned char **bin, size_t *bin_len)
{
return ares__buf_parse_dns_binstr_int(buf, remaining_len, bin, bin_len,
allow_multiple, ARES_FALSE);
ARES_FALSE);
}
ares_status_t ares__buf_parse_dns_str(ares__buf_t *buf, size_t remaining_len,
char **str, ares_bool_t allow_multiple)
char **str)
{
size_t len;
return ares__buf_parse_dns_binstr_int(
buf, remaining_len, (unsigned char **)str, &len, allow_multiple, ARES_TRUE);
return ares__buf_parse_dns_binstr_int(buf, remaining_len,
(unsigned char **)str, &len, ARES_TRUE);
}
ares_status_t ares__buf_append_num_dec(ares__buf_t *buf, size_t num, size_t len)

@ -554,13 +554,31 @@ size_t ares__buf_get_position(const ares__buf_t *buf);
* \param[out] name Pointer passed by reference to be filled in with
* allocated string of the parsed that must be
* ares_free()'d by the caller.
* \param[in] allow_multiple ARES_TRUE if it should attempt to parse multiple
* strings back to back, and will concatenate in
* the returned str.
* \return ARES_SUCCESS on success
*/
ares_status_t ares__buf_parse_dns_str(ares__buf_t *buf, size_t remaining_len,
char **name, ares_bool_t allow_multiple);
char **name);
/*! Parse an array of character strings as defined in RFC1035, as binary,
* however, for convenience this does guarantee a NULL terminator (that is
* not included in the length for each value).
*
* \param[in] buf initialized buffer object
* \param[in] remaining_len maximum length that should be used for
* parsing the string, this is often less than
* the remaining buffer and is based on the RR
* record length.
* \param[out] strs Pointer passed by reference to be filled in
* with
* the array of values.
* \param[out] validate_printable Validate the strings contain only printable
* data.
* \return ARES_SUCCESS on success
*/
ares_status_t ares__buf_parse_dns_abinstr(ares__buf_t *buf,
size_t remaining_len,
ares__dns_multistring_t **strs,
ares_bool_t validate_printable);
/*! Parse a character-string as defined in RFC1035, as binary, however for
* convenience this does guarantee a NULL terminator (that is not included
@ -574,14 +592,10 @@ ares_status_t ares__buf_parse_dns_str(ares__buf_t *buf, size_t remaining_len,
* allocated string of the parsed that must be
* ares_free()'d by the caller.
* \param[out] bin_len Length of returned string.
* \param[in] allow_multiple ARES_TRUE if it should attempt to parse multiple
* strings back to back, and will concatenate in
* the returned str.
* \return ARES_SUCCESS on success
*/
ares_status_t ares__buf_parse_dns_binstr(ares__buf_t *buf, size_t remaining_len,
unsigned char **bin, size_t *bin_len,
ares_bool_t allow_multiple);
unsigned char **bin, size_t *bin_len);
/*! Load data from specified file path into provided buffer. The entire file
* is loaded into memory.

@ -184,6 +184,26 @@ ares__llist_node_t *ares__llist_node_first(ares__llist_t *list)
return list->head;
}
ares__llist_node_t *ares__llist_node_idx(ares__llist_t *list, size_t idx)
{
ares__llist_node_t *node;
size_t cnt;
if (list == NULL) {
return NULL;
}
if (idx >= list->cnt) {
return NULL;
}
node = list->head;
for (cnt = 0; node != NULL && cnt < idx; cnt++) {
node = node->next;
}
return node;
}
ares__llist_node_t *ares__llist_node_last(ares__llist_t *list)
{
if (list == NULL) {
@ -321,7 +341,7 @@ void ares__llist_node_replace(ares__llist_node_t *node, void *val)
node->data = val;
}
void ares__llist_destroy(ares__llist_t *list)
void ares__llist_clear(ares__llist_t *list)
{
ares__llist_node_t *node;
@ -332,6 +352,14 @@ void ares__llist_destroy(ares__llist_t *list)
while ((node = ares__llist_node_first(list)) != NULL) {
ares__llist_node_destroy(node);
}
}
void ares__llist_destroy(ares__llist_t *list)
{
if (list == NULL) {
return;
}
ares__llist_clear(list);
ares_free(list);
}

@ -122,6 +122,14 @@ ares__llist_node_t *ares__llist_node_first(ares__llist_t *list);
*/
ares__llist_node_t *ares__llist_node_last(ares__llist_t *list);
/*! Obtain a node based on its index. This is an O(n) operation.
*
* \param[in] list Initialized list object
* \param[in] idx Index of node to retrieve
* \return node at index or NULL if invalid index
*/
ares__llist_node_t *ares__llist_node_idx(ares__llist_t *list, size_t idx);
/*! Obtain next node in respect to specified node
*
* \param[in] node Node referenced
@ -136,6 +144,7 @@ ares__llist_node_t *ares__llist_node_next(ares__llist_node_t *node);
*/
ares__llist_node_t *ares__llist_node_prev(ares__llist_node_t *node);
/*! Obtain value from node
*
* \param[in] node Node referenced
@ -150,6 +159,12 @@ void *ares__llist_node_val(ares__llist_node_t *node);
*/
size_t ares__llist_len(const ares__llist_t *list);
/*! Clear all entries in the list, but don't destroy the list object.
*
* \param[in] list Initialized list object
*/
void ares__llist_clear(ares__llist_t *list);
/*! Obtain list object from referenced node
*
* \param[in] node Node referenced

@ -516,9 +516,11 @@ ares_dns_datatype_t ares_dns_rr_key_datatype(ares_dns_rr_key_t key)
return ARES_DATATYPE_U8;
case ARES_RR_CAA_VALUE:
case ARES_RR_TXT_DATA:
return ARES_DATATYPE_BINP;
case ARES_RR_TXT_DATA:
return ARES_DATATYPE_ABINP;
case ARES_RR_SIG_SIGNATURE:
case ARES_RR_TLSA_DATA:
case ARES_RR_RAW_RR_DATA:

@ -0,0 +1,207 @@
/* MIT License
*
* Copyright (c) 2024 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_private.h"
#include "ares_dns_private.h"
typedef struct {
unsigned char *data;
size_t len;
} multistring_data_t;
struct ares__dns_multistring {
/*! whether or not cached concatenated string is valid */
ares_bool_t cache_invalidated;
/*! combined/concatenated string cache */
unsigned char *cache_str;
/*! length of combined/concatenated string */
size_t cache_str_len;
/*! Data making up strings */
multistring_data_t *strs;
size_t cnt;
size_t alloc;
};
ares__dns_multistring_t *ares__dns_multistring_create(void)
{
return ares_malloc_zero(sizeof(ares__dns_multistring_t));
}
void ares__dns_multistring_clear(ares__dns_multistring_t *strs)
{
size_t i;
if (strs == NULL) {
return;
}
for (i = 0; i < strs->cnt; i++) {
ares_free(strs->strs[i].data);
memset(&strs->strs[i], 0, sizeof(strs->strs[i]));
}
strs->cnt = 0;
}
void ares__dns_multistring_destroy(ares__dns_multistring_t *strs)
{
if (strs == NULL) {
return;
}
ares__dns_multistring_clear(strs);
ares_free(strs->strs);
ares_free(strs->cache_str);
ares_free(strs);
}
ares_status_t ares__dns_multistring_replace_own(ares__dns_multistring_t *strs,
size_t idx, unsigned char *str,
size_t len)
{
if (strs == NULL || str == NULL || len == 0 || idx >= strs->cnt) {
return ARES_EFORMERR;
}
strs->cache_invalidated = ARES_TRUE;
ares_free(strs->strs[idx].data);
strs->strs[idx].data = str;
strs->strs[idx].len = len;
return ARES_SUCCESS;
}
ares_status_t ares__dns_multistring_del(ares__dns_multistring_t *strs,
size_t idx)
{
size_t move_cnt;
if (strs == NULL || idx >= strs->cnt) {
return ARES_EFORMERR;
}
strs->cache_invalidated = ARES_TRUE;
ares_free(strs->strs[idx].data);
move_cnt = strs->cnt - idx - 1;
if (move_cnt) {
memmove(&strs->strs[idx], &strs->strs[idx + 1],
sizeof(*strs->strs) * move_cnt);
}
strs->cnt--;
return ARES_SUCCESS;
}
ares_status_t ares__dns_multistring_add_own(ares__dns_multistring_t *strs,
unsigned char *str, size_t len)
{
if (strs == NULL) {
return ARES_EFORMERR;
}
strs->cache_invalidated = ARES_TRUE;
/* NOTE: its ok to have an empty string added */
if (str == NULL && len != 0) {
return ARES_EFORMERR;
}
if (strs->alloc < strs->cnt + 1) {
size_t newalloc = (strs->alloc == 0) ? 1 : (strs->alloc << 1);
void *ptr = ares_realloc_zero(strs->strs, strs->alloc * sizeof(*strs->strs),
(newalloc) * sizeof(*strs->strs));
if (ptr == NULL) {
return ARES_ENOMEM;
}
strs->strs = ptr;
strs->alloc = newalloc;
}
strs->strs[strs->cnt].data = str;
strs->strs[strs->cnt].len = len;
strs->cnt++;
return ARES_SUCCESS;
}
size_t ares__dns_multistring_cnt(ares__dns_multistring_t *strs)
{
if (strs == NULL) {
return 0;
}
return strs->cnt;
}
const unsigned char *ares__dns_multistring_get(ares__dns_multistring_t *strs,
size_t idx, size_t *len)
{
if (strs == NULL || idx >= strs->cnt || len == NULL) {
return NULL;
}
*len = strs->strs[idx].len;
return strs->strs[idx].data;
}
const unsigned char *
ares__dns_multistring_get_combined(ares__dns_multistring_t *strs, size_t *len)
{
ares__buf_t *buf = NULL;
size_t i;
if (strs == NULL || len == NULL) {
return NULL;
}
*len = 0;
/* Return cache if possible */
if (!strs->cache_invalidated) {
*len = strs->cache_str_len;
return strs->cache_str;
}
/* Clear cache */
ares_free(strs->cache_str);
strs->cache_str = NULL;
strs->cache_str_len = 0;
buf = ares__buf_create();
for (i = 0; i < strs->cnt; i++) {
if (ares__buf_append(buf, strs->strs[i].data, strs->strs[i].len) !=
ARES_SUCCESS) {
ares__buf_destroy(buf);
return NULL;
}
}
strs->cache_str =
(unsigned char *)ares__buf_finish_str(buf, &strs->cache_str_len);
if (strs->cache_str != NULL) {
strs->cache_invalidated = ARES_FALSE;
}
*len = strs->cache_str_len;
return strs->cache_str;
}

@ -0,0 +1,49 @@
/* MIT License
*
* Copyright (c) 2024 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
*/
#ifndef __ARES_DNS_MULTISTRING_H
#define __ARES_DNS_MULTISTRING_H
struct ares__dns_multistring;
typedef struct ares__dns_multistring ares__dns_multistring_t;
ares__dns_multistring_t *ares__dns_multistring_create(void);
void ares__dns_multistring_clear(ares__dns_multistring_t *strs);
void ares__dns_multistring_destroy(ares__dns_multistring_t *strs);
ares_status_t ares__dns_multistring_replace_own(ares__dns_multistring_t *strs,
size_t idx, unsigned char *str,
size_t len);
ares_status_t ares__dns_multistring_del(ares__dns_multistring_t *strs,
size_t idx);
ares_status_t ares__dns_multistring_add_own(ares__dns_multistring_t *strs,
unsigned char *str, size_t len);
size_t ares__dns_multistring_cnt(ares__dns_multistring_t *strs);
const unsigned char *ares__dns_multistring_get(ares__dns_multistring_t *strs,
size_t idx, size_t *len);
const unsigned char *
ares__dns_multistring_get_combined(ares__dns_multistring_t *strs,
size_t *len);
#endif

@ -60,14 +60,16 @@ static ares_status_t ares_dns_parse_and_set_dns_name(ares__buf_t *buf,
return ARES_SUCCESS;
}
static ares_status_t ares_dns_parse_and_set_dns_str(
ares__buf_t *buf, size_t max_len, ares_bool_t allow_multiple,
ares_dns_rr_t *rr, ares_dns_rr_key_t key, ares_bool_t blank_allowed)
static ares_status_t ares_dns_parse_and_set_dns_str(ares__buf_t *buf,
size_t max_len,
ares_dns_rr_t *rr,
ares_dns_rr_key_t key,
ares_bool_t blank_allowed)
{
ares_status_t status;
char *str = NULL;
status = ares__buf_parse_dns_str(buf, max_len, &str, allow_multiple);
status = ares__buf_parse_dns_str(buf, max_len, &str);
if (status != ARES_SUCCESS) {
return status;
}
@ -86,23 +88,21 @@ static ares_status_t ares_dns_parse_and_set_dns_str(
}
static ares_status_t
ares_dns_parse_and_set_dns_binstr(ares__buf_t *buf, size_t max_len,
ares_bool_t allow_multiple,
ares_dns_rr_t *rr, ares_dns_rr_key_t key)
ares_dns_parse_and_set_dns_abin(ares__buf_t *buf, size_t max_len,
ares_dns_rr_t *rr, ares_dns_rr_key_t key,
ares_bool_t validate_printable)
{
ares_status_t status;
unsigned char *bin = NULL;
size_t bin_len = 0;
ares_status_t status;
ares__dns_multistring_t *strs = NULL;
status =
ares__buf_parse_dns_binstr(buf, max_len, &bin, &bin_len, allow_multiple);
status = ares__buf_parse_dns_abinstr(buf, max_len, &strs, validate_printable);
if (status != ARES_SUCCESS) {
return status;
}
status = ares_dns_rr_set_bin_own(rr, key, bin, bin_len);
status = ares_dns_rr_set_abin_own(rr, key, strs);
if (status != ARES_SUCCESS) {
ares_free(bin);
ares__dns_multistring_destroy(strs);
return status;
}
return ARES_SUCCESS;
@ -255,7 +255,7 @@ static ares_status_t ares_dns_parse_rr_hinfo(ares__buf_t *buf,
/* CPU */
status = ares_dns_parse_and_set_dns_str(
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), ARES_FALSE, rr,
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), rr,
ARES_RR_HINFO_CPU, ARES_TRUE);
if (status != ARES_SUCCESS) {
return status;
@ -263,7 +263,7 @@ static ares_status_t ares_dns_parse_rr_hinfo(ares__buf_t *buf,
/* OS */
status = ares_dns_parse_and_set_dns_str(
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), ARES_FALSE, rr,
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), rr,
ARES_RR_HINFO_OS, ARES_TRUE);
return status;
@ -290,8 +290,8 @@ static ares_status_t ares_dns_parse_rr_mx(ares__buf_t *buf, ares_dns_rr_t *rr,
static ares_status_t ares_dns_parse_rr_txt(ares__buf_t *buf, ares_dns_rr_t *rr,
size_t rdlength)
{
return ares_dns_parse_and_set_dns_binstr(buf, rdlength, ARES_TRUE, rr,
ARES_RR_TXT_DATA);
return ares_dns_parse_and_set_dns_abin(buf, rdlength, rr, ARES_RR_TXT_DATA,
ARES_FALSE);
}
static ares_status_t ares_dns_parse_rr_sig(ares__buf_t *buf, ares_dns_rr_t *rr,
@ -428,7 +428,7 @@ static ares_status_t ares_dns_parse_rr_naptr(ares__buf_t *buf,
/* FLAGS */
status = ares_dns_parse_and_set_dns_str(
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), ARES_FALSE, rr,
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), rr,
ARES_RR_NAPTR_FLAGS, ARES_TRUE);
if (status != ARES_SUCCESS) {
return status;
@ -436,7 +436,7 @@ static ares_status_t ares_dns_parse_rr_naptr(ares__buf_t *buf,
/* SERVICES */
status = ares_dns_parse_and_set_dns_str(
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), ARES_FALSE, rr,
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), rr,
ARES_RR_NAPTR_SERVICES, ARES_TRUE);
if (status != ARES_SUCCESS) {
return status;
@ -444,7 +444,7 @@ static ares_status_t ares_dns_parse_rr_naptr(ares__buf_t *buf,
/* REGEXP */
status = ares_dns_parse_and_set_dns_str(
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), ARES_FALSE, rr,
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), rr,
ARES_RR_NAPTR_REGEXP, ARES_TRUE);
if (status != ARES_SUCCESS) {
return status;
@ -729,7 +729,7 @@ static ares_status_t ares_dns_parse_rr_caa(ares__buf_t *buf, ares_dns_rr_t *rr,
/* Tag */
status = ares_dns_parse_and_set_dns_str(
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), ARES_FALSE, rr,
buf, ares_dns_rr_remaining_len(buf, orig_len, rdlength), rr,
ARES_RR_CAA_TAG, ARES_FALSE);
if (status != ARES_SUCCESS) {
return status;

@ -41,6 +41,9 @@ ares_status_t ares_dns_rr_set_str_own(ares_dns_rr_t *dns_rr,
ares_status_t ares_dns_rr_set_bin_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, unsigned char *val,
size_t len);
ares_status_t ares_dns_rr_set_abin_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
ares__dns_multistring_t *strs);
ares_status_t ares_dns_rr_set_opt_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, unsigned short opt,
unsigned char *val, size_t val_len);
@ -120,8 +123,7 @@ typedef struct {
} ares__dns_mx_t;
typedef struct {
char *data;
size_t data_len;
ares__dns_multistring_t *strs;
} ares__dns_txt_t;
typedef struct {

@ -152,7 +152,7 @@ static void ares__dns_rr_free(ares_dns_rr_t *rr)
break;
case ARES_REC_TYPE_TXT:
ares_free(rr->r.txt.data);
ares__dns_multistring_destroy(rr->r.txt.strs);
break;
case ARES_REC_TYPE_SIG:
@ -671,11 +671,7 @@ static void *ares_dns_rr_data_ptr(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
return &dns_rr->r.sig.signature;
case ARES_RR_TXT_DATA:
if (lenptr == NULL) {
return NULL;
}
*lenptr = &dns_rr->r.txt.data_len;
return &dns_rr->r.txt.data;
return &dns_rr->r.txt.strs;
case ARES_RR_SRV_PRIORITY:
return &dns_rr->r.srv.priority;
@ -890,26 +886,138 @@ const unsigned char *ares_dns_rr_get_bin(const ares_dns_rr_t *dns_rr,
size_t const *bin_len = NULL;
if ((ares_dns_rr_key_datatype(key) != ARES_DATATYPE_BIN &&
ares_dns_rr_key_datatype(key) != ARES_DATATYPE_BINP) ||
ares_dns_rr_key_datatype(key) != ARES_DATATYPE_BINP &&
ares_dns_rr_key_datatype(key) != ARES_DATATYPE_ABINP) ||
len == NULL) {
return NULL;
}
/* Array of strings, return concatenated version */
if (ares_dns_rr_key_datatype(key) == ARES_DATATYPE_ABINP) {
ares__dns_multistring_t * const *strs =
ares_dns_rr_data_ptr_const(dns_rr, key, NULL);
if (strs == NULL) {
return NULL;
}
return ares__dns_multistring_get_combined(*strs, len);
}
/* Not a multi-string, just straight binary data */
bin = ares_dns_rr_data_ptr_const(dns_rr, key, &bin_len);
if (bin == NULL) {
return 0;
return NULL;
}
/* Shouldn't be possible */
if (bin_len == NULL) {
return NULL;
}
*len = *bin_len;
return *bin;
}
size_t ares_dns_rr_get_abin_cnt(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key)
{
ares__dns_multistring_t * const *strs;
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_ABINP) {
return 0;
}
strs = ares_dns_rr_data_ptr_const(dns_rr, key, NULL);
if (strs == NULL) {
return 0;
}
return ares__dns_multistring_cnt(*strs);
}
const unsigned char *ares_dns_rr_get_abin(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, size_t idx,
size_t *len)
{
ares__dns_multistring_t * const *strs;
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_ABINP) {
return NULL;
}
strs = ares_dns_rr_data_ptr_const(dns_rr, key, NULL);
if (strs == NULL) {
return NULL;
}
return ares__dns_multistring_get(*strs, idx, len);
}
ares_status_t ares_dns_rr_del_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
size_t idx)
{
ares__dns_multistring_t **strs;
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_ABINP) {
return ARES_EFORMERR;
}
strs = ares_dns_rr_data_ptr(dns_rr, key, NULL);
if (strs == NULL) {
return ARES_EFORMERR;
}
return ares__dns_multistring_del(*strs, idx);
}
ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
const unsigned char *val, size_t len)
{
ares_status_t status;
ares_dns_datatype_t datatype = ares_dns_rr_key_datatype(key);
ares_bool_t is_nullterm =
(datatype == ARES_DATATYPE_ABINP) ? ARES_TRUE : ARES_FALSE;
size_t alloclen = is_nullterm ? len + 1 : len;
unsigned char *temp;
ares__dns_multistring_t **strs;
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_ABINP) {
return ARES_EFORMERR;
}
strs = ares_dns_rr_data_ptr(dns_rr, key, NULL);
if (strs == NULL) {
return ARES_EFORMERR;
}
if (*strs == NULL) {
*strs = ares__dns_multistring_create();
if (*strs == NULL) {
return ARES_ENOMEM;
}
}
temp = ares_malloc(alloclen);
if (temp == NULL) {
return ARES_ENOMEM;
}
memcpy(temp, val, len);
/* NULL-term ABINP */
if (is_nullterm) {
temp[len] = 0;
}
status = ares__dns_multistring_add_own(*strs, temp, len);
if (status != ARES_SUCCESS) {
ares_free(temp);
}
return status;
}
const char *ares_dns_rr_get_str(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key)
{
@ -1122,10 +1230,30 @@ ares_status_t ares_dns_rr_set_bin_own(ares_dns_rr_t *dns_rr,
size_t *bin_len = NULL;
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_BIN &&
ares_dns_rr_key_datatype(key) != ARES_DATATYPE_BINP) {
ares_dns_rr_key_datatype(key) != ARES_DATATYPE_BINP &&
ares_dns_rr_key_datatype(key) != ARES_DATATYPE_ABINP) {
return ARES_EFORMERR;
}
if (ares_dns_rr_key_datatype(key) == ARES_DATATYPE_ABINP) {
ares__dns_multistring_t **strs = ares_dns_rr_data_ptr(dns_rr, key, NULL);
if (strs == NULL) {
return ARES_EFORMERR;
}
if (*strs == NULL) {
*strs = ares__dns_multistring_create();
if (*strs == NULL) {
return ARES_ENOMEM;
}
}
/* Clear all existing entries as this is an override */
ares__dns_multistring_clear(*strs);
return ares__dns_multistring_add_own(*strs, val, len);
}
bin = ares_dns_rr_data_ptr(dns_rr, key, &bin_len);
if (bin == NULL || bin_len == NULL) {
return ARES_EFORMERR;
@ -1145,7 +1273,11 @@ ares_status_t ares_dns_rr_set_bin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
{
ares_status_t status;
ares_dns_datatype_t datatype = ares_dns_rr_key_datatype(key);
size_t alloclen = (datatype == ARES_DATATYPE_BINP) ? len + 1 : len;
ares_bool_t is_nullterm =
(datatype == ARES_DATATYPE_BINP || datatype == ARES_DATATYPE_ABINP)
? ARES_TRUE
: ARES_FALSE;
size_t alloclen = is_nullterm ? len + 1 : len;
unsigned char *temp = ares_malloc(alloclen);
if (temp == NULL) {
@ -1155,7 +1287,7 @@ ares_status_t ares_dns_rr_set_bin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
memcpy(temp, val, len);
/* NULL-term BINP */
if (datatype == ARES_DATATYPE_BINP) {
if (is_nullterm) {
temp[len] = 0;
}
@ -1211,6 +1343,29 @@ ares_status_t ares_dns_rr_set_str(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
return status;
}
ares_status_t ares_dns_rr_set_abin_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
ares__dns_multistring_t *strs)
{
ares__dns_multistring_t **strs_ptr;
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_ABINP) {
return ARES_EFORMERR;
}
strs_ptr = ares_dns_rr_data_ptr(dns_rr, key, NULL);
if (strs_ptr == NULL) {
return ARES_EFORMERR;
}
if (*strs_ptr != NULL) {
ares__dns_multistring_destroy(*strs_ptr);
}
*strs_ptr = strs;
return ARES_SUCCESS;
}
ares_status_t ares_dns_rr_set_opt_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, unsigned short opt,
unsigned char *val, size_t val_len)

@ -218,20 +218,14 @@ static ares_status_t ares_dns_write_rr_str(ares__buf_t *buf,
return ares__buf_append(buf, (const unsigned char *)str, len);
}
static ares_status_t ares_dns_write_rr_binstrs(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
static ares_status_t ares_dns_write_binstr(ares__buf_t *buf,
const unsigned char *bin,
size_t bin_len)
{
const unsigned char *bin;
const unsigned char *ptr;
size_t bin_len;
size_t ptr_len;
ares_status_t status;
bin = ares_dns_rr_get_bin(rr, key, &bin_len);
if (bin == NULL) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
/* split into possible multiple 255-byte or less length strings */
ptr = bin;
ptr_len = bin_len;
@ -262,6 +256,33 @@ static ares_status_t ares_dns_write_rr_binstrs(ares__buf_t *buf,
return ARES_SUCCESS;
}
static ares_status_t ares_dns_write_rr_abin(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
{
ares_status_t status = ARES_EFORMERR;
size_t i;
size_t cnt = ares_dns_rr_get_abin_cnt(rr, key);
if (cnt == 0) {
return ARES_EFORMERR;
}
for (i = 0; i < cnt; i++) {
const unsigned char *bin;
size_t bin_len;
bin = ares_dns_rr_get_abin(rr, key, i, &bin_len);
status = ares_dns_write_binstr(buf, bin, bin_len);
if (status != ARES_SUCCESS) {
break;
}
}
return status;
}
static ares_status_t ares_dns_write_rr_be32(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
@ -419,7 +440,7 @@ static ares_status_t ares_dns_write_rr_txt(ares__buf_t *buf,
ares__llist_t **namelist)
{
(void)namelist;
return ares_dns_write_rr_binstrs(buf, rr, ARES_RR_TXT_DATA);
return ares_dns_write_rr_abin(buf, rr, ARES_RR_TXT_DATA);
}
static ares_status_t ares_dns_write_rr_sig(ares__buf_t *buf,

@ -75,7 +75,7 @@ ares_status_t ares_expand_string_ex(const unsigned char *encoded,
start_len = ares__buf_len(buf);
status =
ares__buf_parse_dns_binstr(buf, ares__buf_len(buf), s, &len, ARES_FALSE);
ares__buf_parse_dns_binstr(buf, ares__buf_len(buf), s, &len);
/* hrm, no way to pass back 'len' with the prototype */
if (status != ARES_SUCCESS) {
goto done;

@ -52,8 +52,9 @@ static int ares__parse_txt_reply(const unsigned char *abuf, size_t alen,
for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) {
const ares_dns_rr_t *rr =
ares_dns_record_rr_get(dnsrec, ARES_SECTION_ANSWER, i);
const unsigned char *ptr;
size_t ptr_len;
size_t j;
size_t cnt;
if (rr == NULL) {
/* Shouldn't be possible */
@ -68,37 +69,44 @@ static int ares__parse_txt_reply(const unsigned char *abuf, size_t alen,
continue;
}
/* Allocate storage for this TXT answer appending it to the list */
txt_curr =
ares_malloc_data(ex ? ARES_DATATYPE_TXT_EXT : ARES_DATATYPE_TXT_REPLY);
if (txt_curr == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
/* Link in the record */
if (txt_last) {
txt_last->next = txt_curr;
} else {
txt_head = txt_curr;
}
txt_last = txt_curr;
/* These days, records are joined, always tag as start */
if (ex) {
txt_curr->record_start = 1;
}
ptr = ares_dns_rr_get_bin(rr, ARES_RR_TXT_DATA, &ptr_len);
txt_curr->txt = ares_malloc(ptr_len + 1);
if (txt_curr->txt == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
cnt = ares_dns_rr_get_abin_cnt(rr, ARES_RR_TXT_DATA);
for (j=0; j<cnt; j++) {
const unsigned char *ptr;
size_t ptr_len;
/* Allocate storage for this TXT answer appending it to the list */
txt_curr =
ares_malloc_data(ex ? ARES_DATATYPE_TXT_EXT : ARES_DATATYPE_TXT_REPLY);
if (txt_curr == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
/* Link in the record */
if (txt_last) {
txt_last->next = txt_curr;
} else {
txt_head = txt_curr;
}
txt_last = txt_curr;
/* Tag start on first for each TXT record */
if (ex && j == 0) {
txt_curr->record_start = 1;
}
ptr = ares_dns_rr_get_abin(rr, ARES_RR_TXT_DATA, j, &ptr_len);
txt_curr->txt = ares_malloc(ptr_len + 1);
if (txt_curr->txt == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
memcpy(txt_curr->txt, ptr, ptr_len);
txt_curr->txt[ptr_len] = 0;
txt_curr->length = ptr_len;
}
memcpy(txt_curr->txt, ptr, ptr_len);
txt_curr->txt[ptr_len] = 0;
txt_curr->length = ptr_len;
}
done:

@ -111,6 +111,7 @@ typedef struct ares_rand_state ares_rand_state;
#include "ares__htable_szvp.h"
#include "ares__htable_asvp.h"
#include "ares__htable_vpvp.h"
#include "ares_dns_multistring.h"
#include "ares__buf.h"
#include "ares_dns_private.h"
#include "ares__iface_ips.h"

@ -84,7 +84,9 @@ static const nv_t configflags[] = {
{ "igntc", ARES_FLAG_IGNTC },
{ "norecurse", ARES_FLAG_NORECURSE },
{ "stayopen", ARES_FLAG_STAYOPEN },
{ "noaliases", ARES_FLAG_NOALIASES }
{ "noaliases", ARES_FLAG_NOALIASES },
{ "edns", ARES_FLAG_EDNS },
{ "dns0x20", ARES_FLAG_DNS0x20 }
};
static const size_t nconfigflags = sizeof(configflags) / sizeof(*configflags);
@ -123,15 +125,19 @@ static void print_help(void)
printf(
" -d : Print some extra debugging output.\n");
printf(
" -f flag : Add a behavior control flag. Possible values are\n"
" -f flag : Add a behavior control flag. May be specified more than once\n"
" to add additional flags. Possible values are:\n"
" igntc - do not retry a truncated query as TCP, just\n"
" return the truncated answer\n"
" noaliases - don't honor the HOSTALIASES environment\n"
" variable\n"
" variable\n");
printf(
" norecurse - don't query upstream servers recursively\n"
" primary - use the first server\n"
" stayopen - don't close the communication sockets\n"
" usevc - use TCP only\n");
" usevc - use TCP only\n"
" edns - use EDNS\n"
" dns0x20 - enable DNS 0x20 support\n");
printf(
" -s server : Connect to the specified DNS server, instead of the\n"
" system's default one(s). Servers are tried in round-robin,\n"
@ -622,6 +628,21 @@ static void print_binp(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
print_opt_binp(binp, len);
}
static void print_abinp(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
size_t i;
size_t cnt = ares_dns_rr_get_abin_cnt(rr, key);
for (i=0; i<cnt; i++) {
size_t len;
const unsigned char *binp = ares_dns_rr_get_abin(rr, key, i, &len);
if (i != 0) {
printf(" ");
}
print_opt_binp(binp, len);
}
}
static void print_rr(const ares_dns_rr_t *rr)
{
const char *name = ares_dns_rr_get_name(rr);
@ -681,6 +702,9 @@ static void print_rr(const ares_dns_rr_t *rr)
case ARES_DATATYPE_BINP:
print_binp(rr, keys[i]);
break;
case ARES_DATATYPE_ABINP:
print_abinp(rr, keys[i]);
break;
case ARES_DATATYPE_OPT:
print_opts(rr, keys[i]);
break;

@ -462,10 +462,14 @@ TEST_F(LibraryTest, DNSRecord) {
EXPECT_EQ(ARES_SUCCESS,
ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ANSWER, "example.com",
ARES_REC_TYPE_TXT, ARES_CLASS_IN, 3600));
const char txt[] = "blah=here blah=there anywhere";
EXPECT_EQ(ARES_SUCCESS,
ares_dns_rr_set_bin(rr, ARES_RR_TXT_DATA, (unsigned char *)txt,
sizeof(txt)));
const char txt1[] = "blah=here blah=there anywhere";
const char txt2[] = "some other record";
EXPECT_EQ(ARES_SUCCESS,
ares_dns_rr_add_abin(rr, ARES_RR_TXT_DATA, (unsigned char *)txt1,
sizeof(txt1)-1));
EXPECT_EQ(ARES_SUCCESS,
ares_dns_rr_add_abin(rr, ARES_RR_TXT_DATA, (unsigned char *)txt2,
sizeof(txt2)-1));
/* SIG */
EXPECT_EQ(ARES_SUCCESS,
ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ANSWER, "example.com",
@ -784,6 +788,17 @@ TEST_F(LibraryTest, DNSRecord) {
ares__buf_append_str(printmsg, (const char *)ares_dns_rr_get_bin(rr, keys[k], &templen));
ares__buf_append_byte(printmsg, '"');
break;
case ARES_DATATYPE_ABINP:
for (size_t a=0; a<ares_dns_rr_get_abin_cnt(rr, keys[k]); a++) {
if (a != 0) {
ares__buf_append_byte(printmsg, ' ');
}
ares__buf_append_byte(printmsg, '"');
size_t templen;
ares__buf_append_str(printmsg, (const char *)ares_dns_rr_get_abin(rr, keys[k], a, &templen));
ares__buf_append_byte(printmsg, '"');
}
break;
case ARES_DATATYPE_OPT:
/* TODO */
break;
@ -1010,7 +1025,7 @@ TEST_F(LibraryTest, BufMisuse) {
EXPECT_EQ((size_t)0, ares__buf_get_position(NULL));
EXPECT_NE(ARES_SUCCESS, ares__buf_set_position(NULL, 0));
EXPECT_NE(ARES_SUCCESS, ares__dns_name_parse(NULL, NULL, ARES_FALSE));
EXPECT_NE(ARES_SUCCESS, ares__buf_parse_dns_binstr(NULL, 0, NULL, NULL, ARES_FALSE));
EXPECT_NE(ARES_SUCCESS, ares__buf_parse_dns_binstr(NULL, 0, NULL, NULL));
}
TEST_F(LibraryTest, HtableMisuse) {

@ -51,11 +51,14 @@ TEST_F(LibraryTest, ParseTxtReplyOK) {
struct ares_txt_reply* txt2 = txt->next;
ASSERT_NE(nullptr, txt2);
std::vector<byte> rsp = std::vector<byte>(expected2a.data(), expected2a.data() + expected2a.size());
rsp.insert(rsp.end(), expected2b.data(), expected2b.data() + expected2b.size());
EXPECT_EQ(rsp,
EXPECT_EQ(std::vector<byte>(expected2a.data(), expected2a.data() + expected2a.size()),
std::vector<byte>(txt2->txt, txt2->txt + txt2->length));
struct ares_txt_reply* txt3 = txt2->next;
ASSERT_NE(nullptr, txt3);
EXPECT_EQ(std::vector<byte>(expected2b.data(), expected2b.data() + expected2b.size()),
std::vector<byte>(txt3->txt, txt3->txt + txt3->length));
EXPECT_EQ(nullptr, txt3->next);
ares_free_data(txt);
}
@ -79,12 +82,16 @@ TEST_F(LibraryTest, ParseTxtExtReplyOK) {
struct ares_txt_ext* txt2 = txt->next;
ASSERT_NE(nullptr, txt2);
std::vector<byte> rsp = std::vector<byte>(expected2a.data(), expected2a.data() + expected2a.size());
rsp.insert(rsp.end(), expected2b.data(), expected2b.data() + expected2b.size());
EXPECT_EQ(rsp,
EXPECT_EQ(std::vector<byte>(expected2a.data(), expected2a.data() + expected2a.size()),
std::vector<byte>(txt2->txt, txt2->txt + txt2->length));
EXPECT_EQ(1, txt2->record_start);
struct ares_txt_ext* txt3 = txt2->next;
ASSERT_NE(nullptr, txt3);
EXPECT_EQ(std::vector<byte>(expected2b.data(), expected2b.data() + expected2b.size()),
std::vector<byte>(txt3->txt, txt3->txt + txt3->length));
EXPECT_EQ(nullptr, txt3->next);
EXPECT_EQ(0, txt3->record_start);
ares_free_data(txt);
}

Loading…
Cancel
Save