]>
Commit | Line | Data |
---|---|---|
524cf458 PF |
1 | /*** |
2 | This file is part of systemd. | |
3 | ||
4 | Copyright (C) 2013 Intel Corporation. All rights reserved. | |
5 | ||
6 | systemd is free software; you can redistribute it and/or modify it | |
7 | under the terms of the GNU Lesser General Public License as published by | |
8 | the Free Software Foundation; either version 2.1 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | systemd is distributed in the hope that it will be useful, but | |
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public License | |
17 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
18 | ***/ | |
19 | ||
524cf458 | 20 | #include <errno.h> |
cf0fbc49 | 21 | #include <stdint.h> |
524cf458 | 22 | #include <stdio.h> |
cf0fbc49 | 23 | #include <string.h> |
524cf458 | 24 | |
f693e9b3 TG |
25 | #include "alloc-util.h" |
26 | #include "utf8.h" | |
27 | ||
524cf458 PF |
28 | #include "dhcp-internal.h" |
29 | ||
04b28be1 TG |
30 | static int option_append(uint8_t options[], size_t size, size_t *offset, |
31 | uint8_t code, size_t optlen, const void *optval) { | |
20b958bf TG |
32 | assert(options); |
33 | assert(offset); | |
524cf458 | 34 | |
22805d92 | 35 | if (code != SD_DHCP_OPTION_END) |
2688ef60 TG |
36 | /* always make sure there is space for an END option */ |
37 | size --; | |
38 | ||
524cf458 PF |
39 | switch (code) { |
40 | ||
22805d92 BG |
41 | case SD_DHCP_OPTION_PAD: |
42 | case SD_DHCP_OPTION_END: | |
04b28be1 | 43 | if (size < *offset + 1) |
524cf458 PF |
44 | return -ENOBUFS; |
45 | ||
20b958bf TG |
46 | options[*offset] = code; |
47 | *offset += 1; | |
524cf458 PF |
48 | break; |
49 | ||
50 | default: | |
04b28be1 | 51 | if (size < *offset + optlen + 2) |
524cf458 PF |
52 | return -ENOBUFS; |
53 | ||
20b958bf TG |
54 | options[*offset] = code; |
55 | options[*offset + 1] = optlen; | |
04b28be1 | 56 | |
75f32f04 | 57 | memcpy_safe(&options[*offset + 2], optval, optlen); |
20b958bf | 58 | *offset += optlen + 2; |
524cf458 PF |
59 | |
60 | break; | |
61 | } | |
62 | ||
63 | return 0; | |
64 | } | |
65 | ||
04b28be1 TG |
66 | int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, |
67 | uint8_t overload, | |
68 | uint8_t code, size_t optlen, const void *optval) { | |
69 | size_t file_offset = 0, sname_offset =0; | |
70 | bool file, sname; | |
71 | int r; | |
72 | ||
73 | assert(message); | |
74 | assert(offset); | |
75 | ||
76 | file = overload & DHCP_OVERLOAD_FILE; | |
77 | sname = overload & DHCP_OVERLOAD_SNAME; | |
78 | ||
79 | if (*offset < size) { | |
80 | /* still space in the options array */ | |
81 | r = option_append(message->options, size, offset, code, optlen, optval); | |
82 | if (r >= 0) | |
83 | return 0; | |
84 | else if (r == -ENOBUFS && (file || sname)) { | |
85 | /* did not fit, but we have more buffers to try | |
86 | close the options array and move the offset to its end */ | |
22805d92 | 87 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
88 | if (r < 0) |
89 | return r; | |
90 | ||
91 | *offset = size; | |
92 | } else | |
93 | return r; | |
94 | } | |
95 | ||
96 | if (overload & DHCP_OVERLOAD_FILE) { | |
97 | file_offset = *offset - size; | |
98 | ||
99 | if (file_offset < sizeof(message->file)) { | |
100 | /* still space in the 'file' array */ | |
101 | r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval); | |
102 | if (r >= 0) { | |
103 | *offset = size + file_offset; | |
104 | return 0; | |
105 | } else if (r == -ENOBUFS && sname) { | |
106 | /* did not fit, but we have more buffers to try | |
107 | close the file array and move the offset to its end */ | |
22805d92 | 108 | r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); |
04b28be1 TG |
109 | if (r < 0) |
110 | return r; | |
111 | ||
112 | *offset = size + sizeof(message->file); | |
113 | } else | |
114 | return r; | |
115 | } | |
116 | } | |
117 | ||
118 | if (overload & DHCP_OVERLOAD_SNAME) { | |
119 | sname_offset = *offset - size - (file ? sizeof(message->file) : 0); | |
120 | ||
121 | if (sname_offset < sizeof(message->sname)) { | |
122 | /* still space in the 'sname' array */ | |
123 | r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval); | |
124 | if (r >= 0) { | |
125 | *offset = size + (file ? sizeof(message->file) : 0) + sname_offset; | |
126 | return 0; | |
127 | } else { | |
128 | /* no space, or other error, give up */ | |
129 | return r; | |
130 | } | |
131 | } | |
132 | } | |
133 | ||
134 | return -ENOBUFS; | |
135 | } | |
136 | ||
32008a96 | 137 | static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, |
ccf86354 | 138 | uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, |
89ca10c6 | 139 | void *userdata) { |
32008a96 | 140 | uint8_t code, len; |
f693e9b3 | 141 | const uint8_t *option; |
32008a96 | 142 | size_t offset = 0; |
524cf458 | 143 | |
32008a96 | 144 | while (offset < buflen) { |
f693e9b3 | 145 | code = options[offset ++]; |
32008a96 | 146 | |
f693e9b3 | 147 | switch (code) { |
22805d92 | 148 | case SD_DHCP_OPTION_PAD: |
f693e9b3 | 149 | continue; |
524cf458 | 150 | |
22805d92 | 151 | case SD_DHCP_OPTION_END: |
524cf458 | 152 | return 0; |
f693e9b3 | 153 | } |
524cf458 | 154 | |
f693e9b3 TG |
155 | if (buflen < offset + 1) |
156 | return -ENOBUFS; | |
157 | ||
158 | len = options[offset ++]; | |
524cf458 | 159 | |
f693e9b3 TG |
160 | if (buflen < offset + len) |
161 | return -EINVAL; | |
162 | ||
163 | option = &options[offset]; | |
164 | ||
165 | switch (code) { | |
22805d92 | 166 | case SD_DHCP_OPTION_MESSAGE_TYPE: |
32008a96 | 167 | if (len != 1) |
524cf458 PF |
168 | return -EINVAL; |
169 | ||
170 | if (message_type) | |
f693e9b3 | 171 | *message_type = *option; |
524cf458 PF |
172 | |
173 | break; | |
174 | ||
22805d92 | 175 | case SD_DHCP_OPTION_ERROR_MESSAGE: |
f693e9b3 | 176 | if (len == 0) |
524cf458 PF |
177 | return -EINVAL; |
178 | ||
f693e9b3 TG |
179 | if (error_message) { |
180 | _cleanup_free_ char *string = NULL; | |
524cf458 | 181 | |
f693e9b3 TG |
182 | /* Accept a trailing NUL byte */ |
183 | if (memchr(option, 0, len - 1)) | |
184 | return -EINVAL; | |
524cf458 | 185 | |
f693e9b3 TG |
186 | string = strndup((const char *) option, len); |
187 | if (!string) | |
188 | return -ENOMEM; | |
524cf458 | 189 | |
f693e9b3 TG |
190 | if (!ascii_is_valid(string)) |
191 | return -EINVAL; | |
524cf458 | 192 | |
f693e9b3 TG |
193 | free(*error_message); |
194 | *error_message = string; | |
195 | string = NULL; | |
196 | } | |
524cf458 | 197 | |
f693e9b3 | 198 | break; |
22805d92 | 199 | case SD_DHCP_OPTION_OVERLOAD: |
f693e9b3 | 200 | if (len != 1) |
524cf458 | 201 | return -EINVAL; |
524cf458 | 202 | |
f693e9b3 TG |
203 | if (overload) |
204 | *overload = *option; | |
524cf458 | 205 | |
f693e9b3 TG |
206 | break; |
207 | ||
208 | default: | |
209 | if (cb) | |
210 | cb(code, len, option, userdata); | |
524cf458 PF |
211 | |
212 | break; | |
213 | } | |
f693e9b3 TG |
214 | |
215 | offset += len; | |
524cf458 PF |
216 | } |
217 | ||
32008a96 | 218 | if (offset < buflen) |
524cf458 PF |
219 | return -EINVAL; |
220 | ||
221 | return 0; | |
222 | } | |
223 | ||
ccf86354 | 224 | int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) { |
f693e9b3 | 225 | _cleanup_free_ char *error_message = NULL; |
524cf458 PF |
226 | uint8_t overload = 0; |
227 | uint8_t message_type = 0; | |
32008a96 | 228 | int r; |
524cf458 PF |
229 | |
230 | if (!message) | |
231 | return -EINVAL; | |
232 | ||
3b7ca119 | 233 | if (len < sizeof(DHCPMessage)) |
524cf458 PF |
234 | return -EINVAL; |
235 | ||
3b7ca119 | 236 | len -= sizeof(DHCPMessage); |
524cf458 | 237 | |
f693e9b3 | 238 | r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); |
32008a96 TG |
239 | if (r < 0) |
240 | return r; | |
524cf458 PF |
241 | |
242 | if (overload & DHCP_OVERLOAD_FILE) { | |
f693e9b3 | 243 | r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
244 | if (r < 0) |
245 | return r; | |
524cf458 PF |
246 | } |
247 | ||
248 | if (overload & DHCP_OVERLOAD_SNAME) { | |
f693e9b3 | 249 | r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); |
32008a96 TG |
250 | if (r < 0) |
251 | return r; | |
524cf458 PF |
252 | } |
253 | ||
f693e9b3 TG |
254 | if (message_type == 0) |
255 | return -ENOMSG; | |
256 | ||
257 | if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) { | |
258 | *_error_message = error_message; | |
259 | error_message = NULL; | |
260 | } | |
524cf458 | 261 | |
f693e9b3 | 262 | return message_type; |
524cf458 | 263 | } |