]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp-option.c
macro: introduce TAKE_PTR() macro
[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_and_replace(*error_message, string);
195 }
196
197 break;
198 case SD_DHCP_OPTION_OVERLOAD:
199 if (len != 1)
200 return -EINVAL;
201
202 if (overload)
203 *overload = *option;
204
205 break;
206
207 default:
208 if (cb)
209 cb(code, len, option, userdata);
210
211 break;
212 }
213
214 offset += len;
215 }
216
217 if (offset < buflen)
218 return -EINVAL;
219
220 return 0;
221 }
222
223 int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
224 _cleanup_free_ char *error_message = NULL;
225 uint8_t overload = 0;
226 uint8_t message_type = 0;
227 int r;
228
229 if (!message)
230 return -EINVAL;
231
232 if (len < sizeof(DHCPMessage))
233 return -EINVAL;
234
235 len -= sizeof(DHCPMessage);
236
237 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
238 if (r < 0)
239 return r;
240
241 if (overload & DHCP_OVERLOAD_FILE) {
242 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
243 if (r < 0)
244 return r;
245 }
246
247 if (overload & DHCP_OVERLOAD_SNAME) {
248 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
249 if (r < 0)
250 return r;
251 }
252
253 if (message_type == 0)
254 return -ENOMSG;
255
256 if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
257 *_error_message = TAKE_PTR(error_message);
258
259 return message_type;
260 }