]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp-option.c
061cfde2f056c3cf6d34cc7ea3cc12bdd4e01f5a
[thirdparty/systemd.git] / src / libsystemd-network / dhcp-option.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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 static int option_append(uint8_t options[], size_t size, size_t *offset,
18 uint8_t code, size_t optlen, const void *optval) {
19 assert(options);
20 assert(offset);
21
22 if (code != SD_DHCP_OPTION_END)
23 /* always make sure there is space for an END option */
24 size--;
25
26 switch (code) {
27
28 case SD_DHCP_OPTION_PAD:
29 case SD_DHCP_OPTION_END:
30 if (*offset + 1 > size)
31 return -ENOBUFS;
32
33 options[*offset] = code;
34 *offset += 1;
35 break;
36
37 case SD_DHCP_OPTION_USER_CLASS: {
38 size_t total = 0;
39 char **s;
40
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 }
49
50 if (*offset + 2 + total > size)
51 return -ENOBUFS;
52
53 options[*offset] = code;
54 options[*offset + 1] = total;
55 *offset += 2;
56
57 STRV_FOREACH(s, (char **) optval) {
58 size_t len = strlen(*s);
59
60 options[*offset] = len;
61
62 memcpy(&options[*offset + 1], *s, len);
63 *offset += 1 + len;
64 }
65
66 break;
67 }
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;
80 case SD_DHCP_OPTION_VENDOR_SPECIFIC: {
81 OrderedHashmap *s = (OrderedHashmap *) optval;
82 struct sd_dhcp_raw_option *p;
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) {
98 options[*offset] = p->type;
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 }
106 default:
107 if (*offset + 2 + optlen > size)
108 return -ENOBUFS;
109
110 options[*offset] = code;
111 options[*offset + 1] = optlen;
112
113 memcpy_safe(&options[*offset + 2], optval, optlen);
114 *offset += 2 + optlen;
115
116 break;
117 }
118
119 return 0;
120 }
121
122 int 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) {
125 const bool use_file = overload & DHCP_OVERLOAD_FILE;
126 const bool use_sname = overload & DHCP_OVERLOAD_SNAME;
127 int r;
128
129 assert(message);
130 assert(offset);
131
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 */
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;
143 else if (r == -ENOBUFS && (use_file || use_sname)) {
144 /* did not fit, but we have more buffers to try
145 close the options array and move the offset to its end */
146 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
147 if (r < 0)
148 return r;
149
150 *offset = size;
151 } else
152 return r;
153 }
154
155 if (use_file) {
156 size_t file_offset = *offset - size;
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;
164 } else if (r == -ENOBUFS && use_sname) {
165 /* did not fit, but we have more buffers to try
166 close the file array and move the offset to its end */
167 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
168 if (r < 0)
169 return r;
170
171 *offset = size + sizeof(message->file);
172 } else
173 return r;
174 }
175 }
176
177 if (use_sname) {
178 size_t sname_offset = *offset - size - use_file*sizeof(message->file);
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) {
184 *offset = size + use_file*sizeof(message->file) + sname_offset;
185 return 0;
186 } else
187 /* no space, or other error, give up */
188 return r;
189 }
190 }
191
192 return -ENOBUFS;
193 }
194
195 static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
196 uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
197 void *userdata) {
198 uint8_t code, len;
199 const uint8_t *option;
200 size_t offset = 0;
201
202 while (offset < buflen) {
203 code = options[offset ++];
204
205 switch (code) {
206 case SD_DHCP_OPTION_PAD:
207 continue;
208
209 case SD_DHCP_OPTION_END:
210 return 0;
211 }
212
213 if (buflen < offset + 1)
214 return -ENOBUFS;
215
216 len = options[offset ++];
217
218 if (buflen < offset + len)
219 return -EINVAL;
220
221 option = &options[offset];
222
223 switch (code) {
224 case SD_DHCP_OPTION_MESSAGE_TYPE:
225 if (len != 1)
226 return -EINVAL;
227
228 if (message_type)
229 *message_type = *option;
230
231 break;
232
233 case SD_DHCP_OPTION_ERROR_MESSAGE:
234 if (len == 0)
235 return -EINVAL;
236
237 if (error_message) {
238 _cleanup_free_ char *string = NULL;
239
240 /* Accept a trailing NUL byte */
241 if (memchr(option, 0, len - 1))
242 return -EINVAL;
243
244 string = memdup_suffix0((const char *) option, len);
245 if (!string)
246 return -ENOMEM;
247
248 if (!ascii_is_valid(string))
249 return -EINVAL;
250
251 free_and_replace(*error_message, string);
252 }
253
254 break;
255 case SD_DHCP_OPTION_OVERLOAD:
256 if (len != 1)
257 return -EINVAL;
258
259 if (overload)
260 *overload = *option;
261
262 break;
263
264 default:
265 if (cb)
266 cb(code, len, option, userdata);
267
268 break;
269 }
270
271 offset += len;
272 }
273
274 if (offset < buflen)
275 return -EINVAL;
276
277 return 0;
278 }
279
280 int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
281 _cleanup_free_ char *error_message = NULL;
282 uint8_t overload = 0;
283 uint8_t message_type = 0;
284 int r;
285
286 if (!message)
287 return -EINVAL;
288
289 if (len < sizeof(DHCPMessage))
290 return -EINVAL;
291
292 len -= sizeof(DHCPMessage);
293
294 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
295 if (r < 0)
296 return r;
297
298 if (overload & DHCP_OVERLOAD_FILE) {
299 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
300 if (r < 0)
301 return r;
302 }
303
304 if (overload & DHCP_OVERLOAD_SNAME) {
305 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
306 if (r < 0)
307 return r;
308 }
309
310 if (message_type == 0)
311 return -ENOMSG;
312
313 if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
314 *_error_message = TAKE_PTR(error_message);
315
316 return message_type;
317 }