1 /* SPDX-License-Identifier: LGPL-2.1+ */
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 static int option_append(uint8_t options
[], size_t size
, size_t *offset
,
18 uint8_t code
, size_t optlen
, const void *optval
) {
22 if (code
!= SD_DHCP_OPTION_END
)
23 /* always make sure there is space for an END option */
28 case SD_DHCP_OPTION_PAD
:
29 case SD_DHCP_OPTION_END
:
30 if (*offset
+ 1 > size
)
33 options
[*offset
] = code
;
37 case SD_DHCP_OPTION_USER_CLASS
: {
41 STRV_FOREACH(s
, (char **) optval
) {
42 size_t len
= strlen(*s
);
50 if (*offset
+ 2 + total
> size
)
53 options
[*offset
] = code
;
54 options
[*offset
+ 1] = total
;
57 STRV_FOREACH(s
, (char **) optval
) {
58 size_t len
= strlen(*s
);
60 options
[*offset
] = len
;
62 memcpy(&options
[*offset
+ 1], *s
, len
);
68 case SD_DHCP_OPTION_SIP_SERVER
:
69 if (*offset
+ 3 + optlen
> size
)
72 options
[*offset
] = code
;
73 options
[*offset
+ 1] = optlen
+ 1;
74 options
[*offset
+ 2] = 1;
76 memcpy_safe(&options
[*offset
+ 3], optval
, optlen
);
77 *offset
+= 3 + optlen
;
80 case SD_DHCP_OPTION_VENDOR_SPECIFIC
: {
81 OrderedHashmap
*s
= (OrderedHashmap
*) optval
;
82 struct sd_dhcp_raw_option
*p
;
86 ORDERED_HASHMAP_FOREACH(p
, s
, i
)
89 if (*offset
+ l
+ 2 > size
)
92 options
[*offset
] = code
;
93 options
[*offset
+ 1] = l
;
97 ORDERED_HASHMAP_FOREACH(p
, s
, i
) {
98 options
[*offset
] = p
->type
;
99 options
[*offset
+ 1] = p
->length
;
100 memcpy(&options
[*offset
+ 2], p
->data
, p
->length
);
101 *offset
+= 2 + p
->length
;
107 if (*offset
+ 2 + optlen
> size
)
110 options
[*offset
] = code
;
111 options
[*offset
+ 1] = optlen
;
113 memcpy_safe(&options
[*offset
+ 2], optval
, optlen
);
114 *offset
+= 2 + optlen
;
122 int dhcp_option_append(DHCPMessage
*message
, size_t size
, size_t *offset
,
124 uint8_t code
, size_t optlen
, const void *optval
) {
125 const bool use_file
= overload
& DHCP_OVERLOAD_FILE
;
126 const bool use_sname
= overload
& DHCP_OVERLOAD_SNAME
;
132 /* If *offset is in range [0, size), we are writing to ->options,
133 * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
134 * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
135 * and use_sname, we are writing to ->sname.
138 if (*offset
< size
) {
139 /* still space in the options array */
140 r
= option_append(message
->options
, size
, offset
, code
, optlen
, optval
);
143 else if (r
== -ENOBUFS
&& (use_file
|| use_sname
)) {
144 /* did not fit, but we have more buffers to try
145 close the options array and move the offset to its end */
146 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
156 size_t file_offset
= *offset
- size
;
158 if (file_offset
< sizeof(message
->file
)) {
159 /* still space in the 'file' array */
160 r
= option_append(message
->file
, sizeof(message
->file
), &file_offset
, code
, optlen
, optval
);
162 *offset
= size
+ file_offset
;
164 } else if (r
== -ENOBUFS
&& use_sname
) {
165 /* did not fit, but we have more buffers to try
166 close the file array and move the offset to its end */
167 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
171 *offset
= size
+ sizeof(message
->file
);
178 size_t sname_offset
= *offset
- size
- use_file
*sizeof(message
->file
);
180 if (sname_offset
< sizeof(message
->sname
)) {
181 /* still space in the 'sname' array */
182 r
= option_append(message
->sname
, sizeof(message
->sname
), &sname_offset
, code
, optlen
, optval
);
184 *offset
= size
+ use_file
*sizeof(message
->file
) + sname_offset
;
187 /* no space, or other error, give up */
195 static int parse_options(const uint8_t options
[], size_t buflen
, uint8_t *overload
,
196 uint8_t *message_type
, char **error_message
, dhcp_option_callback_t cb
,
199 const uint8_t *option
;
202 while (offset
< buflen
) {
203 code
= options
[offset
++];
206 case SD_DHCP_OPTION_PAD
:
209 case SD_DHCP_OPTION_END
:
213 if (buflen
< offset
+ 1)
216 len
= options
[offset
++];
218 if (buflen
< offset
+ len
)
221 option
= &options
[offset
];
224 case SD_DHCP_OPTION_MESSAGE_TYPE
:
229 *message_type
= *option
;
233 case SD_DHCP_OPTION_ERROR_MESSAGE
:
238 _cleanup_free_
char *string
= NULL
;
240 /* Accept a trailing NUL byte */
241 if (memchr(option
, 0, len
- 1))
244 string
= memdup_suffix0((const char *) option
, len
);
248 if (!ascii_is_valid(string
))
251 free_and_replace(*error_message
, string
);
255 case SD_DHCP_OPTION_OVERLOAD
:
266 cb(code
, len
, option
, userdata
);
280 int dhcp_option_parse(DHCPMessage
*message
, size_t len
, dhcp_option_callback_t cb
, void *userdata
, char **_error_message
) {
281 _cleanup_free_
char *error_message
= NULL
;
282 uint8_t overload
= 0;
283 uint8_t message_type
= 0;
289 if (len
< sizeof(DHCPMessage
))
292 len
-= sizeof(DHCPMessage
);
294 r
= parse_options(message
->options
, len
, &overload
, &message_type
, &error_message
, cb
, userdata
);
298 if (overload
& DHCP_OVERLOAD_FILE
) {
299 r
= parse_options(message
->file
, sizeof(message
->file
), NULL
, &message_type
, &error_message
, cb
, userdata
);
304 if (overload
& DHCP_OVERLOAD_SNAME
) {
305 r
= parse_options(message
->sname
, sizeof(message
->sname
), NULL
, &message_type
, &error_message
, cb
, userdata
);
310 if (message_type
== 0)
313 if (_error_message
&& IN_SET(message_type
, DHCP_NAK
, DHCP_DECLINE
))
314 *_error_message
= TAKE_PTR(error_message
);