]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp-option.c
Merge pull request #8817 from yuwata/cleanup-nsflags
[thirdparty/systemd.git] / src / libsystemd-network / dhcp-option.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright (C) 2013 Intel Corporation. All rights reserved.
6 ***/
7
8 #include <errno.h>
9 #include <stdint.h>
10 #include <stdio.h>
11 #include <string.h>
12
13 #include "alloc-util.h"
14 #include "utf8.h"
15 #include "strv.h"
16
17 #include "dhcp-internal.h"
18
19 static int option_append(uint8_t options[], size_t size, size_t *offset,
20 uint8_t code, size_t optlen, const void *optval) {
21 assert(options);
22 assert(offset);
23
24 if (code != SD_DHCP_OPTION_END)
25 /* always make sure there is space for an END option */
26 size--;
27
28 switch (code) {
29
30 case SD_DHCP_OPTION_PAD:
31 case SD_DHCP_OPTION_END:
32 if (size < *offset + 1)
33 return -ENOBUFS;
34
35 options[*offset] = code;
36 *offset += 1;
37 break;
38
39 case SD_DHCP_OPTION_USER_CLASS: {
40 size_t len = 0;
41 char **s;
42
43 STRV_FOREACH(s, (char **) optval)
44 len += strlen(*s) + 1;
45
46 if (size < *offset + len + 2)
47 return -ENOBUFS;
48
49 options[*offset] = code;
50 options[*offset + 1] = len;
51 *offset += 2;
52
53 STRV_FOREACH(s, (char **) optval) {
54 len = strlen(*s);
55
56 if (len > 255)
57 return -ENAMETOOLONG;
58
59 options[*offset] = len;
60
61 memcpy_safe(&options[*offset + 1], *s, len);
62 *offset += len + 1;
63 }
64
65 break;
66 }
67 default:
68 if (size < *offset + optlen + 2)
69 return -ENOBUFS;
70
71 options[*offset] = code;
72 options[*offset + 1] = optlen;
73
74 memcpy_safe(&options[*offset + 2], optval, optlen);
75 *offset += optlen + 2;
76
77 break;
78 }
79
80 return 0;
81 }
82
83 int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
84 uint8_t overload,
85 uint8_t code, size_t optlen, const void *optval) {
86 size_t file_offset = 0, sname_offset =0;
87 bool file, sname;
88 int r;
89
90 assert(message);
91 assert(offset);
92
93 file = overload & DHCP_OVERLOAD_FILE;
94 sname = overload & DHCP_OVERLOAD_SNAME;
95
96 if (*offset < size) {
97 /* still space in the options array */
98 r = option_append(message->options, size, offset, code, optlen, optval);
99 if (r >= 0)
100 return 0;
101 else if (r == -ENOBUFS && (file || sname)) {
102 /* did not fit, but we have more buffers to try
103 close the options array and move the offset to its end */
104 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
105 if (r < 0)
106 return r;
107
108 *offset = size;
109 } else
110 return r;
111 }
112
113 if (overload & DHCP_OVERLOAD_FILE) {
114 file_offset = *offset - size;
115
116 if (file_offset < sizeof(message->file)) {
117 /* still space in the 'file' array */
118 r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
119 if (r >= 0) {
120 *offset = size + file_offset;
121 return 0;
122 } else if (r == -ENOBUFS && sname) {
123 /* did not fit, but we have more buffers to try
124 close the file array and move the offset to its end */
125 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
126 if (r < 0)
127 return r;
128
129 *offset = size + sizeof(message->file);
130 } else
131 return r;
132 }
133 }
134
135 if (overload & DHCP_OVERLOAD_SNAME) {
136 sname_offset = *offset - size - (file ? sizeof(message->file) : 0);
137
138 if (sname_offset < sizeof(message->sname)) {
139 /* still space in the 'sname' array */
140 r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
141 if (r >= 0) {
142 *offset = size + (file ? sizeof(message->file) : 0) + sname_offset;
143 return 0;
144 } else {
145 /* no space, or other error, give up */
146 return r;
147 }
148 }
149 }
150
151 return -ENOBUFS;
152 }
153
154 static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
155 uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
156 void *userdata) {
157 uint8_t code, len;
158 const uint8_t *option;
159 size_t offset = 0;
160
161 while (offset < buflen) {
162 code = options[offset ++];
163
164 switch (code) {
165 case SD_DHCP_OPTION_PAD:
166 continue;
167
168 case SD_DHCP_OPTION_END:
169 return 0;
170 }
171
172 if (buflen < offset + 1)
173 return -ENOBUFS;
174
175 len = options[offset ++];
176
177 if (buflen < offset + len)
178 return -EINVAL;
179
180 option = &options[offset];
181
182 switch (code) {
183 case SD_DHCP_OPTION_MESSAGE_TYPE:
184 if (len != 1)
185 return -EINVAL;
186
187 if (message_type)
188 *message_type = *option;
189
190 break;
191
192 case SD_DHCP_OPTION_ERROR_MESSAGE:
193 if (len == 0)
194 return -EINVAL;
195
196 if (error_message) {
197 _cleanup_free_ char *string = NULL;
198
199 /* Accept a trailing NUL byte */
200 if (memchr(option, 0, len - 1))
201 return -EINVAL;
202
203 string = strndup((const char *) option, len);
204 if (!string)
205 return -ENOMEM;
206
207 if (!ascii_is_valid(string))
208 return -EINVAL;
209
210 free_and_replace(*error_message, string);
211 }
212
213 break;
214 case SD_DHCP_OPTION_OVERLOAD:
215 if (len != 1)
216 return -EINVAL;
217
218 if (overload)
219 *overload = *option;
220
221 break;
222
223 default:
224 if (cb)
225 cb(code, len, option, userdata);
226
227 break;
228 }
229
230 offset += len;
231 }
232
233 if (offset < buflen)
234 return -EINVAL;
235
236 return 0;
237 }
238
239 int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
240 _cleanup_free_ char *error_message = NULL;
241 uint8_t overload = 0;
242 uint8_t message_type = 0;
243 int r;
244
245 if (!message)
246 return -EINVAL;
247
248 if (len < sizeof(DHCPMessage))
249 return -EINVAL;
250
251 len -= sizeof(DHCPMessage);
252
253 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
254 if (r < 0)
255 return r;
256
257 if (overload & DHCP_OVERLOAD_FILE) {
258 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
259 if (r < 0)
260 return r;
261 }
262
263 if (overload & DHCP_OVERLOAD_SNAME) {
264 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
265 if (r < 0)
266 return r;
267 }
268
269 if (message_type == 0)
270 return -ENOMSG;
271
272 if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
273 *_error_message = TAKE_PTR(error_message);
274
275 return message_type;
276 }