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