1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright (C) 2013 Intel Corporation. All rights reserved.
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include "alloc-util.h"
29 #include "dhcp-internal.h"
31 static int option_append(uint8_t options
[], size_t size
, size_t *offset
,
32 uint8_t code
, size_t optlen
, const void *optval
) {
36 if (code
!= SD_DHCP_OPTION_END
)
37 /* always make sure there is space for an END option */
42 case SD_DHCP_OPTION_PAD
:
43 case SD_DHCP_OPTION_END
:
44 if (size
< *offset
+ 1)
47 options
[*offset
] = code
;
52 if (size
< *offset
+ optlen
+ 2)
55 options
[*offset
] = code
;
56 options
[*offset
+ 1] = optlen
;
58 memcpy_safe(&options
[*offset
+ 2], optval
, optlen
);
59 *offset
+= optlen
+ 2;
67 int dhcp_option_append(DHCPMessage
*message
, size_t size
, size_t *offset
,
69 uint8_t code
, size_t optlen
, const void *optval
) {
70 size_t file_offset
= 0, sname_offset
=0;
77 file
= overload
& DHCP_OVERLOAD_FILE
;
78 sname
= overload
& DHCP_OVERLOAD_SNAME
;
81 /* still space in the options array */
82 r
= option_append(message
->options
, size
, offset
, code
, optlen
, optval
);
85 else if (r
== -ENOBUFS
&& (file
|| sname
)) {
86 /* did not fit, but we have more buffers to try
87 close the options array and move the offset to its end */
88 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
97 if (overload
& DHCP_OVERLOAD_FILE
) {
98 file_offset
= *offset
- size
;
100 if (file_offset
< sizeof(message
->file
)) {
101 /* still space in the 'file' array */
102 r
= option_append(message
->file
, sizeof(message
->file
), &file_offset
, code
, optlen
, optval
);
104 *offset
= size
+ file_offset
;
106 } else if (r
== -ENOBUFS
&& sname
) {
107 /* did not fit, but we have more buffers to try
108 close the file array and move the offset to its end */
109 r
= option_append(message
->options
, size
, offset
, SD_DHCP_OPTION_END
, 0, NULL
);
113 *offset
= size
+ sizeof(message
->file
);
119 if (overload
& DHCP_OVERLOAD_SNAME
) {
120 sname_offset
= *offset
- size
- (file
? sizeof(message
->file
) : 0);
122 if (sname_offset
< sizeof(message
->sname
)) {
123 /* still space in the 'sname' array */
124 r
= option_append(message
->sname
, sizeof(message
->sname
), &sname_offset
, code
, optlen
, optval
);
126 *offset
= size
+ (file
? sizeof(message
->file
) : 0) + sname_offset
;
129 /* no space, or other error, give up */
138 static int parse_options(const uint8_t options
[], size_t buflen
, uint8_t *overload
,
139 uint8_t *message_type
, char **error_message
, dhcp_option_callback_t cb
,
142 const uint8_t *option
;
145 while (offset
< buflen
) {
146 code
= options
[offset
++];
149 case SD_DHCP_OPTION_PAD
:
152 case SD_DHCP_OPTION_END
:
156 if (buflen
< offset
+ 1)
159 len
= options
[offset
++];
161 if (buflen
< offset
+ len
)
164 option
= &options
[offset
];
167 case SD_DHCP_OPTION_MESSAGE_TYPE
:
172 *message_type
= *option
;
176 case SD_DHCP_OPTION_ERROR_MESSAGE
:
181 _cleanup_free_
char *string
= NULL
;
183 /* Accept a trailing NUL byte */
184 if (memchr(option
, 0, len
- 1))
187 string
= strndup((const char *) option
, len
);
191 if (!ascii_is_valid(string
))
194 free(*error_message
);
195 *error_message
= string
;
200 case SD_DHCP_OPTION_OVERLOAD
:
211 cb(code
, len
, option
, userdata
);
225 int dhcp_option_parse(DHCPMessage
*message
, size_t len
, dhcp_option_callback_t cb
, void *userdata
, char **_error_message
) {
226 _cleanup_free_
char *error_message
= NULL
;
227 uint8_t overload
= 0;
228 uint8_t message_type
= 0;
234 if (len
< sizeof(DHCPMessage
))
237 len
-= sizeof(DHCPMessage
);
239 r
= parse_options(message
->options
, len
, &overload
, &message_type
, &error_message
, cb
, userdata
);
243 if (overload
& DHCP_OVERLOAD_FILE
) {
244 r
= parse_options(message
->file
, sizeof(message
->file
), NULL
, &message_type
, &error_message
, cb
, userdata
);
249 if (overload
& DHCP_OVERLOAD_SNAME
) {
250 r
= parse_options(message
->sname
, sizeof(message
->sname
), NULL
, &message_type
, &error_message
, cb
, userdata
);
255 if (message_type
== 0)
258 if (_error_message
&& IN_SET(message_type
, DHCP_NAK
, DHCP_DECLINE
)) {
259 *_error_message
= error_message
;
260 error_message
= NULL
;