]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp-option.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / libsystemd-network / dhcp-option.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
524cf458
PF
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
524cf458 21#include <errno.h>
cf0fbc49 22#include <stdint.h>
524cf458 23#include <stdio.h>
cf0fbc49 24#include <string.h>
524cf458 25
f693e9b3
TG
26#include "alloc-util.h"
27#include "utf8.h"
28
524cf458
PF
29#include "dhcp-internal.h"
30
04b28be1
TG
31static int option_append(uint8_t options[], size_t size, size_t *offset,
32 uint8_t code, size_t optlen, const void *optval) {
20b958bf
TG
33 assert(options);
34 assert(offset);
524cf458 35
22805d92 36 if (code != SD_DHCP_OPTION_END)
2688ef60 37 /* always make sure there is space for an END option */
313cefa1 38 size--;
2688ef60 39
524cf458
PF
40 switch (code) {
41
22805d92
BG
42 case SD_DHCP_OPTION_PAD:
43 case SD_DHCP_OPTION_END:
04b28be1 44 if (size < *offset + 1)
524cf458
PF
45 return -ENOBUFS;
46
20b958bf
TG
47 options[*offset] = code;
48 *offset += 1;
524cf458
PF
49 break;
50
51 default:
04b28be1 52 if (size < *offset + optlen + 2)
524cf458
PF
53 return -ENOBUFS;
54
20b958bf
TG
55 options[*offset] = code;
56 options[*offset + 1] = optlen;
04b28be1 57
75f32f04 58 memcpy_safe(&options[*offset + 2], optval, optlen);
20b958bf 59 *offset += optlen + 2;
524cf458
PF
60
61 break;
62 }
63
64 return 0;
65}
66
04b28be1
TG
67int 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 */
22805d92 88 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
04b28be1
TG
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 */
22805d92 109 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
04b28be1
TG
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
32008a96 138static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
ccf86354 139 uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
89ca10c6 140 void *userdata) {
32008a96 141 uint8_t code, len;
f693e9b3 142 const uint8_t *option;
32008a96 143 size_t offset = 0;
524cf458 144
32008a96 145 while (offset < buflen) {
f693e9b3 146 code = options[offset ++];
32008a96 147
f693e9b3 148 switch (code) {
22805d92 149 case SD_DHCP_OPTION_PAD:
f693e9b3 150 continue;
524cf458 151
22805d92 152 case SD_DHCP_OPTION_END:
524cf458 153 return 0;
f693e9b3 154 }
524cf458 155
f693e9b3
TG
156 if (buflen < offset + 1)
157 return -ENOBUFS;
158
159 len = options[offset ++];
524cf458 160
f693e9b3
TG
161 if (buflen < offset + len)
162 return -EINVAL;
163
164 option = &options[offset];
165
166 switch (code) {
22805d92 167 case SD_DHCP_OPTION_MESSAGE_TYPE:
32008a96 168 if (len != 1)
524cf458
PF
169 return -EINVAL;
170
171 if (message_type)
f693e9b3 172 *message_type = *option;
524cf458
PF
173
174 break;
175
22805d92 176 case SD_DHCP_OPTION_ERROR_MESSAGE:
f693e9b3 177 if (len == 0)
524cf458
PF
178 return -EINVAL;
179
f693e9b3
TG
180 if (error_message) {
181 _cleanup_free_ char *string = NULL;
524cf458 182
f693e9b3
TG
183 /* Accept a trailing NUL byte */
184 if (memchr(option, 0, len - 1))
185 return -EINVAL;
524cf458 186
f693e9b3
TG
187 string = strndup((const char *) option, len);
188 if (!string)
189 return -ENOMEM;
524cf458 190
f693e9b3
TG
191 if (!ascii_is_valid(string))
192 return -EINVAL;
524cf458 193
f693e9b3
TG
194 free(*error_message);
195 *error_message = string;
196 string = NULL;
197 }
524cf458 198
f693e9b3 199 break;
22805d92 200 case SD_DHCP_OPTION_OVERLOAD:
f693e9b3 201 if (len != 1)
524cf458 202 return -EINVAL;
524cf458 203
f693e9b3
TG
204 if (overload)
205 *overload = *option;
524cf458 206
f693e9b3
TG
207 break;
208
209 default:
210 if (cb)
211 cb(code, len, option, userdata);
524cf458
PF
212
213 break;
214 }
f693e9b3
TG
215
216 offset += len;
524cf458
PF
217 }
218
32008a96 219 if (offset < buflen)
524cf458
PF
220 return -EINVAL;
221
222 return 0;
223}
224
ccf86354 225int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
f693e9b3 226 _cleanup_free_ char *error_message = NULL;
524cf458
PF
227 uint8_t overload = 0;
228 uint8_t message_type = 0;
32008a96 229 int r;
524cf458
PF
230
231 if (!message)
232 return -EINVAL;
233
3b7ca119 234 if (len < sizeof(DHCPMessage))
524cf458
PF
235 return -EINVAL;
236
3b7ca119 237 len -= sizeof(DHCPMessage);
524cf458 238
f693e9b3 239 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
32008a96
TG
240 if (r < 0)
241 return r;
524cf458
PF
242
243 if (overload & DHCP_OVERLOAD_FILE) {
f693e9b3 244 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
32008a96
TG
245 if (r < 0)
246 return r;
524cf458
PF
247 }
248
249 if (overload & DHCP_OVERLOAD_SNAME) {
f693e9b3 250 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
32008a96
TG
251 if (r < 0)
252 return r;
524cf458
PF
253 }
254
f693e9b3
TG
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 }
524cf458 262
f693e9b3 263 return message_type;
524cf458 264}