]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp-option.c
network: DHCP server Add support to transmit SIP server
[thirdparty/systemd.git] / src / libsystemd-network / dhcp-option.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
524cf458 2/***
810adae9 3 Copyright © 2013 Intel Corporation. All rights reserved.
524cf458
PF
4***/
5
524cf458 6#include <errno.h>
cf0fbc49 7#include <stdint.h>
524cf458 8#include <stdio.h>
cf0fbc49 9#include <string.h>
524cf458 10
f693e9b3 11#include "alloc-util.h"
524cf458 12#include "dhcp-internal.h"
0a970718
LP
13#include "memory-util.h"
14#include "strv.h"
15#include "utf8.h"
524cf458 16
04b28be1
TG
17static int option_append(uint8_t options[], size_t size, size_t *offset,
18 uint8_t code, size_t optlen, const void *optval) {
20b958bf
TG
19 assert(options);
20 assert(offset);
524cf458 21
22805d92 22 if (code != SD_DHCP_OPTION_END)
2688ef60 23 /* always make sure there is space for an END option */
313cefa1 24 size--;
2688ef60 25
524cf458
PF
26 switch (code) {
27
22805d92
BG
28 case SD_DHCP_OPTION_PAD:
29 case SD_DHCP_OPTION_END:
bc67342e 30 if (*offset + 1 > size)
524cf458
PF
31 return -ENOBUFS;
32
20b958bf
TG
33 options[*offset] = code;
34 *offset += 1;
524cf458
PF
35 break;
36
af1c0de0 37 case SD_DHCP_OPTION_USER_CLASS: {
bc67342e 38 size_t total = 0;
af1c0de0
SS
39 char **s;
40
bc67342e
ZJS
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 }
af1c0de0 49
bc67342e 50 if (*offset + 2 + total > size)
af1c0de0
SS
51 return -ENOBUFS;
52
53 options[*offset] = code;
bc67342e 54 options[*offset + 1] = total;
af1c0de0
SS
55 *offset += 2;
56
57 STRV_FOREACH(s, (char **) optval) {
bc67342e 58 size_t len = strlen(*s);
af1c0de0
SS
59
60 options[*offset] = len;
61
bc67342e
ZJS
62 memcpy(&options[*offset + 1], *s, len);
63 *offset += 1 + len;
af1c0de0
SS
64 }
65
66 break;
67 }
299d578f
SS
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;
524cf458 80 default:
bc67342e 81 if (*offset + 2 + optlen > size)
524cf458
PF
82 return -ENOBUFS;
83
20b958bf
TG
84 options[*offset] = code;
85 options[*offset + 1] = optlen;
04b28be1 86
75f32f04 87 memcpy_safe(&options[*offset + 2], optval, optlen);
bc67342e 88 *offset += 2 + optlen;
524cf458
PF
89
90 break;
91 }
92
93 return 0;
94}
95
04b28be1
TG
96int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
97 uint8_t overload,
98 uint8_t code, size_t optlen, const void *optval) {
bc67342e
ZJS
99 const bool use_file = overload & DHCP_OVERLOAD_FILE;
100 const bool use_sname = overload & DHCP_OVERLOAD_SNAME;
04b28be1
TG
101 int r;
102
103 assert(message);
104 assert(offset);
105
bc67342e
ZJS
106 /* If *offset is in range [0, size), we are writing to ->options,
107 * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
108 * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
109 * and use_sname, we are writing to ->sname.
110 */
04b28be1
TG
111
112 if (*offset < size) {
113 /* still space in the options array */
114 r = option_append(message->options, size, offset, code, optlen, optval);
115 if (r >= 0)
116 return 0;
bc67342e 117 else if (r == -ENOBUFS && (use_file || use_sname)) {
04b28be1
TG
118 /* did not fit, but we have more buffers to try
119 close the options array and move the offset to its end */
22805d92 120 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
04b28be1
TG
121 if (r < 0)
122 return r;
123
124 *offset = size;
125 } else
126 return r;
127 }
128
bc67342e
ZJS
129 if (use_file) {
130 size_t file_offset = *offset - size;
04b28be1
TG
131
132 if (file_offset < sizeof(message->file)) {
133 /* still space in the 'file' array */
134 r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
135 if (r >= 0) {
136 *offset = size + file_offset;
137 return 0;
bc67342e 138 } else if (r == -ENOBUFS && use_sname) {
04b28be1
TG
139 /* did not fit, but we have more buffers to try
140 close the file array and move the offset to its end */
22805d92 141 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
04b28be1
TG
142 if (r < 0)
143 return r;
144
145 *offset = size + sizeof(message->file);
146 } else
147 return r;
148 }
149 }
150
bc67342e
ZJS
151 if (use_sname) {
152 size_t sname_offset = *offset - size - use_file*sizeof(message->file);
04b28be1
TG
153
154 if (sname_offset < sizeof(message->sname)) {
155 /* still space in the 'sname' array */
156 r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
157 if (r >= 0) {
bc67342e 158 *offset = size + use_file*sizeof(message->file) + sname_offset;
04b28be1 159 return 0;
bc67342e 160 } else
04b28be1
TG
161 /* no space, or other error, give up */
162 return r;
04b28be1
TG
163 }
164 }
165
166 return -ENOBUFS;
167}
168
32008a96 169static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
ccf86354 170 uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
89ca10c6 171 void *userdata) {
32008a96 172 uint8_t code, len;
f693e9b3 173 const uint8_t *option;
32008a96 174 size_t offset = 0;
524cf458 175
32008a96 176 while (offset < buflen) {
f693e9b3 177 code = options[offset ++];
32008a96 178
f693e9b3 179 switch (code) {
22805d92 180 case SD_DHCP_OPTION_PAD:
f693e9b3 181 continue;
524cf458 182
22805d92 183 case SD_DHCP_OPTION_END:
524cf458 184 return 0;
f693e9b3 185 }
524cf458 186
f693e9b3
TG
187 if (buflen < offset + 1)
188 return -ENOBUFS;
189
190 len = options[offset ++];
524cf458 191
f693e9b3
TG
192 if (buflen < offset + len)
193 return -EINVAL;
194
195 option = &options[offset];
196
197 switch (code) {
22805d92 198 case SD_DHCP_OPTION_MESSAGE_TYPE:
32008a96 199 if (len != 1)
524cf458
PF
200 return -EINVAL;
201
202 if (message_type)
f693e9b3 203 *message_type = *option;
524cf458
PF
204
205 break;
206
22805d92 207 case SD_DHCP_OPTION_ERROR_MESSAGE:
f693e9b3 208 if (len == 0)
524cf458
PF
209 return -EINVAL;
210
f693e9b3
TG
211 if (error_message) {
212 _cleanup_free_ char *string = NULL;
524cf458 213
f693e9b3
TG
214 /* Accept a trailing NUL byte */
215 if (memchr(option, 0, len - 1))
216 return -EINVAL;
524cf458 217
79cd22d6 218 string = memdup_suffix0((const char *) option, len);
f693e9b3
TG
219 if (!string)
220 return -ENOMEM;
524cf458 221
f693e9b3
TG
222 if (!ascii_is_valid(string))
223 return -EINVAL;
524cf458 224
f9ecfd3b 225 free_and_replace(*error_message, string);
f693e9b3 226 }
524cf458 227
f693e9b3 228 break;
22805d92 229 case SD_DHCP_OPTION_OVERLOAD:
f693e9b3 230 if (len != 1)
524cf458 231 return -EINVAL;
524cf458 232
f693e9b3
TG
233 if (overload)
234 *overload = *option;
524cf458 235
f693e9b3
TG
236 break;
237
238 default:
239 if (cb)
240 cb(code, len, option, userdata);
524cf458
PF
241
242 break;
243 }
f693e9b3
TG
244
245 offset += len;
524cf458
PF
246 }
247
32008a96 248 if (offset < buflen)
524cf458
PF
249 return -EINVAL;
250
251 return 0;
252}
253
ccf86354 254int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
f693e9b3 255 _cleanup_free_ char *error_message = NULL;
524cf458
PF
256 uint8_t overload = 0;
257 uint8_t message_type = 0;
32008a96 258 int r;
524cf458
PF
259
260 if (!message)
261 return -EINVAL;
262
3b7ca119 263 if (len < sizeof(DHCPMessage))
524cf458
PF
264 return -EINVAL;
265
3b7ca119 266 len -= sizeof(DHCPMessage);
524cf458 267
f693e9b3 268 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
32008a96
TG
269 if (r < 0)
270 return r;
524cf458
PF
271
272 if (overload & DHCP_OVERLOAD_FILE) {
f693e9b3 273 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
32008a96
TG
274 if (r < 0)
275 return r;
524cf458
PF
276 }
277
278 if (overload & DHCP_OVERLOAD_SNAME) {
f693e9b3 279 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
32008a96
TG
280 if (r < 0)
281 return r;
524cf458
PF
282 }
283
f693e9b3
TG
284 if (message_type == 0)
285 return -ENOMSG;
286
ae2a15bc
LP
287 if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
288 *_error_message = TAKE_PTR(error_message);
524cf458 289
f693e9b3 290 return message_type;
524cf458 291}