]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp-option.c
sd-boot+bootctl: invert order of entries w/o sort-key
[thirdparty/systemd.git] / src / libsystemd-network / dhcp-option.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3 Copyright © 2013 Intel Corporation. All rights reserved.
4 ***/
5
6 #include <errno.h>
7 #include <stdint.h>
8 #include <stdio.h>
9
10 #include "alloc-util.h"
11 #include "dhcp-internal.h"
12 #include "dhcp-server-internal.h"
13 #include "memory-util.h"
14 #include "strv.h"
15 #include "utf8.h"
16
17 /* Append type-length value structure to the options buffer */
18 static int dhcp_option_append_tlv(uint8_t options[], size_t size, size_t *offset, uint8_t code, size_t optlen, const void *optval) {
19 assert(options);
20 assert(size > 0);
21 assert(offset);
22 assert(optlen <= UINT8_MAX);
23 assert(*offset < size);
24
25 if (*offset + 2 + optlen > size)
26 return -ENOBUFS;
27
28 options[*offset] = code;
29 options[*offset + 1] = optlen;
30
31 memcpy_safe(&options[*offset + 2], optval, optlen);
32 *offset += 2 + optlen;
33 return 0;
34 }
35
36 static int option_append(uint8_t options[], size_t size, size_t *offset,
37 uint8_t code, size_t optlen, const void *optval) {
38 assert(options);
39 assert(size > 0);
40 assert(offset);
41
42 int r;
43
44 if (code != SD_DHCP_OPTION_END)
45 /* always make sure there is space for an END option */
46 size--;
47
48 switch (code) {
49
50 case SD_DHCP_OPTION_PAD:
51 case SD_DHCP_OPTION_END:
52 if (*offset + 1 > size)
53 return -ENOBUFS;
54
55 options[*offset] = code;
56 *offset += 1;
57 break;
58
59 case SD_DHCP_OPTION_USER_CLASS: {
60 size_t total = 0;
61 char **s;
62
63 if (strv_isempty((char **) optval))
64 return -EINVAL;
65
66 STRV_FOREACH(s, (char **) optval) {
67 size_t len = strlen(*s);
68
69 if (len > 255 || len == 0)
70 return -EINVAL;
71
72 total += 1 + len;
73 }
74
75 if (*offset + 2 + total > size)
76 return -ENOBUFS;
77
78 options[*offset] = code;
79 options[*offset + 1] = total;
80 *offset += 2;
81
82 STRV_FOREACH(s, (char **) optval) {
83 size_t len = strlen(*s);
84
85 options[*offset] = len;
86 memcpy(&options[*offset + 1], *s, len);
87 *offset += 1 + len;
88 }
89
90 break;
91 }
92 case SD_DHCP_OPTION_SIP_SERVER:
93 if (*offset + 3 + optlen > size)
94 return -ENOBUFS;
95
96 options[*offset] = code;
97 options[*offset + 1] = optlen + 1;
98 options[*offset + 2] = 1;
99
100 memcpy_safe(&options[*offset + 3], optval, optlen);
101 *offset += 3 + optlen;
102
103 break;
104 case SD_DHCP_OPTION_VENDOR_SPECIFIC: {
105 OrderedSet *s = (OrderedSet *) optval;
106 struct sd_dhcp_option *p;
107 size_t l = 0;
108
109 ORDERED_SET_FOREACH(p, s)
110 l += p->length + 2;
111
112 if (*offset + l + 2 > size)
113 return -ENOBUFS;
114
115 options[*offset] = code;
116 options[*offset + 1] = l;
117 *offset += 2;
118
119 ORDERED_SET_FOREACH(p, s) {
120 r = dhcp_option_append_tlv(options, size, offset, p->option, p->length, p->data);
121 if (r < 0)
122 return r;
123 }
124 break;
125 }
126 case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: {
127 sd_dhcp_server *server = (sd_dhcp_server *) optval;
128 size_t current_offset = *offset + 2;
129
130 if (server->agent_circuit_id) {
131 r = dhcp_option_append_tlv(options, size, &current_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID,
132 strlen(server->agent_circuit_id), server->agent_circuit_id);
133 if (r < 0)
134 return r;
135 }
136 if (server->agent_remote_id) {
137 r = dhcp_option_append_tlv(options, size, &current_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID,
138 strlen(server->agent_remote_id), server->agent_remote_id);
139 if (r < 0)
140 return r;
141 }
142
143 options[*offset] = code;
144 options[*offset + 1] = current_offset - *offset - 2;
145 assert(current_offset - *offset - 2 <= UINT8_MAX);
146 *offset = current_offset;
147 break;
148 }
149 default:
150 return dhcp_option_append_tlv(options, size, offset, code, optlen, optval);
151 }
152 return 0;
153 }
154
155 static int option_length(uint8_t *options, size_t length, size_t offset) {
156 assert(options);
157 assert(offset < length);
158
159 if (IN_SET(options[offset], SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END))
160 return 1;
161 if (length < offset + 2)
162 return -ENOBUFS;
163
164 /* validating that buffer is long enough */
165 if (length < offset + 2 + options[offset + 1])
166 return -ENOBUFS;
167
168 return options[offset + 1] + 2;
169 }
170
171 int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t code, size_t *ret_offset) {
172 int r;
173
174 assert(options);
175 assert(ret_offset);
176
177 for (size_t offset = 0; offset < length; offset += r) {
178 r = option_length(options, length, offset);
179 if (r < 0)
180 return r;
181
182 if (code == options[offset]) {
183 *ret_offset = offset;
184 return r;
185 }
186 }
187 return -ENOENT;
188 }
189
190 int dhcp_option_remove_option(uint8_t *options, size_t length, uint8_t option_code) {
191 int r;
192 size_t offset;
193
194 assert(options);
195
196 r = dhcp_option_find_option(options, length, option_code, &offset);
197 if (r < 0)
198 return r;
199
200 memmove(options + offset, options + offset + r, length - offset - r);
201 return length - r;
202 }
203
204 int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
205 uint8_t overload,
206 uint8_t code, size_t optlen, const void *optval) {
207 const bool use_file = overload & DHCP_OVERLOAD_FILE;
208 const bool use_sname = overload & DHCP_OVERLOAD_SNAME;
209 int r;
210
211 assert(message);
212 assert(offset);
213
214 /* If *offset is in range [0, size), we are writing to ->options,
215 * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
216 * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
217 * and use_sname, we are writing to ->sname.
218 */
219
220 if (*offset < size) {
221 /* still space in the options array */
222 r = option_append(message->options, size, offset, code, optlen, optval);
223 if (r >= 0)
224 return 0;
225 else if (r == -ENOBUFS && (use_file || use_sname)) {
226 /* did not fit, but we have more buffers to try
227 close the options array and move the offset to its end */
228 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
229 if (r < 0)
230 return r;
231
232 *offset = size;
233 } else
234 return r;
235 }
236
237 if (use_file) {
238 size_t file_offset = *offset - size;
239
240 if (file_offset < sizeof(message->file)) {
241 /* still space in the 'file' array */
242 r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
243 if (r >= 0) {
244 *offset = size + file_offset;
245 return 0;
246 } else if (r == -ENOBUFS && use_sname) {
247 /* did not fit, but we have more buffers to try
248 close the file array and move the offset to its end */
249 r = option_append(message->file, sizeof(message->file), &file_offset, SD_DHCP_OPTION_END, 0, NULL);
250 if (r < 0)
251 return r;
252
253 *offset = size + sizeof(message->file);
254 } else
255 return r;
256 }
257 }
258
259 if (use_sname) {
260 size_t sname_offset = *offset - size - use_file*sizeof(message->file);
261
262 if (sname_offset < sizeof(message->sname)) {
263 /* still space in the 'sname' array */
264 r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
265 if (r >= 0) {
266 *offset = size + use_file*sizeof(message->file) + sname_offset;
267 return 0;
268 } else
269 /* no space, or other error, give up */
270 return r;
271 }
272 }
273
274 return -ENOBUFS;
275 }
276
277 static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
278 uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
279 void *userdata) {
280 uint8_t code, len;
281 const uint8_t *option;
282 size_t offset = 0;
283
284 while (offset < buflen) {
285 code = options[offset ++];
286
287 switch (code) {
288 case SD_DHCP_OPTION_PAD:
289 continue;
290
291 case SD_DHCP_OPTION_END:
292 return 0;
293 }
294
295 if (buflen < offset + 1)
296 return -ENOBUFS;
297
298 len = options[offset ++];
299
300 if (buflen < offset + len)
301 return -EINVAL;
302
303 option = &options[offset];
304
305 switch (code) {
306 case SD_DHCP_OPTION_MESSAGE_TYPE:
307 if (len != 1)
308 return -EINVAL;
309
310 if (message_type)
311 *message_type = *option;
312
313 break;
314
315 case SD_DHCP_OPTION_ERROR_MESSAGE:
316 if (len == 0)
317 return -EINVAL;
318
319 if (error_message) {
320 _cleanup_free_ char *string = NULL;
321
322 /* Accept a trailing NUL byte */
323 if (memchr(option, 0, len - 1))
324 return -EINVAL;
325
326 string = memdup_suffix0((const char *) option, len);
327 if (!string)
328 return -ENOMEM;
329
330 if (!ascii_is_valid(string))
331 return -EINVAL;
332
333 free_and_replace(*error_message, string);
334 }
335
336 break;
337 case SD_DHCP_OPTION_OVERLOAD:
338 if (len != 1)
339 return -EINVAL;
340
341 if (overload)
342 *overload = *option;
343
344 break;
345
346 default:
347 if (cb)
348 cb(code, len, option, userdata);
349
350 break;
351 }
352
353 offset += len;
354 }
355
356 if (offset < buflen)
357 return -EINVAL;
358
359 return 0;
360 }
361
362 int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
363 _cleanup_free_ char *error_message = NULL;
364 uint8_t overload = 0;
365 uint8_t message_type = 0;
366 int r;
367
368 if (!message)
369 return -EINVAL;
370
371 if (len < sizeof(DHCPMessage))
372 return -EINVAL;
373
374 len -= sizeof(DHCPMessage);
375
376 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
377 if (r < 0)
378 return r;
379
380 if (overload & DHCP_OVERLOAD_FILE) {
381 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
382 if (r < 0)
383 return r;
384 }
385
386 if (overload & DHCP_OVERLOAD_SNAME) {
387 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
388 if (r < 0)
389 return r;
390 }
391
392 if (message_type == 0)
393 return -ENOMSG;
394
395 if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
396 *_error_message = TAKE_PTR(error_message);
397
398 return message_type;
399 }
400
401 static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) {
402 if (!i)
403 return NULL;
404
405 free(i->data);
406 return mfree(i);
407 }
408
409 int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) {
410 assert_return(ret, -EINVAL);
411 assert_return(length == 0 || data, -EINVAL);
412
413 _cleanup_free_ void *q = memdup(data, length);
414 if (!q)
415 return -ENOMEM;
416
417 sd_dhcp_option *p = new(sd_dhcp_option, 1);
418 if (!p)
419 return -ENOMEM;
420
421 *p = (sd_dhcp_option) {
422 .n_ref = 1,
423 .option = option,
424 .length = length,
425 .data = TAKE_PTR(q),
426 };
427
428 *ret = TAKE_PTR(p);
429 return 0;
430 }
431
432 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free);
433 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
434 dhcp_option_hash_ops,
435 void,
436 trivial_hash_func,
437 trivial_compare_func,
438 sd_dhcp_option,
439 sd_dhcp_option_unref);