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