1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright (C) 2013 Intel Corporation. All rights reserved.
13 #include "alloc-util.h"
17 #include "dhcp-internal.h"
19 static int option_append(uint8_t options
[], size_t size
, size_t *offset
,
20 uint8_t code
, size_t optlen
, const void *optval
) {
24 if (code
!= SD_DHCP_OPTION_END
)
25 /* always make sure there is space for an END option */
30 case SD_DHCP_OPTION_PAD
:
31 case SD_DHCP_OPTION_END
:
32 if (size
< *offset
+ 1)
35 options
[*offset
] = code
;
39 case SD_DHCP_OPTION_USER_CLASS
: {
43 STRV_FOREACH(s
, (char **) optval
)
44 len
+= strlen(*s
) + 1;
46 if (size
< *offset
+ len
+ 2)
49 options
[*offset
] = code
;
50 options
[*offset
+ 1] = len
;
53 STRV_FOREACH(s
, (char **) optval
) {
59 options
[*offset
] = len
;
61 memcpy_safe(&options
[*offset
+ 1], *s
, len
);
68 if (size
< *offset
+ optlen
+ 2)
71 options
[*offset
] = code
;
72 options
[*offset
+ 1] = optlen
;
74 memcpy_safe(&options
[*offset
+ 2], optval
, optlen
);
75 *offset
+= optlen
+ 2;
83 int dhcp_option_append(DHCPMessage
*message
, size_t size
, size_t *offset
,
85 uint8_t code
, size_t optlen
, const void *optval
) {
86 size_t file_offset
= 0, sname_offset
=0;
93 file
= overload
& DHCP_OVERLOAD_FILE
;
94 sname
= overload
& DHCP_OVERLOAD_SNAME
;
97 /* still space in the options array */
98 r
= option_append(message
->options
, size
, offset
, code
, optlen
, optval
);
101 else if (r
== -ENOBUFS
&& (file
|| sname
)) {
102 /* did not fit, but we have more buffers to try
103 close the options array and move the offset to its end */
104 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
113 if (overload
& DHCP_OVERLOAD_FILE
) {
114 file_offset
= *offset
- size
;
116 if (file_offset
< sizeof(message
->file
)) {
117 /* still space in the 'file' array */
118 r
= option_append(message
->file
, sizeof(message
->file
), &file_offset
, code
, optlen
, optval
);
120 *offset
= size
+ file_offset
;
122 } else if (r
== -ENOBUFS
&& sname
) {
123 /* did not fit, but we have more buffers to try
124 close the file array and move the offset to its end */
125 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
129 *offset
= size
+ sizeof(message
->file
);
135 if (overload
& DHCP_OVERLOAD_SNAME
) {
136 sname_offset
= *offset
- size
- (file
? sizeof(message
->file
) : 0);
138 if (sname_offset
< sizeof(message
->sname
)) {
139 /* still space in the 'sname' array */
140 r
= option_append(message
->sname
, sizeof(message
->sname
), &sname_offset
, code
, optlen
, optval
);
142 *offset
= size
+ (file
? sizeof(message
->file
) : 0) + sname_offset
;
145 /* no space, or other error, give up */
154 static int parse_options(const uint8_t options
[], size_t buflen
, uint8_t *overload
,
155 uint8_t *message_type
, char **error_message
, dhcp_option_callback_t cb
,
158 const uint8_t *option
;
161 while (offset
< buflen
) {
162 code
= options
[offset
++];
165 case SD_DHCP_OPTION_PAD
:
168 case SD_DHCP_OPTION_END
:
172 if (buflen
< offset
+ 1)
175 len
= options
[offset
++];
177 if (buflen
< offset
+ len
)
180 option
= &options
[offset
];
183 case SD_DHCP_OPTION_MESSAGE_TYPE
:
188 *message_type
= *option
;
192 case SD_DHCP_OPTION_ERROR_MESSAGE
:
197 _cleanup_free_
char *string
= NULL
;
199 /* Accept a trailing NUL byte */
200 if (memchr(option
, 0, len
- 1))
203 string
= strndup((const char *) option
, len
);
207 if (!ascii_is_valid(string
))
210 free_and_replace(*error_message
, string
);
214 case SD_DHCP_OPTION_OVERLOAD
:
225 cb(code
, len
, option
, userdata
);
239 int dhcp_option_parse(DHCPMessage
*message
, size_t len
, dhcp_option_callback_t cb
, void *userdata
, char **_error_message
) {
240 _cleanup_free_
char *error_message
= NULL
;
241 uint8_t overload
= 0;
242 uint8_t message_type
= 0;
248 if (len
< sizeof(DHCPMessage
))
251 len
-= sizeof(DHCPMessage
);
253 r
= parse_options(message
->options
, len
, &overload
, &message_type
, &error_message
, cb
, userdata
);
257 if (overload
& DHCP_OVERLOAD_FILE
) {
258 r
= parse_options(message
->file
, sizeof(message
->file
), NULL
, &message_type
, &error_message
, cb
, userdata
);
263 if (overload
& DHCP_OVERLOAD_SNAME
) {
264 r
= parse_options(message
->sname
, sizeof(message
->sname
), NULL
, &message_type
, &error_message
, cb
, userdata
);
269 if (message_type
== 0)
272 if (_error_message
&& IN_SET(message_type
, DHCP_NAK
, DHCP_DECLINE
))
273 *_error_message
= TAKE_PTR(error_message
);