]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp-option.c
19fc525866b213f8cf4b8c04522c646caa1eb06c
[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 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <string.h>
25
26 #include "alloc-util.h"
27 #include "utf8.h"
28
29 #include "dhcp-internal.h"
30
31 static int option_append(uint8_t options[], size_t size, size_t *offset,
32 uint8_t code, size_t optlen, const void *optval) {
33 assert(options);
34 assert(offset);
35
36 if (code != SD_DHCP_OPTION_END)
37 /* always make sure there is space for an END option */
38 size--;
39
40 switch (code) {
41
42 case SD_DHCP_OPTION_PAD:
43 case SD_DHCP_OPTION_END:
44 if (size < *offset + 1)
45 return -ENOBUFS;
46
47 options[*offset] = code;
48 *offset += 1;
49 break;
50
51 default:
52 if (size < *offset + optlen + 2)
53 return -ENOBUFS;
54
55 options[*offset] = code;
56 options[*offset + 1] = optlen;
57
58 memcpy_safe(&options[*offset + 2], optval, optlen);
59 *offset += optlen + 2;
60
61 break;
62 }
63
64 return 0;
65 }
66
67 int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
68 uint8_t overload,
69 uint8_t code, size_t optlen, const void *optval) {
70 size_t file_offset = 0, sname_offset =0;
71 bool file, sname;
72 int r;
73
74 assert(message);
75 assert(offset);
76
77 file = overload & DHCP_OVERLOAD_FILE;
78 sname = overload & DHCP_OVERLOAD_SNAME;
79
80 if (*offset < size) {
81 /* still space in the options array */
82 r = option_append(message->options, size, offset, code, optlen, optval);
83 if (r >= 0)
84 return 0;
85 else if (r == -ENOBUFS && (file || sname)) {
86 /* did not fit, but we have more buffers to try
87 close the options array and move the offset to its end */
88 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
89 if (r < 0)
90 return r;
91
92 *offset = size;
93 } else
94 return r;
95 }
96
97 if (overload & DHCP_OVERLOAD_FILE) {
98 file_offset = *offset - size;
99
100 if (file_offset < sizeof(message->file)) {
101 /* still space in the 'file' array */
102 r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
103 if (r >= 0) {
104 *offset = size + file_offset;
105 return 0;
106 } else if (r == -ENOBUFS && sname) {
107 /* did not fit, but we have more buffers to try
108 close the file array and move the offset to its end */
109 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
110 if (r < 0)
111 return r;
112
113 *offset = size + sizeof(message->file);
114 } else
115 return r;
116 }
117 }
118
119 if (overload & DHCP_OVERLOAD_SNAME) {
120 sname_offset = *offset - size - (file ? sizeof(message->file) : 0);
121
122 if (sname_offset < sizeof(message->sname)) {
123 /* still space in the 'sname' array */
124 r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
125 if (r >= 0) {
126 *offset = size + (file ? sizeof(message->file) : 0) + sname_offset;
127 return 0;
128 } else {
129 /* no space, or other error, give up */
130 return r;
131 }
132 }
133 }
134
135 return -ENOBUFS;
136 }
137
138 static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
139 uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
140 void *userdata) {
141 uint8_t code, len;
142 const uint8_t *option;
143 size_t offset = 0;
144
145 while (offset < buflen) {
146 code = options[offset ++];
147
148 switch (code) {
149 case SD_DHCP_OPTION_PAD:
150 continue;
151
152 case SD_DHCP_OPTION_END:
153 return 0;
154 }
155
156 if (buflen < offset + 1)
157 return -ENOBUFS;
158
159 len = options[offset ++];
160
161 if (buflen < offset + len)
162 return -EINVAL;
163
164 option = &options[offset];
165
166 switch (code) {
167 case SD_DHCP_OPTION_MESSAGE_TYPE:
168 if (len != 1)
169 return -EINVAL;
170
171 if (message_type)
172 *message_type = *option;
173
174 break;
175
176 case SD_DHCP_OPTION_ERROR_MESSAGE:
177 if (len == 0)
178 return -EINVAL;
179
180 if (error_message) {
181 _cleanup_free_ char *string = NULL;
182
183 /* Accept a trailing NUL byte */
184 if (memchr(option, 0, len - 1))
185 return -EINVAL;
186
187 string = strndup((const char *) option, len);
188 if (!string)
189 return -ENOMEM;
190
191 if (!ascii_is_valid(string))
192 return -EINVAL;
193
194 free(*error_message);
195 *error_message = string;
196 string = NULL;
197 }
198
199 break;
200 case SD_DHCP_OPTION_OVERLOAD:
201 if (len != 1)
202 return -EINVAL;
203
204 if (overload)
205 *overload = *option;
206
207 break;
208
209 default:
210 if (cb)
211 cb(code, len, option, userdata);
212
213 break;
214 }
215
216 offset += len;
217 }
218
219 if (offset < buflen)
220 return -EINVAL;
221
222 return 0;
223 }
224
225 int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
226 _cleanup_free_ char *error_message = NULL;
227 uint8_t overload = 0;
228 uint8_t message_type = 0;
229 int r;
230
231 if (!message)
232 return -EINVAL;
233
234 if (len < sizeof(DHCPMessage))
235 return -EINVAL;
236
237 len -= sizeof(DHCPMessage);
238
239 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
240 if (r < 0)
241 return r;
242
243 if (overload & DHCP_OVERLOAD_FILE) {
244 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
245 if (r < 0)
246 return r;
247 }
248
249 if (overload & DHCP_OVERLOAD_SNAME) {
250 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
251 if (r < 0)
252 return r;
253 }
254
255 if (message_type == 0)
256 return -ENOMSG;
257
258 if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) {
259 *_error_message = error_message;
260 error_message = NULL;
261 }
262
263 return message_type;
264 }