]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp6-option.c
Merge pull request #18863 from keszybz/cmdline-escaping
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3 Copyright © 2014-2015 Intel Corporation. All rights reserved.
4 ***/
5
6 #include <errno.h>
7 #include <netinet/in.h>
8
9 #include "sd-dhcp6-client.h"
10
11 #include "alloc-util.h"
12 #include "dhcp-identifier.h"
13 #include "dhcp6-internal.h"
14 #include "dhcp6-lease-internal.h"
15 #include "dhcp6-protocol.h"
16 #include "dns-domain.h"
17 #include "memory-util.h"
18 #include "sparse-endian.h"
19 #include "strv.h"
20 #include "unaligned.h"
21
22 typedef struct DHCP6StatusOption {
23 struct DHCP6Option option;
24 be16_t status;
25 char msg[];
26 } _packed_ DHCP6StatusOption;
27
28 typedef struct DHCP6AddressOption {
29 struct DHCP6Option option;
30 struct iaaddr iaaddr;
31 uint8_t options[];
32 } _packed_ DHCP6AddressOption;
33
34 typedef struct DHCP6PDPrefixOption {
35 struct DHCP6Option option;
36 struct iapdprefix iapdprefix;
37 uint8_t options[];
38 } _packed_ DHCP6PDPrefixOption;
39
40 #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
41 #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
42 #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
43
44 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) {
45 DHCP6Option *option;
46
47 assert_return(buf, -EINVAL);
48 assert_return(*buf, -EINVAL);
49 assert_return(buflen, -EINVAL);
50
51 option = (DHCP6Option*) *buf;
52
53 if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
54 return -ENOBUFS;
55
56 option->code = htobe16(optcode);
57 option->len = htobe16(optlen);
58
59 *buf += offsetof(DHCP6Option, data);
60 *buflen -= offsetof(DHCP6Option, data);
61
62 return 0;
63 }
64
65 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
66 size_t optlen, const void *optval) {
67 int r;
68
69 assert_return(optval || optlen == 0, -EINVAL);
70
71 r = option_append_hdr(buf, buflen, code, optlen);
72 if (r < 0)
73 return r;
74
75 memcpy_safe(*buf, optval, optlen);
76
77 *buf += optlen;
78 *buflen -= optlen;
79
80 return 0;
81 }
82
83 int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
84 sd_dhcp6_option *options;
85 int r;
86
87 assert(buf);
88 assert(*buf);
89 assert(buflen);
90 assert(vendor_options);
91
92 ORDERED_HASHMAP_FOREACH(options, vendor_options) {
93 _cleanup_free_ uint8_t *p = NULL;
94 size_t total;
95
96 total = 4 + 2 + 2 + options->length;
97
98 p = malloc(total);
99 if (!p)
100 return -ENOMEM;
101
102 unaligned_write_be32(p, options->enterprise_identifier);
103 unaligned_write_be16(p + 4, options->option);
104 unaligned_write_be16(p + 6, options->length);
105 memcpy(p + 8, options->data, options->length);
106
107 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
108 if (r < 0)
109 return r;
110 }
111
112 return 0;
113 }
114
115 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
116 size_t ia_buflen, ia_addrlen = 0;
117 struct ia_na ia_na;
118 struct ia_ta ia_ta;
119 DHCP6Address *addr;
120 uint8_t *ia_hdr;
121 uint16_t len;
122 void *p;
123 int r;
124
125 assert_return(buf, -EINVAL);
126 assert_return(*buf, -EINVAL);
127 assert_return(buflen, -EINVAL);
128 assert_return(ia, -EINVAL);
129
130 /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
131
132 switch (ia->type) {
133 case SD_DHCP6_OPTION_IA_NA:
134 len = DHCP6_OPTION_IA_NA_LEN;
135 ia_na = (struct ia_na) {
136 .id = ia->ia_na.id,
137 };
138 p = &ia_na;
139 break;
140
141 case SD_DHCP6_OPTION_IA_TA:
142 len = DHCP6_OPTION_IA_TA_LEN;
143 ia_ta = (struct ia_ta) {
144 .id = ia->ia_ta.id,
145 };
146 p = &ia_ta;
147 break;
148
149 default:
150 return -EINVAL;
151 }
152
153 if (*buflen < offsetof(DHCP6Option, data) + len)
154 return -ENOBUFS;
155
156 ia_hdr = *buf;
157 ia_buflen = *buflen;
158
159 *buf += offsetof(DHCP6Option, data);
160 *buflen -= offsetof(DHCP6Option, data);
161
162 memcpy(*buf, p, len);
163
164 *buf += len;
165 *buflen -= len;
166
167 LIST_FOREACH(addresses, addr, ia->addresses) {
168 struct iaaddr a = {
169 .address = addr->iaaddr.address,
170 };
171
172 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr));
173 if (r < 0)
174 return r;
175
176 memcpy(*buf, &a, sizeof(struct iaaddr));
177
178 *buf += sizeof(struct iaaddr);
179 *buflen -= sizeof(struct iaaddr);
180
181 ia_addrlen += offsetof(DHCP6Option, data) + sizeof(struct iaaddr);
182 }
183
184 return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
185 }
186
187 static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Address *prefix) {
188 struct iapdprefix p;
189 int r;
190
191 assert(buf);
192 assert(*buf);
193 assert(buflen);
194 assert(prefix);
195
196 if (prefix->iapdprefix.prefixlen == 0)
197 return -EINVAL;
198
199 /* Do not append T1 and T2. */
200
201 p = (struct iapdprefix) {
202 .prefixlen = prefix->iapdprefix.prefixlen,
203 .address = prefix->iapdprefix.address,
204 };
205
206 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix));
207 if (r < 0)
208 return r;
209
210 memcpy(*buf, &p, sizeof(struct iapdprefix));
211
212 *buf += sizeof(struct iapdprefix);
213 *buflen -= sizeof(struct iapdprefix);
214
215 return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix);
216 }
217
218 int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix) {
219 struct ia_pd ia_pd;
220 size_t len, pd_buflen;
221 uint8_t *pd_hdr;
222 int r;
223
224 assert_return(buf, -EINVAL);
225 assert_return(*buf, -EINVAL);
226 assert_return(buflen, -EINVAL);
227 assert_return(pd, -EINVAL);
228 assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
229
230 /* Do not set T1 and T2. */
231 ia_pd = (struct ia_pd) {
232 .id = pd->ia_pd.id,
233 };
234 len = sizeof(struct ia_pd);
235
236 if (*buflen < offsetof(DHCP6Option, data) + len)
237 return -ENOBUFS;
238
239 pd_hdr = *buf;
240 pd_buflen = *buflen;
241
242 /* The header will be written at the end of this function. */
243 *buf += offsetof(DHCP6Option, data);
244 *buflen -= offsetof(DHCP6Option, data);
245
246 memcpy(*buf, &ia_pd, len);
247
248 *buf += sizeof(struct ia_pd);
249 *buflen -= sizeof(struct ia_pd);
250
251 DHCP6Address *prefix;
252 LIST_FOREACH(addresses, prefix, pd->addresses) {
253 r = option_append_pd_prefix(buf, buflen, prefix);
254 if (r < 0)
255 return r;
256
257 len += r;
258 }
259
260 if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) {
261 r = option_append_pd_prefix(buf, buflen, hint_pd_prefix);
262 if (r < 0)
263 return r;
264
265 len += r;
266 }
267
268 return option_append_hdr(&pd_hdr, &pd_buflen, pd->type, len);
269 }
270
271 int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
272 uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
273 int r;
274
275 assert_return(buf && *buf && buflen && fqdn, -EINVAL);
276
277 buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
278
279 /* Store domain name after flags field */
280 r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
281 if (r <= 0)
282 return r;
283
284 /*
285 * According to RFC 4704, chapter 4.2 only add terminating zero-length
286 * label in case a FQDN is provided. Since dns_name_to_wire_format
287 * always adds terminating zero-length label remove if only a hostname
288 * is provided.
289 */
290 if (dns_name_is_single_label(fqdn))
291 r--;
292
293 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
294
295 return r;
296 }
297
298 int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class) {
299 _cleanup_free_ uint8_t *p = NULL;
300 size_t total = 0, offset = 0;
301 char * const *s;
302
303 assert(buf);
304 assert(*buf);
305 assert(buflen);
306 assert(!strv_isempty(user_class));
307
308 STRV_FOREACH(s, user_class) {
309 size_t len = strlen(*s);
310 uint8_t *q;
311
312 if (len > 0xffff || len == 0)
313 return -EINVAL;
314 q = realloc(p, total + len + 2);
315 if (!q)
316 return -ENOMEM;
317
318 p = q;
319
320 unaligned_write_be16(&p[offset], len);
321 memcpy(&p[offset + 2], *s, len);
322
323 offset += 2 + len;
324 total += 2 + len;
325 }
326
327 return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p);
328 }
329
330 int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *vendor_class) {
331 _cleanup_free_ uint8_t *p = NULL;
332 uint32_t enterprise_identifier;
333 size_t total, offset;
334 char * const *s;
335
336 assert(buf);
337 assert(*buf);
338 assert(buflen);
339 assert(!strv_isempty(vendor_class));
340
341 enterprise_identifier = htobe32(SYSTEMD_PEN);
342
343 p = memdup(&enterprise_identifier, sizeof(enterprise_identifier));
344 if (!p)
345 return -ENOMEM;
346
347 total = sizeof(enterprise_identifier);
348 offset = total;
349
350 STRV_FOREACH(s, vendor_class) {
351 size_t len = strlen(*s);
352 uint8_t *q;
353
354 if (len > UINT16_MAX || len == 0)
355 return -EINVAL;
356
357 q = realloc(p, total + len + 2);
358 if (!q)
359 return -ENOMEM;
360
361 p = q;
362
363 unaligned_write_be16(&p[offset], len);
364 memcpy(&p[offset + 2], *s, len);
365
366 offset += 2 + len;
367 total += 2 + len;
368 }
369
370 return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
371 }
372
373 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
374 DHCP6Option *option = (DHCP6Option*) *buf;
375 uint16_t len;
376
377 assert_return(buf, -EINVAL);
378 assert_return(optcode, -EINVAL);
379 assert_return(optlen, -EINVAL);
380
381 if (*buflen < offsetof(DHCP6Option, data))
382 return -ENOMSG;
383
384 len = be16toh(option->len);
385
386 if (len > *buflen)
387 return -ENOMSG;
388
389 *optcode = be16toh(option->code);
390 *optlen = len;
391
392 *buf += 4;
393 *buflen -= 4;
394
395 return 0;
396 }
397
398 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
399 size_t *optlen, uint8_t **optvalue) {
400 int r;
401
402 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
403
404 r = option_parse_hdr(buf, buflen, optcode, optlen);
405 if (r < 0)
406 return r;
407
408 if (*optlen > *buflen)
409 return -ENOBUFS;
410
411 *optvalue = *buf;
412 *buflen -= *optlen;
413 *buf += *optlen;
414
415 return 0;
416 }
417
418 int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
419 DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
420
421 if (len < sizeof(DHCP6StatusOption) ||
422 be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
423 return -ENOBUFS;
424
425 return be16toh(statusopt->status);
426 }
427
428 static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) {
429 DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
430 DHCP6Address *addr;
431 uint32_t lt_valid, lt_pref;
432 int r;
433
434 if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
435 return -ENOBUFS;
436
437 lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
438 lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
439
440 if (lt_valid == 0 || lt_pref > lt_valid)
441 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
442 "Valid lifetime of an IA address is zero or "
443 "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32,
444 lt_pref, lt_valid);
445
446 if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
447 r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
448 if (r < 0)
449 return r;
450 if (r > 0)
451 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
452 "Non-zero status code '%s' for address is received",
453 dhcp6_message_status_to_string(r));
454 }
455
456 addr = new0(DHCP6Address, 1);
457 if (!addr)
458 return -ENOMEM;
459
460 LIST_INIT(addresses, addr);
461 memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
462
463 LIST_PREPEND(addresses, ia->addresses, addr);
464
465 *ret_lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
466
467 return 0;
468 }
469
470 static int dhcp6_option_parse_pdprefix(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) {
471 DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
472 DHCP6Address *prefix;
473 uint32_t lt_valid, lt_pref;
474 int r;
475
476 if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
477 return -ENOBUFS;
478
479 lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
480 lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
481
482 if (lt_valid == 0 || lt_pref > lt_valid)
483 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
484 "Valid lifetieme of a PD prefix is zero or "
485 "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32,
486 lt_pref, lt_valid);
487
488 if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
489 r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
490 if (r < 0)
491 return r;
492 if (r > 0)
493 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
494 "Non-zero status code '%s' for PD prefix is received",
495 dhcp6_message_status_to_string(r));
496 }
497
498 prefix = new0(DHCP6Address, 1);
499 if (!prefix)
500 return -ENOMEM;
501
502 LIST_INIT(addresses, prefix);
503 memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
504
505 LIST_PREPEND(addresses, ia->addresses, prefix);
506
507 *ret_lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
508
509 return 0;
510 }
511
512 int dhcp6_option_parse_ia(sd_dhcp6_client *client, DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) {
513 uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
514 uint16_t iatype, optlen;
515 size_t iaaddr_offset;
516 int r = 0, status;
517 size_t i, len;
518 uint16_t opt;
519
520 assert_return(ia, -EINVAL);
521 assert_return(!ia->addresses, -EINVAL);
522
523 iatype = be16toh(iaoption->code);
524 len = be16toh(iaoption->len);
525
526 switch (iatype) {
527 case SD_DHCP6_OPTION_IA_NA:
528
529 if (len < DHCP6_OPTION_IA_NA_LEN)
530 return -ENOBUFS;
531
532 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
533 memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
534
535 lt_t1 = be32toh(ia->ia_na.lifetime_t1);
536 lt_t2 = be32toh(ia->ia_na.lifetime_t2);
537
538 if (lt_t1 > lt_t2)
539 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
540 "IA NA T1 %"PRIu32"sec > T2 %"PRIu32"sec",
541 lt_t1, lt_t2);
542
543 break;
544
545 case SD_DHCP6_OPTION_IA_PD:
546
547 if (len < sizeof(ia->ia_pd))
548 return -ENOBUFS;
549
550 iaaddr_offset = sizeof(ia->ia_pd);
551 memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
552
553 lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
554 lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
555
556 if (lt_t1 > lt_t2)
557 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
558 "IA PD T1 %"PRIu32"sec > T2 %"PRIu32"sec",
559 lt_t1, lt_t2);
560
561 break;
562
563 case SD_DHCP6_OPTION_IA_TA:
564 if (len < DHCP6_OPTION_IA_TA_LEN)
565 return -ENOBUFS;
566
567 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
568 memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
569
570 break;
571
572 default:
573 return -ENOMSG;
574 }
575
576 ia->type = iatype;
577 i = iaaddr_offset;
578
579 while (i < len) {
580 DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
581
582 if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
583 return -ENOBUFS;
584
585 opt = be16toh(option->code);
586 optlen = be16toh(option->len);
587
588 switch (opt) {
589 case SD_DHCP6_OPTION_IAADDR:
590
591 if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA))
592 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
593 "IA Address option not in IA NA or TA option");
594
595 r = dhcp6_option_parse_address(client, option, ia, &lt_valid);
596 if (r < 0 && r != -EINVAL)
597 return r;
598 if (r >= 0 && lt_valid < lt_min)
599 lt_min = lt_valid;
600
601 break;
602
603 case SD_DHCP6_OPTION_IA_PD_PREFIX:
604
605 if (ia->type != SD_DHCP6_OPTION_IA_PD)
606 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
607 "IA PD Prefix option not in IA PD option");
608
609 r = dhcp6_option_parse_pdprefix(client, option, ia, &lt_valid);
610 if (r < 0 && r != -EINVAL)
611 return r;
612 if (r >= 0 && lt_valid < lt_min)
613 lt_min = lt_valid;
614
615 break;
616
617 case SD_DHCP6_OPTION_STATUS_CODE:
618
619 status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
620 if (status < 0)
621 return status;
622
623 if (status > 0) {
624 if (ret_status_code)
625 *ret_status_code = status;
626
627 log_dhcp6_client(client, "IA status %s",
628 dhcp6_message_status_to_string(status));
629
630 return 0;
631 }
632
633 break;
634
635 default:
636 log_dhcp6_client(client, "Unknown IA option %d", opt);
637 break;
638 }
639
640 i += sizeof(*option) + optlen;
641 }
642
643 switch(iatype) {
644 case SD_DHCP6_OPTION_IA_NA:
645 if (ia->ia_na.lifetime_t1 == 0 && ia->ia_na.lifetime_t2 == 0 && lt_min != UINT32_MAX) {
646 lt_t1 = lt_min / 2;
647 lt_t2 = lt_min / 10 * 8;
648 ia->ia_na.lifetime_t1 = htobe32(lt_t1);
649 ia->ia_na.lifetime_t2 = htobe32(lt_t2);
650
651 log_dhcp6_client(client, "Computed IA NA T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero",
652 lt_t1, lt_t2);
653 }
654
655 break;
656
657 case SD_DHCP6_OPTION_IA_PD:
658 if (ia->ia_pd.lifetime_t1 == 0 && ia->ia_pd.lifetime_t2 == 0 && lt_min != UINT32_MAX) {
659 lt_t1 = lt_min / 2;
660 lt_t2 = lt_min / 10 * 8;
661 ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
662 ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
663
664 log_dhcp6_client(client, "Computed IA PD T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero",
665 lt_t1, lt_t2);
666 }
667
668 break;
669
670 default:
671 break;
672 }
673
674 if (ret_status_code)
675 *ret_status_code = 0;
676
677 return 1;
678 }
679
680 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
681 struct in6_addr **addrs, size_t count,
682 size_t *allocated) {
683
684 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
685 return -EINVAL;
686
687 if (!GREEDY_REALLOC(*addrs, *allocated,
688 count * sizeof(struct in6_addr) + optlen))
689 return -ENOMEM;
690
691 memcpy(*addrs + count, optval, optlen);
692
693 count += optlen / sizeof(struct in6_addr);
694
695 return count;
696 }
697
698 static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) {
699 _cleanup_free_ char *ret = NULL;
700 size_t n = 0, allocated = 0;
701 const uint8_t *optval = *data;
702 uint16_t optlen = *len;
703 bool first = true;
704 int r;
705
706 if (optlen <= 1)
707 return -ENODATA;
708
709 for (;;) {
710 const char *label;
711 uint8_t c;
712
713 if (optlen == 0)
714 break;
715
716 c = *optval;
717 optval++;
718 optlen--;
719
720 if (c == 0)
721 /* End label */
722 break;
723 if (c > 63)
724 return -EBADMSG;
725 if (c > optlen)
726 return -EMSGSIZE;
727
728 /* Literal label */
729 label = (const char *)optval;
730 optval += c;
731 optlen -= c;
732
733 if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
734 return -ENOMEM;
735
736 if (first)
737 first = false;
738 else
739 ret[n++] = '.';
740
741 r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
742 if (r < 0)
743 return r;
744
745 n += r;
746 }
747
748 if (n) {
749 if (!GREEDY_REALLOC(ret, allocated, n + 1))
750 return -ENOMEM;
751 ret[n] = 0;
752 }
753
754 *out_domain = TAKE_PTR(ret);
755 *data = optval;
756 *len = optlen;
757
758 return n;
759 }
760
761 int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) {
762 _cleanup_free_ char *domain = NULL;
763 int r;
764
765 r = parse_domain(&optval, &optlen, &domain);
766 if (r < 0)
767 return r;
768 if (r == 0)
769 return -ENODATA;
770 if (optlen != 0)
771 return -EINVAL;
772
773 *str = TAKE_PTR(domain);
774 return 0;
775 }
776
777 int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
778 size_t idx = 0;
779 _cleanup_strv_free_ char **names = NULL;
780 int r;
781
782 if (optlen <= 1)
783 return -ENODATA;
784 if (optval[optlen - 1] != '\0')
785 return -EINVAL;
786
787 while (optlen > 0) {
788 _cleanup_free_ char *ret = NULL;
789
790 r = parse_domain(&optval, &optlen, &ret);
791 if (r < 0)
792 return r;
793 if (r == 0)
794 continue;
795
796 r = strv_extend(&names, ret);
797 if (r < 0)
798 return r;
799
800 idx++;
801 }
802
803 *str_arr = TAKE_PTR(names);
804
805 return idx;
806 }
807
808 static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
809 if (!i)
810 return NULL;
811
812 free(i->data);
813 return mfree(i);
814 }
815
816 int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
817 assert_return(ret, -EINVAL);
818 assert_return(length == 0 || data, -EINVAL);
819
820 _cleanup_free_ void *q = memdup(data, length);
821 if (!q)
822 return -ENOMEM;
823
824 sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
825 if (!p)
826 return -ENOMEM;
827
828 *p = (sd_dhcp6_option) {
829 .n_ref = 1,
830 .option = option,
831 .enterprise_identifier = enterprise_identifier,
832 .length = length,
833 .data = TAKE_PTR(q),
834 };
835
836 *ret = p;
837 return 0;
838 }
839
840 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
841 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
842 dhcp6_option_hash_ops,
843 void,
844 trivial_hash_func,
845 trivial_compare_func,
846 sd_dhcp6_option,
847 sd_dhcp6_option_unref);