1 /* MIT License
2 *
3 * Copyright (c) 2024 Brad House
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * SPDX-License-Identifier: MIT
25 */
26 #include "ares_private.h"
27 #include "ares_dns_private.h"
28
29 typedef struct {
30 unsigned char *data;
31 size_t len;
32 } multistring_data_t;
33
34 struct ares_dns_multistring {
35 /*! whether or not cached concatenated string is valid */
36 ares_bool_t cache_invalidated;
37 /*! combined/concatenated string cache */
38 unsigned char *cache_str;
39 /*! length of combined/concatenated string */
40 size_t cache_str_len;
41 /*! Data making up strings */
42 ares_array_t *strs; /*!< multistring_data_t type */
43 };
44
ares_dns_multistring_free_cb(void * arg)45 static void ares_dns_multistring_free_cb(void *arg)
46 {
47 multistring_data_t *data = arg;
48 if (data == NULL) {
49 return;
50 }
51 ares_free(data->data);
52 }
53
ares_dns_multistring_create(void)54 ares_dns_multistring_t *ares_dns_multistring_create(void)
55 {
56 ares_dns_multistring_t *strs = ares_malloc_zero(sizeof(*strs));
57 if (strs == NULL) {
58 return NULL;
59 }
60
61 strs->strs =
62 ares_array_create(sizeof(multistring_data_t), ares_dns_multistring_free_cb);
63 if (strs->strs == NULL) {
64 ares_free(strs);
65 return NULL;
66 }
67
68 return strs;
69 }
70
ares_dns_multistring_clear(ares_dns_multistring_t * strs)71 void ares_dns_multistring_clear(ares_dns_multistring_t *strs)
72 {
73 if (strs == NULL) {
74 return;
75 }
76
77 while (ares_array_len(strs->strs)) {
78 ares_array_remove_last(strs->strs);
79 }
80 }
81
ares_dns_multistring_destroy(ares_dns_multistring_t * strs)82 void ares_dns_multistring_destroy(ares_dns_multistring_t *strs)
83 {
84 if (strs == NULL) {
85 return;
86 }
87 ares_dns_multistring_clear(strs);
88 ares_array_destroy(strs->strs);
89 ares_free(strs->cache_str);
90 ares_free(strs);
91 }
92
ares_dns_multistring_swap_own(ares_dns_multistring_t * strs,size_t idx,unsigned char * str,size_t len)93 ares_status_t ares_dns_multistring_swap_own(ares_dns_multistring_t *strs,
94 size_t idx, unsigned char *str,
95 size_t len)
96 {
97 multistring_data_t *data;
98
99 if (strs == NULL || str == NULL || len == 0) {
100 return ARES_EFORMERR;
101 }
102
103 strs->cache_invalidated = ARES_TRUE;
104
105 data = ares_array_at(strs->strs, idx);
106 if (data == NULL) {
107 return ARES_EFORMERR;
108 }
109
110 ares_free(data->data);
111 data->data = str;
112 data->len = len;
113 return ARES_SUCCESS;
114 }
115
ares_dns_multistring_del(ares_dns_multistring_t * strs,size_t idx)116 ares_status_t ares_dns_multistring_del(ares_dns_multistring_t *strs, size_t idx)
117 {
118 if (strs == NULL) {
119 return ARES_EFORMERR;
120 }
121
122 strs->cache_invalidated = ARES_TRUE;
123
124 return ares_array_remove_at(strs->strs, idx);
125 }
126
ares_dns_multistring_add_own(ares_dns_multistring_t * strs,unsigned char * str,size_t len)127 ares_status_t ares_dns_multistring_add_own(ares_dns_multistring_t *strs,
128 unsigned char *str, size_t len)
129 {
130 multistring_data_t *data;
131 ares_status_t status;
132
133 if (strs == NULL) {
134 return ARES_EFORMERR;
135 }
136
137 strs->cache_invalidated = ARES_TRUE;
138
139 /* NOTE: its ok to have an empty string added */
140 if (str == NULL && len != 0) {
141 return ARES_EFORMERR;
142 }
143
144 status = ares_array_insert_last((void **)&data, strs->strs);
145 if (status != ARES_SUCCESS) {
146 return status;
147 }
148
149 /* Issue #921, ares_dns_multistring_get() doesn't have a way to indicate
150 * success or fail on a zero-length string which is actually valid. So we
151 * are going to allocate a 1-byte buffer to use as a placeholder in this
152 * case */
153 if (str == NULL) {
154 str = ares_malloc_zero(1);
155 if (str == NULL) {
156 ares_array_remove_last(strs->strs);
157 return ARES_ENOMEM;
158 }
159 }
160
161 data->data = str;
162 data->len = len;
163
164 return ARES_SUCCESS;
165 }
166
ares_dns_multistring_cnt(const ares_dns_multistring_t * strs)167 size_t ares_dns_multistring_cnt(const ares_dns_multistring_t *strs)
168 {
169 if (strs == NULL) {
170 return 0;
171 }
172 return ares_array_len(strs->strs);
173 }
174
175 const unsigned char *
ares_dns_multistring_get(const ares_dns_multistring_t * strs,size_t idx,size_t * len)176 ares_dns_multistring_get(const ares_dns_multistring_t *strs, size_t idx,
177 size_t *len)
178 {
179 const multistring_data_t *data;
180
181 if (strs == NULL || len == NULL) {
182 return NULL;
183 }
184
185 data = ares_array_at_const(strs->strs, idx);
186 if (data == NULL) {
187 return NULL;
188 }
189
190 *len = data->len;
191 return data->data;
192 }
193
ares_dns_multistring_combined(ares_dns_multistring_t * strs,size_t * len)194 const unsigned char *ares_dns_multistring_combined(ares_dns_multistring_t *strs,
195 size_t *len)
196 {
197 ares_buf_t *buf = NULL;
198 size_t i;
199
200 if (strs == NULL || len == NULL) {
201 return NULL;
202 }
203
204 *len = 0;
205
206 /* Return cache if possible */
207 if (!strs->cache_invalidated) {
208 *len = strs->cache_str_len;
209 return strs->cache_str;
210 }
211
212 /* Clear cache */
213 ares_free(strs->cache_str);
214 strs->cache_str = NULL;
215 strs->cache_str_len = 0;
216
217 buf = ares_buf_create();
218
219 for (i = 0; i < ares_array_len(strs->strs); i++) {
220 const multistring_data_t *data = ares_array_at_const(strs->strs, i);
221 if (data == NULL ||
222 ares_buf_append(buf, data->data, data->len) != ARES_SUCCESS) {
223 ares_buf_destroy(buf);
224 return NULL;
225 }
226 }
227
228 strs->cache_str =
229 (unsigned char *)ares_buf_finish_str(buf, &strs->cache_str_len);
230 if (strs->cache_str != NULL) {
231 strs->cache_invalidated = ARES_FALSE;
232 }
233 *len = strs->cache_str_len;
234 return strs->cache_str;
235 }
236
ares_dns_multistring_parse_buf(ares_buf_t * buf,size_t remaining_len,ares_dns_multistring_t ** strs,ares_bool_t validate_printable)237 ares_status_t ares_dns_multistring_parse_buf(ares_buf_t *buf,
238 size_t remaining_len,
239 ares_dns_multistring_t **strs,
240 ares_bool_t validate_printable)
241 {
242 unsigned char len;
243 ares_status_t status = ARES_EBADRESP;
244 size_t orig_len = ares_buf_len(buf);
245
246 if (buf == NULL) {
247 return ARES_EFORMERR;
248 }
249
250 if (remaining_len == 0) {
251 return ARES_EBADRESP;
252 }
253
254 if (strs != NULL) {
255 *strs = ares_dns_multistring_create();
256 if (*strs == NULL) {
257 return ARES_ENOMEM;
258 }
259 }
260
261 while (orig_len - ares_buf_len(buf) < remaining_len) {
262 status = ares_buf_fetch_bytes(buf, &len, 1);
263 if (status != ARES_SUCCESS) {
264 break; /* LCOV_EXCL_LINE: DefensiveCoding */
265 }
266
267
268 /* When used by the _str() parser, it really needs to be validated to
269 * be a valid printable ascii string. Do that here */
270 if (len && validate_printable && ares_buf_len(buf) >= len) {
271 size_t mylen;
272 const char *data = (const char *)ares_buf_peek(buf, &mylen);
273 if (!ares_str_isprint(data, len)) {
274 status = ARES_EBADSTR;
275 break;
276 }
277 }
278
279 if (strs != NULL) {
280 unsigned char *data = NULL;
281 if (len) {
282 status = ares_buf_fetch_bytes_dup(buf, len, ARES_TRUE, &data);
283 if (status != ARES_SUCCESS) {
284 break;
285 }
286 }
287 status = ares_dns_multistring_add_own(*strs, data, len);
288 if (status != ARES_SUCCESS) {
289 ares_free(data);
290 break;
291 }
292 } else {
293 status = ares_buf_consume(buf, len);
294 if (status != ARES_SUCCESS) {
295 break;
296 }
297 }
298
299 }
300
301 if (status != ARES_SUCCESS && strs != NULL) {
302 ares_dns_multistring_destroy(*strs);
303 *strs = NULL;
304 }
305
306 return status;
307 }
308