]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
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 PF |
8 | #include <stdio.h> |
9 | ||
f693e9b3 | 10 | #include "alloc-util.h" |
524cf458 | 11 | #include "dhcp-internal.h" |
564ca984 | 12 | #include "dhcp-server-internal.h" |
0a970718 LP |
13 | #include "memory-util.h" |
14 | #include "strv.h" | |
15 | #include "utf8.h" | |
524cf458 | 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: | |
bc67342e | 30 | if (*offset + 1 > size) |
524cf458 PF |
31 | return -ENOBUFS; |
32 | ||
20b958bf TG |
33 | options[*offset] = code; |
34 | *offset += 1; | |
524cf458 PF |
35 | break; |
36 | ||
af1c0de0 | 37 | case SD_DHCP_OPTION_USER_CLASS: { |
bc67342e | 38 | size_t total = 0; |
af1c0de0 SS |
39 | char **s; |
40 | ||
e4336c0a YW |
41 | if (strv_isempty((char **) optval)) |
42 | return -EINVAL; | |
43 | ||
bc67342e ZJS |
44 | STRV_FOREACH(s, (char **) optval) { |
45 | size_t len = strlen(*s); | |
46 | ||
e4336c0a YW |
47 | if (len > 255 || len == 0) |
48 | return -EINVAL; | |
bc67342e ZJS |
49 | |
50 | total += 1 + len; | |
51 | } | |
af1c0de0 | 52 | |
bc67342e | 53 | if (*offset + 2 + total > size) |
af1c0de0 SS |
54 | return -ENOBUFS; |
55 | ||
56 | options[*offset] = code; | |
e4336c0a | 57 | options[*offset + 1] = total; |
af1c0de0 SS |
58 | *offset += 2; |
59 | ||
60 | STRV_FOREACH(s, (char **) optval) { | |
bc67342e | 61 | size_t len = strlen(*s); |
af1c0de0 SS |
62 | |
63 | options[*offset] = len; | |
bc67342e ZJS |
64 | memcpy(&options[*offset + 1], *s, len); |
65 | *offset += 1 + len; | |
af1c0de0 SS |
66 | } |
67 | ||
68 | break; | |
69 | } | |
299d578f SS |
70 | case SD_DHCP_OPTION_SIP_SERVER: |
71 | if (*offset + 3 + optlen > size) | |
72 | return -ENOBUFS; | |
73 | ||
74 | options[*offset] = code; | |
75 | options[*offset + 1] = optlen + 1; | |
76 | options[*offset + 2] = 1; | |
77 | ||
78 | memcpy_safe(&options[*offset + 3], optval, optlen); | |
79 | *offset += 3 + optlen; | |
80 | ||
81 | break; | |
564ca984 SS |
82 | case SD_DHCP_OPTION_VENDOR_SPECIFIC: { |
83 | OrderedHashmap *s = (OrderedHashmap *) optval; | |
461dbb2f | 84 | struct sd_dhcp_option *p; |
564ca984 | 85 | size_t l = 0; |
564ca984 | 86 | |
90e74a66 | 87 | ORDERED_HASHMAP_FOREACH(p, s) |
564ca984 SS |
88 | l += p->length + 2; |
89 | ||
90 | if (*offset + l + 2 > size) | |
91 | return -ENOBUFS; | |
92 | ||
93 | options[*offset] = code; | |
94 | options[*offset + 1] = l; | |
95 | ||
96 | *offset += 2; | |
97 | ||
90e74a66 | 98 | ORDERED_HASHMAP_FOREACH(p, s) { |
461dbb2f | 99 | options[*offset] = p->option; |
564ca984 SS |
100 | options[*offset + 1] = p->length; |
101 | memcpy(&options[*offset + 2], p->data, p->length); | |
102 | *offset += 2 + p->length; | |
103 | } | |
104 | ||
105 | break; | |
106 | } | |
524cf458 | 107 | default: |
bc67342e | 108 | if (*offset + 2 + optlen > size) |
524cf458 PF |
109 | return -ENOBUFS; |
110 | ||
20b958bf TG |
111 | options[*offset] = code; |
112 | options[*offset + 1] = optlen; | |
04b28be1 | 113 | |
75f32f04 | 114 | memcpy_safe(&options[*offset + 2], optval, optlen); |
bc67342e | 115 | *offset += 2 + optlen; |
524cf458 PF |
116 | |
117 | break; | |
118 | } | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
04b28be1 TG |
123 | int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, |
124 | uint8_t overload, | |
125 | uint8_t code, size_t optlen, const void *optval) { | |
bc67342e ZJS |
126 | const bool use_file = overload & DHCP_OVERLOAD_FILE; |
127 | const bool use_sname = overload & DHCP_OVERLOAD_SNAME; | |
04b28be1 TG |
128 | int r; |
129 | ||
130 | assert(message); | |
131 | assert(offset); | |
132 | ||
bc67342e ZJS |
133 | /* If *offset is in range [0, size), we are writing to ->options, |
134 | * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file, | |
135 | * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname)) | |
136 | * and use_sname, we are writing to ->sname. | |
137 | */ | |
04b28be1 TG |
138 | |
139 | if (*offset < size) { | |
140 | /* still space in the options array */ | |
141 | r = option_append(message->options, size, offset, code, optlen, optval); | |
142 | if (r >= 0) | |
143 | return 0; | |
bc67342e | 144 | else if (r == -ENOBUFS && (use_file || use_sname)) { |
04b28be1 TG |
145 | /* did not fit, but we have more buffers to try |
146 | close the options array and move the offset to its end */ | |
22805d92 | 147 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
148 | if (r < 0) |
149 | return r; | |
150 | ||
151 | *offset = size; | |
152 | } else | |
153 | return r; | |
154 | } | |
155 | ||
bc67342e ZJS |
156 | if (use_file) { |
157 | size_t file_offset = *offset - size; | |
04b28be1 TG |
158 | |
159 | if (file_offset < sizeof(message->file)) { | |
160 | /* still space in the 'file' array */ | |
161 | r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval); | |
162 | if (r >= 0) { | |
163 | *offset = size + file_offset; | |
164 | return 0; | |
bc67342e | 165 | } else if (r == -ENOBUFS && use_sname) { |
04b28be1 TG |
166 | /* did not fit, but we have more buffers to try |
167 | close the file array and move the offset to its end */ | |
22805d92 | 168 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
169 | if (r < 0) |
170 | return r; | |
171 | ||
172 | *offset = size + sizeof(message->file); | |
173 | } else | |
174 | return r; | |
175 | } | |
176 | } | |
177 | ||
bc67342e ZJS |
178 | if (use_sname) { |
179 | size_t sname_offset = *offset - size - use_file*sizeof(message->file); | |
04b28be1 TG |
180 | |
181 | if (sname_offset < sizeof(message->sname)) { | |
182 | /* still space in the 'sname' array */ | |
183 | r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval); | |
184 | if (r >= 0) { | |
bc67342e | 185 | *offset = size + use_file*sizeof(message->file) + sname_offset; |
04b28be1 | 186 | return 0; |
bc67342e | 187 | } else |
04b28be1 TG |
188 | /* no space, or other error, give up */ |
189 | return r; | |
04b28be1 TG |
190 | } |
191 | } | |
192 | ||
193 | return -ENOBUFS; | |
194 | } | |
195 | ||
32008a96 | 196 | static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, |
ccf86354 | 197 | uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, |
89ca10c6 | 198 | void *userdata) { |
32008a96 | 199 | uint8_t code, len; |
f693e9b3 | 200 | const uint8_t *option; |
32008a96 | 201 | size_t offset = 0; |
524cf458 | 202 | |
32008a96 | 203 | while (offset < buflen) { |
f693e9b3 | 204 | code = options[offset ++]; |
32008a96 | 205 | |
f693e9b3 | 206 | switch (code) { |
22805d92 | 207 | case SD_DHCP_OPTION_PAD: |
f693e9b3 | 208 | continue; |
524cf458 | 209 | |
22805d92 | 210 | case SD_DHCP_OPTION_END: |
524cf458 | 211 | return 0; |
f693e9b3 | 212 | } |
524cf458 | 213 | |
f693e9b3 TG |
214 | if (buflen < offset + 1) |
215 | return -ENOBUFS; | |
216 | ||
217 | len = options[offset ++]; | |
524cf458 | 218 | |
f693e9b3 TG |
219 | if (buflen < offset + len) |
220 | return -EINVAL; | |
221 | ||
222 | option = &options[offset]; | |
223 | ||
224 | switch (code) { | |
22805d92 | 225 | case SD_DHCP_OPTION_MESSAGE_TYPE: |
32008a96 | 226 | if (len != 1) |
524cf458 PF |
227 | return -EINVAL; |
228 | ||
229 | if (message_type) | |
f693e9b3 | 230 | *message_type = *option; |
524cf458 PF |
231 | |
232 | break; | |
233 | ||
22805d92 | 234 | case SD_DHCP_OPTION_ERROR_MESSAGE: |
f693e9b3 | 235 | if (len == 0) |
524cf458 PF |
236 | return -EINVAL; |
237 | ||
f693e9b3 TG |
238 | if (error_message) { |
239 | _cleanup_free_ char *string = NULL; | |
524cf458 | 240 | |
f693e9b3 TG |
241 | /* Accept a trailing NUL byte */ |
242 | if (memchr(option, 0, len - 1)) | |
243 | return -EINVAL; | |
524cf458 | 244 | |
79cd22d6 | 245 | string = memdup_suffix0((const char *) option, len); |
f693e9b3 TG |
246 | if (!string) |
247 | return -ENOMEM; | |
524cf458 | 248 | |
f693e9b3 TG |
249 | if (!ascii_is_valid(string)) |
250 | return -EINVAL; | |
524cf458 | 251 | |
f9ecfd3b | 252 | free_and_replace(*error_message, string); |
f693e9b3 | 253 | } |
524cf458 | 254 | |
f693e9b3 | 255 | break; |
22805d92 | 256 | case SD_DHCP_OPTION_OVERLOAD: |
f693e9b3 | 257 | if (len != 1) |
524cf458 | 258 | return -EINVAL; |
524cf458 | 259 | |
f693e9b3 TG |
260 | if (overload) |
261 | *overload = *option; | |
524cf458 | 262 | |
f693e9b3 TG |
263 | break; |
264 | ||
265 | default: | |
266 | if (cb) | |
267 | cb(code, len, option, userdata); | |
524cf458 PF |
268 | |
269 | break; | |
270 | } | |
f693e9b3 TG |
271 | |
272 | offset += len; | |
524cf458 PF |
273 | } |
274 | ||
32008a96 | 275 | if (offset < buflen) |
524cf458 PF |
276 | return -EINVAL; |
277 | ||
278 | return 0; | |
279 | } | |
280 | ||
ccf86354 | 281 | int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) { |
f693e9b3 | 282 | _cleanup_free_ char *error_message = NULL; |
524cf458 PF |
283 | uint8_t overload = 0; |
284 | uint8_t message_type = 0; | |
32008a96 | 285 | int r; |
524cf458 PF |
286 | |
287 | if (!message) | |
288 | return -EINVAL; | |
289 | ||
3b7ca119 | 290 | if (len < sizeof(DHCPMessage)) |
524cf458 PF |
291 | return -EINVAL; |
292 | ||
3b7ca119 | 293 | len -= sizeof(DHCPMessage); |
524cf458 | 294 | |
f693e9b3 | 295 | r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); |
32008a96 TG |
296 | if (r < 0) |
297 | return r; | |
524cf458 PF |
298 | |
299 | if (overload & DHCP_OVERLOAD_FILE) { | |
f693e9b3 | 300 | r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
301 | if (r < 0) |
302 | return r; | |
524cf458 PF |
303 | } |
304 | ||
305 | if (overload & DHCP_OVERLOAD_SNAME) { | |
f693e9b3 | 306 | r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
307 | if (r < 0) |
308 | return r; | |
524cf458 PF |
309 | } |
310 | ||
f693e9b3 TG |
311 | if (message_type == 0) |
312 | return -ENOMSG; | |
313 | ||
ae2a15bc LP |
314 | if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) |
315 | *_error_message = TAKE_PTR(error_message); | |
524cf458 | 316 | |
f693e9b3 | 317 | return message_type; |
524cf458 | 318 | } |
461dbb2f YW |
319 | |
320 | static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) { | |
321 | if (!i) | |
322 | return NULL; | |
323 | ||
324 | free(i->data); | |
325 | return mfree(i); | |
326 | } | |
327 | ||
328 | int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) { | |
329 | assert_return(ret, -EINVAL); | |
330 | assert_return(length == 0 || data, -EINVAL); | |
331 | ||
332 | _cleanup_free_ void *q = memdup(data, length); | |
333 | if (!q) | |
334 | return -ENOMEM; | |
335 | ||
336 | sd_dhcp_option *p = new(sd_dhcp_option, 1); | |
337 | if (!p) | |
338 | return -ENOMEM; | |
339 | ||
340 | *p = (sd_dhcp_option) { | |
341 | .n_ref = 1, | |
342 | .option = option, | |
343 | .length = length, | |
344 | .data = TAKE_PTR(q), | |
345 | }; | |
346 | ||
347 | *ret = TAKE_PTR(p); | |
348 | return 0; | |
349 | } | |
350 | ||
351 | DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free); | |
352 | DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( | |
353 | dhcp_option_hash_ops, | |
354 | void, | |
355 | trivial_hash_func, | |
356 | trivial_compare_func, | |
357 | sd_dhcp_option, | |
358 | sd_dhcp_option_unref); |