]>
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 | 11 | #include "alloc-util.h" |
524cf458 | 12 | #include "dhcp-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; | |
524cf458 | 80 | default: |
bc67342e | 81 | if (*offset + 2 + optlen > size) |
524cf458 PF |
82 | return -ENOBUFS; |
83 | ||
20b958bf TG |
84 | options[*offset] = code; |
85 | options[*offset + 1] = optlen; | |
04b28be1 | 86 | |
75f32f04 | 87 | memcpy_safe(&options[*offset + 2], optval, optlen); |
bc67342e | 88 | *offset += 2 + optlen; |
524cf458 PF |
89 | |
90 | break; | |
91 | } | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
04b28be1 TG |
96 | int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, |
97 | uint8_t overload, | |
98 | uint8_t code, size_t optlen, const void *optval) { | |
bc67342e ZJS |
99 | const bool use_file = overload & DHCP_OVERLOAD_FILE; |
100 | const bool use_sname = overload & DHCP_OVERLOAD_SNAME; | |
04b28be1 TG |
101 | int r; |
102 | ||
103 | assert(message); | |
104 | assert(offset); | |
105 | ||
bc67342e ZJS |
106 | /* If *offset is in range [0, size), we are writing to ->options, |
107 | * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file, | |
108 | * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname)) | |
109 | * and use_sname, we are writing to ->sname. | |
110 | */ | |
04b28be1 TG |
111 | |
112 | if (*offset < size) { | |
113 | /* still space in the options array */ | |
114 | r = option_append(message->options, size, offset, code, optlen, optval); | |
115 | if (r >= 0) | |
116 | return 0; | |
bc67342e | 117 | else if (r == -ENOBUFS && (use_file || use_sname)) { |
04b28be1 TG |
118 | /* did not fit, but we have more buffers to try |
119 | close the options array and move the offset to its end */ | |
22805d92 | 120 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
121 | if (r < 0) |
122 | return r; | |
123 | ||
124 | *offset = size; | |
125 | } else | |
126 | return r; | |
127 | } | |
128 | ||
bc67342e ZJS |
129 | if (use_file) { |
130 | size_t file_offset = *offset - size; | |
04b28be1 TG |
131 | |
132 | if (file_offset < sizeof(message->file)) { | |
133 | /* still space in the 'file' array */ | |
134 | r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval); | |
135 | if (r >= 0) { | |
136 | *offset = size + file_offset; | |
137 | return 0; | |
bc67342e | 138 | } else if (r == -ENOBUFS && use_sname) { |
04b28be1 TG |
139 | /* did not fit, but we have more buffers to try |
140 | close the file array and move the offset to its end */ | |
22805d92 | 141 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
142 | if (r < 0) |
143 | return r; | |
144 | ||
145 | *offset = size + sizeof(message->file); | |
146 | } else | |
147 | return r; | |
148 | } | |
149 | } | |
150 | ||
bc67342e ZJS |
151 | if (use_sname) { |
152 | size_t sname_offset = *offset - size - use_file*sizeof(message->file); | |
04b28be1 TG |
153 | |
154 | if (sname_offset < sizeof(message->sname)) { | |
155 | /* still space in the 'sname' array */ | |
156 | r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval); | |
157 | if (r >= 0) { | |
bc67342e | 158 | *offset = size + use_file*sizeof(message->file) + sname_offset; |
04b28be1 | 159 | return 0; |
bc67342e | 160 | } else |
04b28be1 TG |
161 | /* no space, or other error, give up */ |
162 | return r; | |
04b28be1 TG |
163 | } |
164 | } | |
165 | ||
166 | return -ENOBUFS; | |
167 | } | |
168 | ||
32008a96 | 169 | static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, |
ccf86354 | 170 | uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, |
89ca10c6 | 171 | void *userdata) { |
32008a96 | 172 | uint8_t code, len; |
f693e9b3 | 173 | const uint8_t *option; |
32008a96 | 174 | size_t offset = 0; |
524cf458 | 175 | |
32008a96 | 176 | while (offset < buflen) { |
f693e9b3 | 177 | code = options[offset ++]; |
32008a96 | 178 | |
f693e9b3 | 179 | switch (code) { |
22805d92 | 180 | case SD_DHCP_OPTION_PAD: |
f693e9b3 | 181 | continue; |
524cf458 | 182 | |
22805d92 | 183 | case SD_DHCP_OPTION_END: |
524cf458 | 184 | return 0; |
f693e9b3 | 185 | } |
524cf458 | 186 | |
f693e9b3 TG |
187 | if (buflen < offset + 1) |
188 | return -ENOBUFS; | |
189 | ||
190 | len = options[offset ++]; | |
524cf458 | 191 | |
f693e9b3 TG |
192 | if (buflen < offset + len) |
193 | return -EINVAL; | |
194 | ||
195 | option = &options[offset]; | |
196 | ||
197 | switch (code) { | |
22805d92 | 198 | case SD_DHCP_OPTION_MESSAGE_TYPE: |
32008a96 | 199 | if (len != 1) |
524cf458 PF |
200 | return -EINVAL; |
201 | ||
202 | if (message_type) | |
f693e9b3 | 203 | *message_type = *option; |
524cf458 PF |
204 | |
205 | break; | |
206 | ||
22805d92 | 207 | case SD_DHCP_OPTION_ERROR_MESSAGE: |
f693e9b3 | 208 | if (len == 0) |
524cf458 PF |
209 | return -EINVAL; |
210 | ||
f693e9b3 TG |
211 | if (error_message) { |
212 | _cleanup_free_ char *string = NULL; | |
524cf458 | 213 | |
f693e9b3 TG |
214 | /* Accept a trailing NUL byte */ |
215 | if (memchr(option, 0, len - 1)) | |
216 | return -EINVAL; | |
524cf458 | 217 | |
79cd22d6 | 218 | string = memdup_suffix0((const char *) option, len); |
f693e9b3 TG |
219 | if (!string) |
220 | return -ENOMEM; | |
524cf458 | 221 | |
f693e9b3 TG |
222 | if (!ascii_is_valid(string)) |
223 | return -EINVAL; | |
524cf458 | 224 | |
f9ecfd3b | 225 | free_and_replace(*error_message, string); |
f693e9b3 | 226 | } |
524cf458 | 227 | |
f693e9b3 | 228 | break; |
22805d92 | 229 | case SD_DHCP_OPTION_OVERLOAD: |
f693e9b3 | 230 | if (len != 1) |
524cf458 | 231 | return -EINVAL; |
524cf458 | 232 | |
f693e9b3 TG |
233 | if (overload) |
234 | *overload = *option; | |
524cf458 | 235 | |
f693e9b3 TG |
236 | break; |
237 | ||
238 | default: | |
239 | if (cb) | |
240 | cb(code, len, option, userdata); | |
524cf458 PF |
241 | |
242 | break; | |
243 | } | |
f693e9b3 TG |
244 | |
245 | offset += len; | |
524cf458 PF |
246 | } |
247 | ||
32008a96 | 248 | if (offset < buflen) |
524cf458 PF |
249 | return -EINVAL; |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
ccf86354 | 254 | int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) { |
f693e9b3 | 255 | _cleanup_free_ char *error_message = NULL; |
524cf458 PF |
256 | uint8_t overload = 0; |
257 | uint8_t message_type = 0; | |
32008a96 | 258 | int r; |
524cf458 PF |
259 | |
260 | if (!message) | |
261 | return -EINVAL; | |
262 | ||
3b7ca119 | 263 | if (len < sizeof(DHCPMessage)) |
524cf458 PF |
264 | return -EINVAL; |
265 | ||
3b7ca119 | 266 | len -= sizeof(DHCPMessage); |
524cf458 | 267 | |
f693e9b3 | 268 | r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); |
32008a96 TG |
269 | if (r < 0) |
270 | return r; | |
524cf458 PF |
271 | |
272 | if (overload & DHCP_OVERLOAD_FILE) { | |
f693e9b3 | 273 | r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
274 | if (r < 0) |
275 | return r; | |
524cf458 PF |
276 | } |
277 | ||
278 | if (overload & DHCP_OVERLOAD_SNAME) { | |
f693e9b3 | 279 | r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
280 | if (r < 0) |
281 | return r; | |
524cf458 PF |
282 | } |
283 | ||
f693e9b3 TG |
284 | if (message_type == 0) |
285 | return -ENOMSG; | |
286 | ||
ae2a15bc LP |
287 | if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) |
288 | *_error_message = TAKE_PTR(error_message); | |
524cf458 | 289 | |
f693e9b3 | 290 | return message_type; |
524cf458 | 291 | } |