]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
524cf458 | 2 | /*** |
810adae9 | 3 | Copyright © 2013 Intel Corporation. All rights reserved. |
524cf458 PF |
4 | ***/ |
5 | ||
524cf458 | 6 | #include <errno.h> |
cf0fbc49 | 7 | #include <stdint.h> |
524cf458 | 8 | #include <stdio.h> |
cf0fbc49 | 9 | #include <string.h> |
524cf458 | 10 | |
f693e9b3 TG |
11 | #include "alloc-util.h" |
12 | #include "utf8.h" | |
af1c0de0 | 13 | #include "strv.h" |
f693e9b3 | 14 | |
524cf458 PF |
15 | #include "dhcp-internal.h" |
16 | ||
04b28be1 TG |
17 | static int option_append(uint8_t options[], size_t size, size_t *offset, |
18 | uint8_t code, size_t optlen, const void *optval) { | |
20b958bf TG |
19 | assert(options); |
20 | assert(offset); | |
524cf458 | 21 | |
22805d92 | 22 | if (code != SD_DHCP_OPTION_END) |
2688ef60 | 23 | /* always make sure there is space for an END option */ |
313cefa1 | 24 | size--; |
2688ef60 | 25 | |
524cf458 PF |
26 | switch (code) { |
27 | ||
22805d92 BG |
28 | case SD_DHCP_OPTION_PAD: |
29 | case SD_DHCP_OPTION_END: | |
04b28be1 | 30 | if (size < *offset + 1) |
524cf458 PF |
31 | return -ENOBUFS; |
32 | ||
20b958bf TG |
33 | options[*offset] = code; |
34 | *offset += 1; | |
524cf458 PF |
35 | break; |
36 | ||
af1c0de0 SS |
37 | case SD_DHCP_OPTION_USER_CLASS: { |
38 | size_t len = 0; | |
39 | char **s; | |
40 | ||
41 | STRV_FOREACH(s, (char **) optval) | |
42 | len += strlen(*s) + 1; | |
43 | ||
44 | if (size < *offset + len + 2) | |
45 | return -ENOBUFS; | |
46 | ||
47 | options[*offset] = code; | |
48 | options[*offset + 1] = len; | |
49 | *offset += 2; | |
50 | ||
51 | STRV_FOREACH(s, (char **) optval) { | |
52 | len = strlen(*s); | |
53 | ||
54 | if (len > 255) | |
55 | return -ENAMETOOLONG; | |
56 | ||
57 | options[*offset] = len; | |
58 | ||
59 | memcpy_safe(&options[*offset + 1], *s, len); | |
60 | *offset += len + 1; | |
61 | } | |
62 | ||
63 | break; | |
64 | } | |
524cf458 | 65 | default: |
04b28be1 | 66 | if (size < *offset + optlen + 2) |
524cf458 PF |
67 | return -ENOBUFS; |
68 | ||
20b958bf TG |
69 | options[*offset] = code; |
70 | options[*offset + 1] = optlen; | |
04b28be1 | 71 | |
75f32f04 | 72 | memcpy_safe(&options[*offset + 2], optval, optlen); |
20b958bf | 73 | *offset += optlen + 2; |
524cf458 PF |
74 | |
75 | break; | |
76 | } | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
04b28be1 TG |
81 | int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, |
82 | uint8_t overload, | |
83 | uint8_t code, size_t optlen, const void *optval) { | |
84 | size_t file_offset = 0, sname_offset =0; | |
85 | bool file, sname; | |
86 | int r; | |
87 | ||
88 | assert(message); | |
89 | assert(offset); | |
90 | ||
91 | file = overload & DHCP_OVERLOAD_FILE; | |
92 | sname = overload & DHCP_OVERLOAD_SNAME; | |
93 | ||
94 | if (*offset < size) { | |
95 | /* still space in the options array */ | |
96 | r = option_append(message->options, size, offset, code, optlen, optval); | |
97 | if (r >= 0) | |
98 | return 0; | |
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 */ | |
22805d92 | 102 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
103 | if (r < 0) |
104 | return r; | |
105 | ||
106 | *offset = size; | |
107 | } else | |
108 | return r; | |
109 | } | |
110 | ||
111 | if (overload & DHCP_OVERLOAD_FILE) { | |
112 | file_offset = *offset - size; | |
113 | ||
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); | |
117 | if (r >= 0) { | |
118 | *offset = size + file_offset; | |
119 | return 0; | |
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 */ | |
22805d92 | 123 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
124 | if (r < 0) |
125 | return r; | |
126 | ||
127 | *offset = size + sizeof(message->file); | |
128 | } else | |
129 | return r; | |
130 | } | |
131 | } | |
132 | ||
133 | if (overload & DHCP_OVERLOAD_SNAME) { | |
134 | sname_offset = *offset - size - (file ? sizeof(message->file) : 0); | |
135 | ||
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); | |
139 | if (r >= 0) { | |
140 | *offset = size + (file ? sizeof(message->file) : 0) + sname_offset; | |
141 | return 0; | |
142 | } else { | |
143 | /* no space, or other error, give up */ | |
144 | return r; | |
145 | } | |
146 | } | |
147 | } | |
148 | ||
149 | return -ENOBUFS; | |
150 | } | |
151 | ||
32008a96 | 152 | static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, |
ccf86354 | 153 | uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, |
89ca10c6 | 154 | void *userdata) { |
32008a96 | 155 | uint8_t code, len; |
f693e9b3 | 156 | const uint8_t *option; |
32008a96 | 157 | size_t offset = 0; |
524cf458 | 158 | |
32008a96 | 159 | while (offset < buflen) { |
f693e9b3 | 160 | code = options[offset ++]; |
32008a96 | 161 | |
f693e9b3 | 162 | switch (code) { |
22805d92 | 163 | case SD_DHCP_OPTION_PAD: |
f693e9b3 | 164 | continue; |
524cf458 | 165 | |
22805d92 | 166 | case SD_DHCP_OPTION_END: |
524cf458 | 167 | return 0; |
f693e9b3 | 168 | } |
524cf458 | 169 | |
f693e9b3 TG |
170 | if (buflen < offset + 1) |
171 | return -ENOBUFS; | |
172 | ||
173 | len = options[offset ++]; | |
524cf458 | 174 | |
f693e9b3 TG |
175 | if (buflen < offset + len) |
176 | return -EINVAL; | |
177 | ||
178 | option = &options[offset]; | |
179 | ||
180 | switch (code) { | |
22805d92 | 181 | case SD_DHCP_OPTION_MESSAGE_TYPE: |
32008a96 | 182 | if (len != 1) |
524cf458 PF |
183 | return -EINVAL; |
184 | ||
185 | if (message_type) | |
f693e9b3 | 186 | *message_type = *option; |
524cf458 PF |
187 | |
188 | break; | |
189 | ||
22805d92 | 190 | case SD_DHCP_OPTION_ERROR_MESSAGE: |
f693e9b3 | 191 | if (len == 0) |
524cf458 PF |
192 | return -EINVAL; |
193 | ||
f693e9b3 TG |
194 | if (error_message) { |
195 | _cleanup_free_ char *string = NULL; | |
524cf458 | 196 | |
f693e9b3 TG |
197 | /* Accept a trailing NUL byte */ |
198 | if (memchr(option, 0, len - 1)) | |
199 | return -EINVAL; | |
524cf458 | 200 | |
f693e9b3 TG |
201 | string = strndup((const char *) option, len); |
202 | if (!string) | |
203 | return -ENOMEM; | |
524cf458 | 204 | |
f693e9b3 TG |
205 | if (!ascii_is_valid(string)) |
206 | return -EINVAL; | |
524cf458 | 207 | |
f9ecfd3b | 208 | free_and_replace(*error_message, string); |
f693e9b3 | 209 | } |
524cf458 | 210 | |
f693e9b3 | 211 | break; |
22805d92 | 212 | case SD_DHCP_OPTION_OVERLOAD: |
f693e9b3 | 213 | if (len != 1) |
524cf458 | 214 | return -EINVAL; |
524cf458 | 215 | |
f693e9b3 TG |
216 | if (overload) |
217 | *overload = *option; | |
524cf458 | 218 | |
f693e9b3 TG |
219 | break; |
220 | ||
221 | default: | |
222 | if (cb) | |
223 | cb(code, len, option, userdata); | |
524cf458 PF |
224 | |
225 | break; | |
226 | } | |
f693e9b3 TG |
227 | |
228 | offset += len; | |
524cf458 PF |
229 | } |
230 | ||
32008a96 | 231 | if (offset < buflen) |
524cf458 PF |
232 | return -EINVAL; |
233 | ||
234 | return 0; | |
235 | } | |
236 | ||
ccf86354 | 237 | int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) { |
f693e9b3 | 238 | _cleanup_free_ char *error_message = NULL; |
524cf458 PF |
239 | uint8_t overload = 0; |
240 | uint8_t message_type = 0; | |
32008a96 | 241 | int r; |
524cf458 PF |
242 | |
243 | if (!message) | |
244 | return -EINVAL; | |
245 | ||
3b7ca119 | 246 | if (len < sizeof(DHCPMessage)) |
524cf458 PF |
247 | return -EINVAL; |
248 | ||
3b7ca119 | 249 | len -= sizeof(DHCPMessage); |
524cf458 | 250 | |
f693e9b3 | 251 | r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); |
32008a96 TG |
252 | if (r < 0) |
253 | return r; | |
524cf458 PF |
254 | |
255 | if (overload & DHCP_OVERLOAD_FILE) { | |
f693e9b3 | 256 | r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
257 | if (r < 0) |
258 | return r; | |
524cf458 PF |
259 | } |
260 | ||
261 | if (overload & DHCP_OVERLOAD_SNAME) { | |
f693e9b3 | 262 | r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
263 | if (r < 0) |
264 | return r; | |
524cf458 PF |
265 | } |
266 | ||
f693e9b3 TG |
267 | if (message_type == 0) |
268 | return -ENOMSG; | |
269 | ||
ae2a15bc LP |
270 | if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) |
271 | *_error_message = TAKE_PTR(error_message); | |
524cf458 | 272 | |
f693e9b3 | 273 | return message_type; |
524cf458 | 274 | } |