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