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