1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2013 Intel Corporation. All rights reserved.
10 #include "alloc-util.h"
11 #include "dhcp-internal.h"
12 #include "dhcp-server-internal.h"
13 #include "memory-util.h"
17 /* Append type-length value structure to the options buffer */
18 static int dhcp_option_append_tlv(uint8_t options
[], size_t size
, size_t *offset
, uint8_t code
, size_t optlen
, const void *optval
) {
22 assert(optlen
<= UINT8_MAX
);
23 assert(*offset
< size
);
25 if (*offset
+ 2 + optlen
> size
)
28 options
[*offset
] = code
;
29 options
[*offset
+ 1] = optlen
;
31 memcpy_safe(&options
[*offset
+ 2], optval
, optlen
);
32 *offset
+= 2 + optlen
;
36 static int option_append(uint8_t options
[], size_t size
, size_t *offset
,
37 uint8_t code
, size_t optlen
, const void *optval
) {
44 if (code
!= SD_DHCP_OPTION_END
)
45 /* always make sure there is space for an END option */
50 case SD_DHCP_OPTION_PAD
:
51 case SD_DHCP_OPTION_END
:
52 if (*offset
+ 1 > size
)
55 options
[*offset
] = code
;
59 case SD_DHCP_OPTION_USER_CLASS
: {
63 if (strv_isempty((char **) optval
))
66 STRV_FOREACH(s
, (char **) optval
) {
67 size_t len
= strlen(*s
);
69 if (len
> 255 || len
== 0)
75 if (*offset
+ 2 + total
> size
)
78 options
[*offset
] = code
;
79 options
[*offset
+ 1] = total
;
82 STRV_FOREACH(s
, (char **) optval
) {
83 size_t len
= strlen(*s
);
85 options
[*offset
] = len
;
86 memcpy(&options
[*offset
+ 1], *s
, len
);
92 case SD_DHCP_OPTION_SIP_SERVER
:
93 if (*offset
+ 3 + optlen
> size
)
96 options
[*offset
] = code
;
97 options
[*offset
+ 1] = optlen
+ 1;
98 options
[*offset
+ 2] = 1;
100 memcpy_safe(&options
[*offset
+ 3], optval
, optlen
);
101 *offset
+= 3 + optlen
;
104 case SD_DHCP_OPTION_VENDOR_SPECIFIC
: {
105 OrderedSet
*s
= (OrderedSet
*) optval
;
106 struct sd_dhcp_option
*p
;
109 ORDERED_SET_FOREACH(p
, s
)
112 if (*offset
+ l
+ 2 > size
)
115 options
[*offset
] = code
;
116 options
[*offset
+ 1] = l
;
119 ORDERED_SET_FOREACH(p
, s
) {
120 r
= dhcp_option_append_tlv(options
, size
, offset
, p
->option
, p
->length
, p
->data
);
126 case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION
: {
127 sd_dhcp_server
*server
= (sd_dhcp_server
*) optval
;
128 size_t current_offset
= *offset
+ 2;
130 if (server
->agent_circuit_id
) {
131 r
= dhcp_option_append_tlv(options
, size
, ¤t_offset
, SD_DHCP_RELAY_AGENT_CIRCUIT_ID
,
132 strlen(server
->agent_circuit_id
), server
->agent_circuit_id
);
136 if (server
->agent_remote_id
) {
137 r
= dhcp_option_append_tlv(options
, size
, ¤t_offset
, SD_DHCP_RELAY_AGENT_REMOTE_ID
,
138 strlen(server
->agent_remote_id
), server
->agent_remote_id
);
143 options
[*offset
] = code
;
144 options
[*offset
+ 1] = current_offset
- *offset
- 2;
145 assert(current_offset
- *offset
- 2 <= UINT8_MAX
);
146 *offset
= current_offset
;
150 return dhcp_option_append_tlv(options
, size
, offset
, code
, optlen
, optval
);
155 static int option_length(uint8_t *options
, size_t length
, size_t offset
) {
157 assert(offset
< length
);
159 if (IN_SET(options
[offset
], SD_DHCP_OPTION_PAD
, SD_DHCP_OPTION_END
))
161 if (length
< offset
+ 2)
164 /* validating that buffer is long enough */
165 if (length
< offset
+ 2 + options
[offset
+ 1])
168 return options
[offset
+ 1] + 2;
171 int dhcp_option_find_option(uint8_t *options
, size_t length
, uint8_t code
, size_t *ret_offset
) {
177 for (size_t offset
= 0; offset
< length
; offset
+= r
) {
178 r
= option_length(options
, length
, offset
);
182 if (code
== options
[offset
]) {
183 *ret_offset
= offset
;
190 int dhcp_option_remove_option(uint8_t *options
, size_t length
, uint8_t option_code
) {
196 r
= dhcp_option_find_option(options
, length
, option_code
, &offset
);
200 memmove(options
+ offset
, options
+ offset
+ r
, length
- offset
- r
);
204 int dhcp_option_append(DHCPMessage
*message
, size_t size
, size_t *offset
,
206 uint8_t code
, size_t optlen
, const void *optval
) {
207 const bool use_file
= overload
& DHCP_OVERLOAD_FILE
;
208 const bool use_sname
= overload
& DHCP_OVERLOAD_SNAME
;
214 /* If *offset is in range [0, size), we are writing to ->options,
215 * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
216 * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
217 * and use_sname, we are writing to ->sname.
220 if (*offset
< size
) {
221 /* still space in the options array */
222 r
= option_append(message
->options
, size
, offset
, code
, optlen
, optval
);
225 else if (r
== -ENOBUFS
&& (use_file
|| use_sname
)) {
226 /* did not fit, but we have more buffers to try
227 close the options array and move the offset to its end */
228 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
238 size_t file_offset
= *offset
- size
;
240 if (file_offset
< sizeof(message
->file
)) {
241 /* still space in the 'file' array */
242 r
= option_append(message
->file
, sizeof(message
->file
), &file_offset
, code
, optlen
, optval
);
244 *offset
= size
+ file_offset
;
246 } else if (r
== -ENOBUFS
&& use_sname
) {
247 /* did not fit, but we have more buffers to try
248 close the file array and move the offset to its end */
249 r
= option_append(message
->file
, sizeof(message
->file
), &file_offset
, SD_DHCP_OPTION_END
, 0, NULL
);
253 *offset
= size
+ sizeof(message
->file
);
260 size_t sname_offset
= *offset
- size
- use_file
*sizeof(message
->file
);
262 if (sname_offset
< sizeof(message
->sname
)) {
263 /* still space in the 'sname' array */
264 r
= option_append(message
->sname
, sizeof(message
->sname
), &sname_offset
, code
, optlen
, optval
);
266 *offset
= size
+ use_file
*sizeof(message
->file
) + sname_offset
;
269 /* no space, or other error, give up */
277 static int parse_options(const uint8_t options
[], size_t buflen
, uint8_t *overload
,
278 uint8_t *message_type
, char **error_message
, dhcp_option_callback_t cb
,
281 const uint8_t *option
;
284 while (offset
< buflen
) {
285 code
= options
[offset
++];
288 case SD_DHCP_OPTION_PAD
:
291 case SD_DHCP_OPTION_END
:
295 if (buflen
< offset
+ 1)
298 len
= options
[offset
++];
300 if (buflen
< offset
+ len
)
303 option
= &options
[offset
];
306 case SD_DHCP_OPTION_MESSAGE_TYPE
:
311 *message_type
= *option
;
315 case SD_DHCP_OPTION_ERROR_MESSAGE
:
320 _cleanup_free_
char *string
= NULL
;
322 /* Accept a trailing NUL byte */
323 if (memchr(option
, 0, len
- 1))
326 string
= memdup_suffix0((const char *) option
, len
);
330 if (!ascii_is_valid(string
))
333 free_and_replace(*error_message
, string
);
337 case SD_DHCP_OPTION_OVERLOAD
:
348 cb(code
, len
, option
, userdata
);
362 int dhcp_option_parse(DHCPMessage
*message
, size_t len
, dhcp_option_callback_t cb
, void *userdata
, char **_error_message
) {
363 _cleanup_free_
char *error_message
= NULL
;
364 uint8_t overload
= 0;
365 uint8_t message_type
= 0;
371 if (len
< sizeof(DHCPMessage
))
374 len
-= sizeof(DHCPMessage
);
376 r
= parse_options(message
->options
, len
, &overload
, &message_type
, &error_message
, cb
, userdata
);
380 if (overload
& DHCP_OVERLOAD_FILE
) {
381 r
= parse_options(message
->file
, sizeof(message
->file
), NULL
, &message_type
, &error_message
, cb
, userdata
);
386 if (overload
& DHCP_OVERLOAD_SNAME
) {
387 r
= parse_options(message
->sname
, sizeof(message
->sname
), NULL
, &message_type
, &error_message
, cb
, userdata
);
392 if (message_type
== 0)
395 if (_error_message
&& IN_SET(message_type
, DHCP_NAK
, DHCP_DECLINE
))
396 *_error_message
= TAKE_PTR(error_message
);
401 static sd_dhcp_option
* dhcp_option_free(sd_dhcp_option
*i
) {
409 int sd_dhcp_option_new(uint8_t option
, const void *data
, size_t length
, sd_dhcp_option
**ret
) {
410 assert_return(ret
, -EINVAL
);
411 assert_return(length
== 0 || data
, -EINVAL
);
413 _cleanup_free_
void *q
= memdup(data
, length
);
417 sd_dhcp_option
*p
= new(sd_dhcp_option
, 1);
421 *p
= (sd_dhcp_option
) {
432 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option
, sd_dhcp_option
, dhcp_option_free
);
433 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
434 dhcp_option_hash_ops
,
437 trivial_compare_func
,
439 sd_dhcp_option_unref
);