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