1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright (C) 2013 Intel Corporation. All rights reserved.
11 #include "alloc-util.h"
15 #include "dhcp-internal.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 (size
< *offset
+ 1)
33 options
[*offset
] = code
;
37 case SD_DHCP_OPTION_USER_CLASS
: {
41 STRV_FOREACH(s
, (char **) optval
)
42 len
+= strlen(*s
) + 1;
44 if (size
< *offset
+ len
+ 2)
47 options
[*offset
] = code
;
48 options
[*offset
+ 1] = len
;
51 STRV_FOREACH(s
, (char **) optval
) {
57 options
[*offset
] = len
;
59 memcpy_safe(&options
[*offset
+ 1], *s
, len
);
66 if (size
< *offset
+ optlen
+ 2)
69 options
[*offset
] = code
;
70 options
[*offset
+ 1] = optlen
;
72 memcpy_safe(&options
[*offset
+ 2], optval
, optlen
);
73 *offset
+= optlen
+ 2;
81 int dhcp_option_append(DHCPMessage
*message
, size_t size
, size_t *offset
,
83 uint8_t code
, size_t optlen
, const void *optval
) {
84 size_t file_offset
= 0, sname_offset
=0;
91 file
= overload
& DHCP_OVERLOAD_FILE
;
92 sname
= overload
& DHCP_OVERLOAD_SNAME
;
95 /* still space in the options array */
96 r
= option_append(message
->options
, size
, offset
, code
, optlen
, optval
);
99 else if (r
== -ENOBUFS
&& (file
|| sname
)) {
100 /* did not fit, but we have more buffers to try
101 close the options array and move the offset to its end */
102 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
111 if (overload
& DHCP_OVERLOAD_FILE
) {
112 file_offset
= *offset
- size
;
114 if (file_offset
< sizeof(message
->file
)) {
115 /* still space in the 'file' array */
116 r
= option_append(message
->file
, sizeof(message
->file
), &file_offset
, code
, optlen
, optval
);
118 *offset
= size
+ file_offset
;
120 } else if (r
== -ENOBUFS
&& sname
) {
121 /* did not fit, but we have more buffers to try
122 close the file array and move the offset to its end */
123 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
127 *offset
= size
+ sizeof(message
->file
);
133 if (overload
& DHCP_OVERLOAD_SNAME
) {
134 sname_offset
= *offset
- size
- (file
? sizeof(message
->file
) : 0);
136 if (sname_offset
< sizeof(message
->sname
)) {
137 /* still space in the 'sname' array */
138 r
= option_append(message
->sname
, sizeof(message
->sname
), &sname_offset
, code
, optlen
, optval
);
140 *offset
= size
+ (file
? sizeof(message
->file
) : 0) + sname_offset
;
143 /* no space, or other error, give up */
152 static int parse_options(const uint8_t options
[], size_t buflen
, uint8_t *overload
,
153 uint8_t *message_type
, char **error_message
, dhcp_option_callback_t cb
,
156 const uint8_t *option
;
159 while (offset
< buflen
) {
160 code
= options
[offset
++];
163 case SD_DHCP_OPTION_PAD
:
166 case SD_DHCP_OPTION_END
:
170 if (buflen
< offset
+ 1)
173 len
= options
[offset
++];
175 if (buflen
< offset
+ len
)
178 option
= &options
[offset
];
181 case SD_DHCP_OPTION_MESSAGE_TYPE
:
186 *message_type
= *option
;
190 case SD_DHCP_OPTION_ERROR_MESSAGE
:
195 _cleanup_free_
char *string
= NULL
;
197 /* Accept a trailing NUL byte */
198 if (memchr(option
, 0, len
- 1))
201 string
= strndup((const char *) option
, len
);
205 if (!ascii_is_valid(string
))
208 free_and_replace(*error_message
, string
);
212 case SD_DHCP_OPTION_OVERLOAD
:
223 cb(code
, len
, option
, userdata
);
237 int dhcp_option_parse(DHCPMessage
*message
, size_t len
, dhcp_option_callback_t cb
, void *userdata
, char **_error_message
) {
238 _cleanup_free_
char *error_message
= NULL
;
239 uint8_t overload
= 0;
240 uint8_t message_type
= 0;
246 if (len
< sizeof(DHCPMessage
))
249 len
-= sizeof(DHCPMessage
);
251 r
= parse_options(message
->options
, len
, &overload
, &message_type
, &error_message
, cb
, userdata
);
255 if (overload
& DHCP_OVERLOAD_FILE
) {
256 r
= parse_options(message
->file
, sizeof(message
->file
), NULL
, &message_type
, &error_message
, cb
, userdata
);
261 if (overload
& DHCP_OVERLOAD_SNAME
) {
262 r
= parse_options(message
->sname
, sizeof(message
->sname
), NULL
, &message_type
, &error_message
, cb
, userdata
);
267 if (message_type
== 0)
270 if (_error_message
&& IN_SET(message_type
, DHCP_NAK
, DHCP_DECLINE
))
271 *_error_message
= TAKE_PTR(error_message
);