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 "dhcp6-internal.h"
13 #include "dhcp6-option.h"
14 #include "dhcp6-protocol.h"
15 #include "dns-domain.h"
16 #include "escape.h"
17 #include "memory-util.h"
18 #include "strv.h"
19 #include "unaligned.h"
20 
21 #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
22 #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
23 #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
24 
dhcp6_option_can_request(uint16_t option)25 bool dhcp6_option_can_request(uint16_t option) {
26         /* See Client ORO field in
27          * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2 */
28 
29         switch (option) {
30         case SD_DHCP6_OPTION_CLIENTID:
31         case SD_DHCP6_OPTION_SERVERID:
32         case SD_DHCP6_OPTION_IA_NA:
33         case SD_DHCP6_OPTION_IA_TA:
34         case SD_DHCP6_OPTION_IAADDR:
35         case SD_DHCP6_OPTION_ORO:
36         case SD_DHCP6_OPTION_PREFERENCE:
37         case SD_DHCP6_OPTION_ELAPSED_TIME:
38         case SD_DHCP6_OPTION_RELAY_MSG:
39         case SD_DHCP6_OPTION_AUTH:
40         case SD_DHCP6_OPTION_UNICAST:
41         case SD_DHCP6_OPTION_STATUS_CODE:
42         case SD_DHCP6_OPTION_RAPID_COMMIT:
43         case SD_DHCP6_OPTION_USER_CLASS:
44         case SD_DHCP6_OPTION_VENDOR_CLASS:
45                 return false;
46         case SD_DHCP6_OPTION_VENDOR_OPTS:
47                 return true;
48         case SD_DHCP6_OPTION_INTERFACE_ID:
49         case SD_DHCP6_OPTION_RECONF_MSG:
50         case SD_DHCP6_OPTION_RECONF_ACCEPT:
51                 return false;
52         case SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME:
53         case SD_DHCP6_OPTION_SIP_SERVER_ADDRESS:
54         case SD_DHCP6_OPTION_DNS_SERVER:
55         case SD_DHCP6_OPTION_DOMAIN:
56                 return true;
57         case SD_DHCP6_OPTION_IA_PD:
58         case SD_DHCP6_OPTION_IA_PD_PREFIX:
59                 return false;
60         case SD_DHCP6_OPTION_NIS_SERVER:
61         case SD_DHCP6_OPTION_NISP_SERVER:
62         case SD_DHCP6_OPTION_NIS_DOMAIN_NAME:
63         case SD_DHCP6_OPTION_NISP_DOMAIN_NAME:
64         case SD_DHCP6_OPTION_SNTP_SERVER:
65                 return true;
66         case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
67                 return false; /* This is automatically set when sending INFORMATION_REQUEST message. */
68         case SD_DHCP6_OPTION_BCMCS_SERVER_D:
69         case SD_DHCP6_OPTION_BCMCS_SERVER_A:
70         case SD_DHCP6_OPTION_GEOCONF_CIVIC:
71                 return true;
72         case SD_DHCP6_OPTION_REMOTE_ID:
73         case SD_DHCP6_OPTION_SUBSCRIBER_ID:
74                 return false;
75         case SD_DHCP6_OPTION_CLIENT_FQDN:
76         case SD_DHCP6_OPTION_PANA_AGENT:
77         case SD_DHCP6_OPTION_POSIX_TIMEZONE:
78         case SD_DHCP6_OPTION_TZDB_TIMEZONE:
79                 return true;
80         case SD_DHCP6_OPTION_ERO:
81         case SD_DHCP6_OPTION_LQ_QUERY:
82         case SD_DHCP6_OPTION_CLIENT_DATA:
83         case SD_DHCP6_OPTION_CLT_TIME:
84         case SD_DHCP6_OPTION_LQ_RELAY_DATA:
85         case SD_DHCP6_OPTION_LQ_CLIENT_LINK:
86                 return false;
87         case SD_DHCP6_OPTION_MIP6_HNIDF:
88         case SD_DHCP6_OPTION_MIP6_VDINF:
89         case SD_DHCP6_OPTION_V6_LOST:
90         case SD_DHCP6_OPTION_CAPWAP_AC_V6:
91                 return true;
92         case SD_DHCP6_OPTION_RELAY_ID:
93                 return false;
94         case SD_DHCP6_OPTION_IPV6_ADDRESS_MOS:
95         case SD_DHCP6_OPTION_IPV6_FQDN_MOS:
96         case SD_DHCP6_OPTION_NTP_SERVER:
97         case SD_DHCP6_OPTION_V6_ACCESS_DOMAIN:
98         case SD_DHCP6_OPTION_SIP_UA_CS_LIST:
99         case SD_DHCP6_OPTION_BOOTFILE_URL:
100         case SD_DHCP6_OPTION_BOOTFILE_PARAM:
101                 return true;
102         case SD_DHCP6_OPTION_CLIENT_ARCH_TYPE:
103                 return false;
104         case SD_DHCP6_OPTION_NII:
105         case SD_DHCP6_OPTION_GEOLOCATION:
106         case SD_DHCP6_OPTION_AFTR_NAME:
107         case SD_DHCP6_OPTION_ERP_LOCAL_DOMAIN_NAME:
108                 return true;
109         case SD_DHCP6_OPTION_RSOO:
110                 return false;
111         case SD_DHCP6_OPTION_PD_EXCLUDE:
112                 return true;
113         case SD_DHCP6_OPTION_VSS:
114                 return false;
115         case SD_DHCP6_OPTION_MIP6_IDINF:
116         case SD_DHCP6_OPTION_MIP6_UDINF:
117         case SD_DHCP6_OPTION_MIP6_HNP:
118         case SD_DHCP6_OPTION_MIP6_HAA:
119         case SD_DHCP6_OPTION_MIP6_HAF:
120         case SD_DHCP6_OPTION_RDNSS_SELECTION:
121         case SD_DHCP6_OPTION_KRB_PRINCIPAL_NAME:
122         case SD_DHCP6_OPTION_KRB_REALM_NAME:
123         case SD_DHCP6_OPTION_KRB_DEFAULT_REALM_NAME:
124         case SD_DHCP6_OPTION_KRB_KDC:
125                 return true;
126         case SD_DHCP6_OPTION_CLIENT_LINKLAYER_ADDR:
127         case SD_DHCP6_OPTION_LINK_ADDRESS:
128         case SD_DHCP6_OPTION_RADIUS:
129         case SD_DHCP6_OPTION_SOL_MAX_RT: /* Automatically set when sending SOLICIT message. */
130         case SD_DHCP6_OPTION_INF_MAX_RT: /* Automatically set when sending INFORMATION_REQUEST message. */
131                 return false;
132         case SD_DHCP6_OPTION_ADDRSEL:
133         case SD_DHCP6_OPTION_ADDRSEL_TABLE:
134         case SD_DHCP6_OPTION_V6_PCP_SERVER:
135                 return true;
136         case SD_DHCP6_OPTION_DHCPV4_MSG:
137                 return false;
138         case SD_DHCP6_OPTION_DHCP4_O_DHCP6_SERVER:
139                 return true;
140         case SD_DHCP6_OPTION_S46_RULE:
141                 return false;
142         case SD_DHCP6_OPTION_S46_BR:
143                 return true;
144         case SD_DHCP6_OPTION_S46_DMR:
145         case SD_DHCP6_OPTION_S46_V4V6BIND:
146         case SD_DHCP6_OPTION_S46_PORTPARAMS:
147                 return false;
148         case SD_DHCP6_OPTION_S46_CONT_MAPE:
149         case SD_DHCP6_OPTION_S46_CONT_MAPT:
150         case SD_DHCP6_OPTION_S46_CONT_LW:
151         case SD_DHCP6_OPTION_4RD:
152         case SD_DHCP6_OPTION_4RD_MAP_RULE:
153         case SD_DHCP6_OPTION_4RD_NON_MAP_RULE:
154                 return true;
155         case SD_DHCP6_OPTION_LQ_BASE_TIME:
156         case SD_DHCP6_OPTION_LQ_START_TIME:
157         case SD_DHCP6_OPTION_LQ_END_TIME:
158                 return false;
159         case SD_DHCP6_OPTION_CAPTIVE_PORTAL:
160         case SD_DHCP6_OPTION_MPL_PARAMETERS:
161                 return true;
162         case SD_DHCP6_OPTION_ANI_ATT:
163         case SD_DHCP6_OPTION_ANI_NETWORK_NAME:
164         case SD_DHCP6_OPTION_ANI_AP_NAME:
165         case SD_DHCP6_OPTION_ANI_AP_BSSID:
166         case SD_DHCP6_OPTION_ANI_OPERATOR_ID:
167         case SD_DHCP6_OPTION_ANI_OPERATOR_REALM:
168                 return false;
169         case SD_DHCP6_OPTION_S46_PRIORITY:
170                 return true;
171         case SD_DHCP6_OPTION_MUD_URL_V6:
172                 return false;
173         case SD_DHCP6_OPTION_V6_PREFIX64:
174                 return true;
175         case SD_DHCP6_OPTION_F_BINDING_STATUS:
176         case SD_DHCP6_OPTION_F_CONNECT_FLAGS:
177         case SD_DHCP6_OPTION_F_DNS_REMOVAL_INFO:
178         case SD_DHCP6_OPTION_F_DNS_HOST_NAME:
179         case SD_DHCP6_OPTION_F_DNS_ZONE_NAME:
180         case SD_DHCP6_OPTION_F_DNS_FLAGS:
181         case SD_DHCP6_OPTION_F_EXPIRATION_TIME:
182         case SD_DHCP6_OPTION_F_MAX_UNACKED_BNDUPD:
183         case SD_DHCP6_OPTION_F_MCLT:
184         case SD_DHCP6_OPTION_F_PARTNER_LIFETIME:
185         case SD_DHCP6_OPTION_F_PARTNER_LIFETIME_SENT:
186         case SD_DHCP6_OPTION_F_PARTNER_DOWN_TIME:
187         case SD_DHCP6_OPTION_F_PARTNER_RAW_CLT_TIME:
188         case SD_DHCP6_OPTION_F_PROTOCOL_VERSION:
189         case SD_DHCP6_OPTION_F_KEEPALIVE_TIME:
190         case SD_DHCP6_OPTION_F_RECONFIGURE_DATA:
191         case SD_DHCP6_OPTION_F_RELATIONSHIP_NAME:
192         case SD_DHCP6_OPTION_F_SERVER_FLAGS:
193         case SD_DHCP6_OPTION_F_SERVER_STATE:
194         case SD_DHCP6_OPTION_F_START_TIME_OF_STATE:
195         case SD_DHCP6_OPTION_F_STATE_EXPIRATION_TIME:
196         case SD_DHCP6_OPTION_RELAY_PORT:
197                 return false;
198         case SD_DHCP6_OPTION_V6_SZTP_REDIRECT:
199         case SD_DHCP6_OPTION_S46_BIND_IPV6_PREFIX:
200                 return true;
201         case SD_DHCP6_OPTION_IA_LL:
202         case SD_DHCP6_OPTION_LLADDR:
203         case SD_DHCP6_OPTION_SLAP_QUAD:
204                 return false;
205         case SD_DHCP6_OPTION_V6_DOTS_RI:
206         case SD_DHCP6_OPTION_V6_DOTS_ADDRESS:
207         case SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF:
208                 return true;
209         default:
210                 return false;
211         }
212 }
213 
option_append_hdr(uint8_t ** buf,size_t * buflen,uint16_t optcode,size_t optlen)214 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) {
215         assert_return(buf, -EINVAL);
216         assert_return(*buf, -EINVAL);
217         assert_return(buflen, -EINVAL);
218 
219         if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
220                 return -ENOBUFS;
221 
222         unaligned_write_be16(*buf + offsetof(DHCP6Option, code), optcode);
223         unaligned_write_be16(*buf + offsetof(DHCP6Option, len), optlen);
224 
225         *buf += offsetof(DHCP6Option, data);
226         *buflen -= offsetof(DHCP6Option, data);
227 
228         return 0;
229 }
230 
dhcp6_option_append(uint8_t ** buf,size_t * buflen,uint16_t code,size_t optlen,const void * optval)231 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
232                         size_t optlen, const void *optval) {
233         int r;
234 
235         assert_return(optval || optlen == 0, -EINVAL);
236 
237         r = option_append_hdr(buf, buflen, code, optlen);
238         if (r < 0)
239                 return r;
240 
241         *buf = mempcpy_safe(*buf, optval, optlen);
242         *buflen -= optlen;
243 
244         return 0;
245 }
246 
dhcp6_option_append_vendor_option(uint8_t ** buf,size_t * buflen,OrderedSet * vendor_options)247 int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet *vendor_options) {
248         sd_dhcp6_option *options;
249         int r;
250 
251         assert(buf);
252         assert(*buf);
253         assert(buflen);
254         assert(vendor_options);
255 
256         ORDERED_SET_FOREACH(options, vendor_options) {
257                 _cleanup_free_ uint8_t *p = NULL;
258                 size_t total;
259 
260                 total = 4 + 2 + 2 + options->length;
261 
262                 p = malloc(total);
263                 if (!p)
264                         return -ENOMEM;
265 
266                 unaligned_write_be32(p, options->enterprise_identifier);
267                 unaligned_write_be16(p + 4, options->option);
268                 unaligned_write_be16(p + 6, options->length);
269                 memcpy(p + 8, options->data, options->length);
270 
271                 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
272                 if (r < 0)
273                         return r;
274         }
275 
276         return 0;
277 }
278 
option_append_ia_address(uint8_t ** buf,size_t * buflen,const struct iaaddr * address)279 static int option_append_ia_address(uint8_t **buf, size_t *buflen, const struct iaaddr *address) {
280         struct iaaddr a;
281         int r;
282 
283         assert(buf);
284         assert(*buf);
285         assert(buflen);
286         assert(address);
287 
288         /* Do not append T1 and T2. */
289         a = (struct iaaddr) {
290                 .address = address->address,
291         };
292 
293         r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr));
294         if (r < 0)
295                 return r;
296 
297         *buf = mempcpy(*buf, &a, sizeof(struct iaaddr));
298         *buflen -= sizeof(struct iaaddr);
299 
300         return offsetof(DHCP6Option, data) + sizeof(struct iaaddr);
301 }
302 
option_append_pd_prefix(uint8_t ** buf,size_t * buflen,const struct iapdprefix * prefix)303 static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const struct iapdprefix *prefix) {
304         struct iapdprefix p;
305         int r;
306 
307         assert(buf);
308         assert(*buf);
309         assert(buflen);
310         assert(prefix);
311 
312         if (prefix->prefixlen == 0)
313                 return -EINVAL;
314 
315         /* Do not append T1 and T2. */
316         p = (struct iapdprefix) {
317                 .prefixlen = prefix->prefixlen,
318                 .address = prefix->address,
319         };
320 
321         r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix));
322         if (r < 0)
323                 return r;
324 
325         *buf = mempcpy(*buf, &p, sizeof(struct iapdprefix));
326         *buflen -= sizeof(struct iapdprefix);
327 
328         return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix);
329 }
330 
dhcp6_option_append_ia(uint8_t ** buf,size_t * buflen,const DHCP6IA * ia)331 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
332         struct ia_header header;
333         size_t ia_buflen;
334         uint8_t *ia_hdr;
335         uint16_t len;
336         int r;
337 
338         assert_return(buf, -EINVAL);
339         assert_return(*buf, -EINVAL);
340         assert_return(buflen, -EINVAL);
341         assert_return(ia, -EINVAL);
342 
343         /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
344 
345         switch (ia->type) {
346         case SD_DHCP6_OPTION_IA_NA:
347         case SD_DHCP6_OPTION_IA_PD:
348                 len = sizeof(struct ia_header);
349                 header = (struct ia_header) {
350                         .id = ia->header.id,
351                 };
352                 break;
353 
354         case SD_DHCP6_OPTION_IA_TA:
355                 len = sizeof(header.id); /* IA_TA does not have lifetime. */
356                 header = (struct ia_header) {
357                         .id = ia->header.id,
358                 };
359                 break;
360 
361         default:
362                 assert_not_reached();
363         }
364 
365         if (*buflen < offsetof(DHCP6Option, data) + len)
366                 return -ENOBUFS;
367 
368         ia_hdr = *buf;
369         ia_buflen = *buflen;
370 
371         /* The header will be written at the end of this function. */
372         *buf += offsetof(DHCP6Option, data);
373         *buflen -= offsetof(DHCP6Option, data);
374 
375         *buf = mempcpy(*buf, &header, len);
376         *buflen -= len;
377 
378         LIST_FOREACH(addresses, addr, ia->addresses) {
379                 if (ia->type == SD_DHCP6_OPTION_IA_PD)
380                         r = option_append_pd_prefix(buf, buflen, &addr->iapdprefix);
381                 else
382                         r = option_append_ia_address(buf, buflen, &addr->iaaddr);
383                 if (r < 0)
384                         return r;
385 
386                 len += r;
387         }
388 
389         return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len);
390 }
391 
dhcp6_option_append_fqdn(uint8_t ** buf,size_t * buflen,const char * fqdn)392 int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
393         uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
394         int r;
395 
396         assert_return(buf && *buf && buflen && fqdn, -EINVAL);
397 
398         buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
399 
400         /* Store domain name after flags field */
401         r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1,  false);
402         if (r <= 0)
403                 return r;
404 
405         /*
406          * According to RFC 4704, chapter 4.2 only add terminating zero-length
407          * label in case a FQDN is provided. Since dns_name_to_wire_format
408          * always adds terminating zero-length label remove if only a hostname
409          * is provided.
410          */
411         if (dns_name_is_single_label(fqdn))
412                 r--;
413 
414         r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_CLIENT_FQDN, 1 + r, buffer);
415 
416         return r;
417 }
418 
dhcp6_option_append_user_class(uint8_t ** buf,size_t * buflen,char * const * user_class)419 int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class) {
420         _cleanup_free_ uint8_t *p = NULL;
421         size_t total = 0, offset = 0;
422 
423         assert(buf);
424         assert(*buf);
425         assert(buflen);
426         assert(!strv_isempty(user_class));
427 
428         STRV_FOREACH(s, user_class) {
429                 size_t len = strlen(*s);
430                 uint8_t *q;
431 
432                 if (len > 0xffff || len == 0)
433                         return -EINVAL;
434                 q = realloc(p, total + len + 2);
435                 if (!q)
436                         return -ENOMEM;
437 
438                 p = q;
439 
440                 unaligned_write_be16(&p[offset], len);
441                 memcpy(&p[offset + 2], *s, len);
442 
443                 offset += 2 + len;
444                 total += 2 + len;
445         }
446 
447         return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p);
448 }
449 
dhcp6_option_append_vendor_class(uint8_t ** buf,size_t * buflen,char * const * vendor_class)450 int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *vendor_class) {
451         _cleanup_free_ uint8_t *p = NULL;
452         uint32_t enterprise_identifier;
453         size_t total, offset;
454 
455         assert(buf);
456         assert(*buf);
457         assert(buflen);
458         assert(!strv_isempty(vendor_class));
459 
460         enterprise_identifier = htobe32(SYSTEMD_PEN);
461 
462         p = memdup(&enterprise_identifier, sizeof(enterprise_identifier));
463         if (!p)
464                 return -ENOMEM;
465 
466         total = sizeof(enterprise_identifier);
467         offset = total;
468 
469         STRV_FOREACH(s, vendor_class) {
470                 size_t len = strlen(*s);
471                 uint8_t *q;
472 
473                 if (len > UINT16_MAX || len == 0)
474                         return -EINVAL;
475 
476                 q = realloc(p, total + len + 2);
477                 if (!q)
478                         return -ENOMEM;
479 
480                 p = q;
481 
482                 unaligned_write_be16(&p[offset], len);
483                 memcpy(&p[offset + 2], *s, len);
484 
485                 offset += 2 + len;
486                 total += 2 + len;
487         }
488 
489         return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
490 }
491 
dhcp6_option_parse(const uint8_t * buf,size_t buflen,size_t * offset,uint16_t * ret_option_code,size_t * ret_option_data_len,const uint8_t ** ret_option_data)492 int dhcp6_option_parse(
493                 const uint8_t *buf,
494                 size_t buflen,
495                 size_t *offset,
496                 uint16_t *ret_option_code,
497                 size_t *ret_option_data_len,
498                 const uint8_t **ret_option_data) {
499 
500         size_t len;
501 
502         assert(buf);
503         assert(offset);
504         assert(ret_option_code);
505         assert(ret_option_data_len);
506         assert(ret_option_data);
507 
508         if (buflen < offsetof(DHCP6Option, data))
509                 return -EBADMSG;
510 
511         if (*offset >= buflen - offsetof(DHCP6Option, data))
512                 return -EBADMSG;
513 
514         len = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, len));
515 
516         if (len > buflen - offsetof(DHCP6Option, data) - *offset)
517                 return -EBADMSG;
518 
519         *ret_option_code = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, code));
520         *ret_option_data_len = len;
521         *ret_option_data = buf + *offset + offsetof(DHCP6Option, data);
522         *offset += offsetof(DHCP6Option, data) + len;
523 
524         return 0;
525 }
526 
dhcp6_option_parse_status(const uint8_t * data,size_t data_len,char ** ret_status_message)527 int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
528         assert(data);
529 
530         if (data_len < sizeof(uint16_t))
531                 return -EBADMSG;
532 
533         if (ret_status_message) {
534                 char *msg;
535 
536                 /* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415.
537                  * Let's escape unsafe characters for safety. */
538                 msg = cescape_length((const char*) (data + sizeof(uint16_t)), data_len - sizeof(uint16_t));
539                 if (!msg)
540                         return -ENOMEM;
541 
542                 *ret_status_message = msg;
543         }
544 
545         return unaligned_read_be16(data);
546 }
547 
dhcp6_option_parse_ia_options(sd_dhcp6_client * client,const uint8_t * buf,size_t buflen)548 static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
549         int r;
550 
551         assert(buf || buflen == 0);
552 
553         for (size_t offset = 0; offset < buflen;) {
554                 const uint8_t *data;
555                 size_t data_len;
556                 uint16_t code;
557 
558                 r = dhcp6_option_parse(buf, buflen, &offset, &code, &data_len, &data);
559                 if (r < 0)
560                         return r;
561 
562                 switch (code) {
563                 case SD_DHCP6_OPTION_STATUS_CODE: {
564                         _cleanup_free_ char *msg = NULL;
565 
566                         r = dhcp6_option_parse_status(data, data_len, &msg);
567                         if (r == -ENOMEM)
568                                 return r;
569                         if (r > 0)
570                                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
571                                                               "Received an IA address or PD prefix option with non-zero status: %s%s%s",
572                                                               strempty(msg), isempty(msg) ? "" : ": ",
573                                                               dhcp6_message_status_to_string(r));
574                         if (r < 0)
575                                 /* Let's log but ignore the invalid status option. */
576                                 log_dhcp6_client_errno(client, r,
577                                                        "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m");
578                         break;
579                 }
580                 default:
581                         log_dhcp6_client(client, "Received an unknown sub option %u in IA address or PD prefix, ignoring.", code);
582                 }
583         }
584 
585         return 0;
586 }
587 
dhcp6_option_parse_ia_address(sd_dhcp6_client * client,DHCP6IA * ia,const uint8_t * data,size_t len)588 static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
589         _cleanup_free_ DHCP6Address *a = NULL;
590         uint32_t lt_valid, lt_pref;
591         int r;
592 
593         assert(ia);
594         assert(data || len == 0);
595 
596         if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA))
597                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
598                                               "Received an IA address sub-option in an invalid option, ignoring.");
599 
600         if (len < sizeof(struct iaaddr))
601                 return -EBADMSG;
602 
603         a = new(DHCP6Address, 1);
604         if (!a)
605                 return -ENOMEM;
606 
607         memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
608 
609         lt_valid = be32toh(a->iaaddr.lifetime_valid);
610         lt_pref = be32toh(a->iaaddr.lifetime_preferred);
611 
612         if (lt_valid == 0)
613                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
614                                               "Received an IA address with zero valid lifetime, ignoring.");
615         if (lt_pref > lt_valid)
616                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
617                                               "Received an IA address with preferred lifetime %"PRIu32
618                                               " larger than valid lifetime %"PRIu32", ignoring.",
619                                               lt_pref, lt_valid);
620 
621         if (len > sizeof(struct iaaddr)) {
622                 r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iaaddr), len - sizeof(struct iaaddr));
623                 if (r < 0)
624                         return r;
625         }
626 
627         LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
628         return 0;
629 }
630 
dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client * client,DHCP6IA * ia,const uint8_t * data,size_t len)631 static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
632         _cleanup_free_ DHCP6Address *a = NULL;
633         uint32_t lt_valid, lt_pref;
634         int r;
635 
636         assert(ia);
637         assert(data || len == 0);
638 
639         if (ia->type != SD_DHCP6_OPTION_IA_PD)
640                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
641                                               "Received an PD prefix sub-option in an invalid option, ignoring");
642 
643         if (len < sizeof(struct iapdprefix))
644                 return -EBADMSG;
645 
646         a = new(DHCP6Address, 1);
647         if (!a)
648                 return -ENOMEM;
649 
650         memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
651 
652         lt_valid = be32toh(a->iapdprefix.lifetime_valid);
653         lt_pref = be32toh(a->iapdprefix.lifetime_preferred);
654 
655         if (lt_valid == 0)
656                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
657                                               "Received a PD prefix with zero valid lifetime, ignoring.");
658         if (lt_pref > lt_valid)
659                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
660                                               "Received a PD prefix with preferred lifetime %"PRIu32
661                                               " larger than valid lifetime %"PRIu32", ignoring.",
662                                               lt_pref, lt_valid);
663 
664         if (len > sizeof(struct iapdprefix)) {
665                 r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix));
666                 if (r < 0)
667                         return r;
668         }
669 
670         LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
671         return 0;
672 }
673 
dhcp6_option_parse_ia(sd_dhcp6_client * client,be32_t iaid,uint16_t option_code,size_t option_data_len,const uint8_t * option_data,DHCP6IA ** ret)674 int dhcp6_option_parse_ia(
675                 sd_dhcp6_client *client,
676                 be32_t iaid,
677                 uint16_t option_code,
678                 size_t option_data_len,
679                 const uint8_t *option_data,
680                 DHCP6IA **ret) {
681 
682         _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
683         uint32_t lt_t1, lt_t2;
684         size_t header_len;
685         int r;
686 
687         assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD));
688         assert(option_data || option_data_len == 0);
689         assert(ret);
690 
691         /* This will return the following:
692          * -ENOMEM: memory allocation error,
693          * -ENOANO: unmatching IAID,
694          * -EINVAL: non-zero status code, or invalid lifetime,
695          * -EBADMSG: invalid message format,
696          * -ENODATA: no valid address or PD prefix,
697          * 0: success. */
698 
699         switch (option_code) {
700         case SD_DHCP6_OPTION_IA_NA:
701         case SD_DHCP6_OPTION_IA_PD:
702                 header_len = sizeof(struct ia_header);
703                 break;
704 
705         case SD_DHCP6_OPTION_IA_TA:
706                 header_len = sizeof(be32_t); /* IA_TA does not have lifetime. */
707                 break;
708 
709         default:
710                 assert_not_reached();
711         }
712 
713         if (option_data_len < header_len)
714                 return -EBADMSG;
715 
716         ia = new(DHCP6IA, 1);
717         if (!ia)
718                 return -ENOMEM;
719 
720         *ia = (DHCP6IA) {
721                 .type = option_code,
722         };
723         memcpy(&ia->header, option_data, header_len);
724 
725         /* According to RFC8415, IAs which do not match the client's IAID should be ignored,
726          * but not necessary to ignore or refuse the whole message. */
727         if (ia->header.id != iaid)
728                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO),
729                                               "Received an IA option with a different IAID "
730                                               "from the one chosen by the client, ignoring.");
731 
732         /* It is not necessary to check if the lifetime_t2 is zero here, as in that case it will be updated later. */
733         lt_t1 = be32toh(ia->header.lifetime_t1);
734         lt_t2 = be32toh(ia->header.lifetime_t2);
735 
736         if (lt_t1 > lt_t2)
737                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
738                                               "Received an IA option with T1 %"PRIu32"sec > T2 %"PRIu32"sec, ignoring.",
739                                               lt_t1, lt_t2);
740         if (lt_t1 == 0 && lt_t2 > 0)
741                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
742                                               "Received an IA option with zero T1 and non-zero T2 (%"PRIu32"sec), ignoring.",
743                                               lt_t2);
744 
745         for (size_t offset = header_len; offset < option_data_len;) {
746                 const uint8_t *subdata;
747                 size_t subdata_len;
748                 uint16_t subopt;
749 
750                 r = dhcp6_option_parse(option_data, option_data_len, &offset, &subopt, &subdata_len, &subdata);
751                 if (r < 0)
752                         return r;
753 
754                 switch (subopt) {
755                 case SD_DHCP6_OPTION_IAADDR: {
756                         r = dhcp6_option_parse_ia_address(client, ia, subdata, subdata_len);
757                         if (r == -ENOMEM)
758                                 return r;
759 
760                         /* Ignore non-critical errors in the sub-option. */
761                         break;
762                 }
763                 case SD_DHCP6_OPTION_IA_PD_PREFIX: {
764                         r = dhcp6_option_parse_ia_pdprefix(client, ia, subdata, subdata_len);
765                         if (r == -ENOMEM)
766                                 return r;
767 
768                         /* Ignore non-critical errors in the sub-option. */
769                         break;
770                 }
771                 case SD_DHCP6_OPTION_STATUS_CODE: {
772                         _cleanup_free_ char *msg = NULL;
773 
774                         r = dhcp6_option_parse_status(subdata, subdata_len, &msg);
775                         if (r == -ENOMEM)
776                                 return r;
777                         if (r > 0)
778                                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
779                                                               "Received an IA option with non-zero status: %s%s%s",
780                                                               strempty(msg), isempty(msg) ? "" : ": ",
781                                                               dhcp6_message_status_to_string(r));
782                         if (r < 0)
783                                 log_dhcp6_client_errno(client, r,
784                                                        "Received an IA option with an invalid status sub option, ignoring: %m");
785                         break;
786                 }
787                 default:
788                         log_dhcp6_client(client, "Received an IA option with an unknown sub-option %u, ignoring", subopt);
789                 }
790         }
791 
792         if (!ia->addresses)
793                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENODATA),
794                                               "Received an IA option without valid IA addresses or PD prefixes, ignoring.");
795 
796         *ret = TAKE_PTR(ia);
797         return 0;
798 }
799 
dhcp6_option_parse_addresses(const uint8_t * optval,size_t optlen,struct in6_addr ** addrs,size_t * count)800 int dhcp6_option_parse_addresses(
801                 const uint8_t *optval,
802                 size_t optlen,
803                 struct in6_addr **addrs,
804                 size_t *count) {
805 
806         assert(optval);
807         assert(addrs);
808         assert(count);
809 
810         if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
811                 return -EBADMSG;
812 
813         if (!GREEDY_REALLOC(*addrs, *count + optlen / sizeof(struct in6_addr)))
814                 return -ENOMEM;
815 
816         memcpy(*addrs + *count, optval, optlen);
817         *count += optlen / sizeof(struct in6_addr);
818 
819         return 0;
820 }
821 
parse_domain(const uint8_t ** data,size_t * len,char ** ret)822 static int parse_domain(const uint8_t **data, size_t *len, char **ret) {
823         _cleanup_free_ char *domain = NULL;
824         const uint8_t *optval;
825         size_t optlen, n = 0;
826         int r;
827 
828         assert(data);
829         assert(*data);
830         assert(len);
831         assert(ret);
832 
833         optval = *data;
834         optlen = *len;
835 
836         if (optlen <= 1)
837                 return -ENODATA;
838 
839         for (;;) {
840                 const char *label;
841                 uint8_t c;
842 
843                 if (optlen == 0)
844                         break;
845 
846                 c = *optval;
847                 optval++;
848                 optlen--;
849 
850                 if (c == 0)
851                         /* End label */
852                         break;
853                 if (c > 63)
854                         return -EBADMSG;
855                 if (c > optlen)
856                         return -EMSGSIZE;
857 
858                 /* Literal label */
859                 label = (const char*) optval;
860                 optval += c;
861                 optlen -= c;
862 
863                 if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX))
864                         return -ENOMEM;
865 
866                 if (n != 0)
867                         domain[n++] = '.';
868 
869                 r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX);
870                 if (r < 0)
871                         return r;
872 
873                 n += r;
874         }
875 
876         if (n > 0) {
877                 if (!GREEDY_REALLOC(domain, n + 1))
878                         return -ENOMEM;
879 
880                 domain[n] = '\0';
881         }
882 
883         *ret = TAKE_PTR(domain);
884         *data = optval;
885         *len = optlen;
886 
887         return n;
888 }
889 
dhcp6_option_parse_domainname(const uint8_t * optval,size_t optlen,char ** ret)890 int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) {
891         _cleanup_free_ char *domain = NULL;
892         int r;
893 
894         assert(optval);
895         assert(ret);
896 
897         r = parse_domain(&optval, &optlen, &domain);
898         if (r < 0)
899                 return r;
900         if (r == 0)
901                 return -ENODATA;
902         if (optlen != 0)
903                 return -EINVAL;
904 
905         *ret = TAKE_PTR(domain);
906         return 0;
907 }
908 
dhcp6_option_parse_domainname_list(const uint8_t * optval,size_t optlen,char *** ret)909 int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret) {
910         _cleanup_strv_free_ char **names = NULL;
911         int r;
912 
913         assert(optval);
914         assert(ret);
915 
916         if (optlen <= 1)
917                 return -ENODATA;
918         if (optval[optlen - 1] != '\0')
919                 return -EINVAL;
920 
921         while (optlen > 0) {
922                 _cleanup_free_ char *name = NULL;
923 
924                 r = parse_domain(&optval, &optlen, &name);
925                 if (r < 0)
926                         return r;
927                 if (r == 0)
928                         continue;
929 
930                 r = strv_consume(&names, TAKE_PTR(name));
931                 if (r < 0)
932                         return r;
933         }
934 
935         *ret = TAKE_PTR(names);
936         return 0;
937 }
938 
dhcp6_option_free(sd_dhcp6_option * i)939 static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
940         if (!i)
941                 return NULL;
942 
943         free(i->data);
944         return mfree(i);
945 }
946 
sd_dhcp6_option_new(uint16_t option,const void * data,size_t length,uint32_t enterprise_identifier,sd_dhcp6_option ** ret)947 int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
948         assert_return(ret, -EINVAL);
949         assert_return(length == 0 || data, -EINVAL);
950 
951         _cleanup_free_ void *q = memdup(data, length);
952         if (!q)
953                 return -ENOMEM;
954 
955         sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
956         if (!p)
957                 return -ENOMEM;
958 
959         *p = (sd_dhcp6_option) {
960                 .n_ref = 1,
961                 .option = option,
962                 .enterprise_identifier = enterprise_identifier,
963                 .length = length,
964                 .data = TAKE_PTR(q),
965         };
966 
967         *ret = p;
968         return 0;
969 }
970 
971 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
972 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
973                 dhcp6_option_hash_ops,
974                 void,
975                 trivial_hash_func,
976                 trivial_compare_func,
977                 sd_dhcp6_option,
978                 sd_dhcp6_option_unref);
979