]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
Add memcpy_safe
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
CommitLineData
f12ed3bf
PF
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
b553817c 6 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
f12ed3bf
PF
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
f12ed3bf 22#include <errno.h>
cf0fbc49 23#include <netinet/in.h>
f12ed3bf
PF
24#include <string.h>
25
2c1ab8ca
BG
26#include "sd-dhcp6-client.h"
27
b5efdb8a 28#include "alloc-util.h"
f12ed3bf
PF
29#include "dhcp6-internal.h"
30#include "dhcp6-protocol.h"
f96ccab7 31#include "dns-domain.h"
b5efdb8a
LP
32#include "sparse-endian.h"
33#include "strv.h"
34#include "unaligned.h"
35#include "util.h"
f12ed3bf 36
f12ed3bf
PF
37#define DHCP6_OPTION_IA_NA_LEN 12
38#define DHCP6_OPTION_IA_TA_LEN 4
f12ed3bf 39
4903a73c
TG
40typedef struct DHCP6Option {
41 be16_t code;
42 be16_t len;
43 uint8_t data[];
c962cb68 44} _packed_ DHCP6Option;
4903a73c 45
f12ed3bf
PF
46static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
47 size_t optlen) {
c962cb68 48 DHCP6Option *option = (DHCP6Option*) *buf;
4903a73c 49
f12ed3bf
PF
50 assert_return(buf, -EINVAL);
51 assert_return(*buf, -EINVAL);
52 assert_return(buflen, -EINVAL);
53
4903a73c 54 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
f12ed3bf
PF
55 return -ENOBUFS;
56
c962cb68
TG
57 option->code = htobe16(optcode);
58 option->len = htobe16(optlen);
f12ed3bf 59
4903a73c
TG
60 *buf += sizeof(DHCP6Option);
61 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
62
63 return 0;
64}
65
66int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
67 size_t optlen, const void *optval) {
68 int r;
69
ed6ee219 70 assert_return(optval || optlen == 0, -EINVAL);
f12ed3bf
PF
71
72 r = option_append_hdr(buf, buflen, code, optlen);
73 if (r < 0)
74 return r;
75
75f32f04 76 memcpy_safe(*buf, optval, optlen);
f12ed3bf
PF
77
78 *buf += optlen;
79 *buflen -= optlen;
80
81 return 0;
82}
83
84int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
85 uint16_t len;
86 uint8_t *ia_hdr;
87 size_t ia_buflen, ia_addrlen = 0;
88 DHCP6Address *addr;
89 int r;
90
91 assert_return(buf && *buf && buflen && ia, -EINVAL);
92
93 switch (ia->type) {
2c1ab8ca 94 case SD_DHCP6_OPTION_IA_NA:
f12ed3bf
PF
95 len = DHCP6_OPTION_IA_NA_LEN;
96 break;
97
2c1ab8ca 98 case SD_DHCP6_OPTION_IA_TA:
f12ed3bf
PF
99 len = DHCP6_OPTION_IA_TA_LEN;
100 break;
101
102 default:
103 return -EINVAL;
104 }
105
106 if (*buflen < len)
107 return -ENOBUFS;
108
109 ia_hdr = *buf;
110 ia_buflen = *buflen;
111
4903a73c
TG
112 *buf += sizeof(DHCP6Option);
113 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
114
115 memcpy(*buf, &ia->id, len);
116
117 *buf += len;
118 *buflen -= len;
119
120 LIST_FOREACH(addresses, addr, ia->addresses) {
2c1ab8ca 121 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
ee3a5027 122 sizeof(addr->iaaddr));
f12ed3bf
PF
123 if (r < 0)
124 return r;
125
ee3a5027 126 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
f12ed3bf 127
ee3a5027
PF
128 *buf += sizeof(addr->iaaddr);
129 *buflen -= sizeof(addr->iaaddr);
f12ed3bf 130
4903a73c 131 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
f12ed3bf
PF
132 }
133
134 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
135 if (r < 0)
136 return r;
137
138 return 0;
139}
140
c6affce8 141
c962cb68
TG
142static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
143 DHCP6Option *option = (DHCP6Option*) *buf;
c6affce8
PF
144 uint16_t len;
145
146 assert_return(buf, -EINVAL);
4903a73c 147 assert_return(optcode, -EINVAL);
c6affce8
PF
148 assert_return(optlen, -EINVAL);
149
4903a73c 150 if (*buflen < sizeof(DHCP6Option))
c6affce8
PF
151 return -ENOMSG;
152
c962cb68 153 len = be16toh(option->len);
c6affce8
PF
154
155 if (len > *buflen)
156 return -ENOMSG;
157
c962cb68 158 *optcode = be16toh(option->code);
c6affce8
PF
159 *optlen = len;
160
161 *buf += 4;
162 *buflen -= 4;
163
164 return 0;
165}
166
f12ed3bf
PF
167int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
168 size_t *optlen, uint8_t **optvalue) {
c6affce8 169 int r;
f12ed3bf 170
c6affce8 171 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
f12ed3bf 172
c6affce8
PF
173 r = option_parse_hdr(buf, buflen, optcode, optlen);
174 if (r < 0)
175 return r;
f12ed3bf 176
c6affce8 177 if (*optlen > *buflen)
f12ed3bf
PF
178 return -ENOBUFS;
179
c6affce8
PF
180 *optvalue = *buf;
181 *buflen -= *optlen;
182 *buf += *optlen;
f12ed3bf
PF
183
184 return 0;
185}
c6affce8
PF
186
187int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
188 DHCP6IA *ia) {
189 int r;
190 uint16_t opt, status;
191 size_t optlen;
192 size_t iaaddr_offset;
193 DHCP6Address *addr;
194 uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
195
196 assert_return(ia, -EINVAL);
197 assert_return(!ia->addresses, -EINVAL);
198
199 switch (iatype) {
2c1ab8ca 200 case SD_DHCP6_OPTION_IA_NA:
c6affce8 201
4903a73c 202 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
ee3a5027 203 sizeof(addr->iaaddr)) {
c6affce8
PF
204 r = -ENOBUFS;
205 goto error;
206 }
207
208 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
209 memcpy(&ia->id, *buf, iaaddr_offset);
210
211 lt_t1 = be32toh(ia->lifetime_t1);
212 lt_t2 = be32toh(ia->lifetime_t2);
213
214 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
215 log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
216 lt_t1, lt_t2);
217 r = -EINVAL;
218 goto error;
219 }
220
221 break;
222
2c1ab8ca 223 case SD_DHCP6_OPTION_IA_TA:
4903a73c 224 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
ee3a5027 225 sizeof(addr->iaaddr)) {
c6affce8
PF
226 r = -ENOBUFS;
227 goto error;
228 }
229
230 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
231 memcpy(&ia->id, *buf, iaaddr_offset);
232
233 ia->lifetime_t1 = 0;
234 ia->lifetime_t2 = 0;
235
236 break;
237
238 default:
239 r = -ENOMSG;
240 goto error;
241 }
242
243 ia->type = iatype;
244
245 *buflen -= iaaddr_offset;
246 *buf += iaaddr_offset;
247
248 while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
249
250 switch (opt) {
2c1ab8ca 251 case SD_DHCP6_OPTION_IAADDR:
c6affce8
PF
252
253 addr = new0(DHCP6Address, 1);
254 if (!addr) {
255 r = -ENOMEM;
256 goto error;
257 }
258
259 LIST_INIT(addresses, addr);
260
ee3a5027 261 memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
c6affce8 262
ee3a5027
PF
263 lt_valid = be32toh(addr->iaaddr.lifetime_valid);
264 lt_pref = be32toh(addr->iaaddr.lifetime_valid);
c6affce8
PF
265
266 if (!lt_valid || lt_pref > lt_valid) {
267 log_dhcp6_client(client, "IA preferred %ds > valid %ds",
268 lt_pref, lt_valid);
269 free(addr);
270 } else {
271 LIST_PREPEND(addresses, ia->addresses, addr);
272 if (lt_valid < lt_min)
273 lt_min = lt_valid;
274 }
275
276 break;
277
2c1ab8ca 278 case SD_DHCP6_OPTION_STATUS_CODE:
c6affce8
PF
279 if (optlen < sizeof(status))
280 break;
281
282 status = (*buf)[0] << 8 | (*buf)[1];
283 if (status) {
284 log_dhcp6_client(client, "IA status %d",
285 status);
286 r = -EINVAL;
287 goto error;
288 }
289
290 break;
291
292 default:
293 log_dhcp6_client(client, "Unknown IA option %d", opt);
294 break;
295 }
296
297 *buflen -= optlen;
298 *buf += optlen;
299 }
300
301 if (r == -ENOMSG)
302 r = 0;
303
304 if (!ia->lifetime_t1 && !ia->lifetime_t2) {
305 lt_t1 = lt_min / 2;
306 lt_t2 = lt_min / 10 * 8;
307 ia->lifetime_t1 = htobe32(lt_t1);
308 ia->lifetime_t2 = htobe32(lt_t2);
309
310 log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
311 lt_t1, lt_t2);
312 }
313
314 if (*buflen)
315 r = -ENOMSG;
316
317error:
318 *buf += *buflen;
319 *buflen = 0;
320
321 return r;
322}
b553817c
PF
323
324int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
325 struct in6_addr **addrs, size_t count,
326 size_t *allocated) {
327
328 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
329 return -EINVAL;
330
331 if (!GREEDY_REALLOC(*addrs, *allocated,
332 count * sizeof(struct in6_addr) + optlen))
333 return -ENOMEM;
334
335 memcpy(*addrs + count, optval, optlen);
336
337 count += optlen / sizeof(struct in6_addr);
338
339 return count;
340}
f96ccab7 341
52efd56a 342int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
f96ccab7
PF
343 size_t pos = 0, idx = 0;
344 _cleanup_free_ char **names = NULL;
345 int r;
346
347 assert_return(optlen > 1, -ENODATA);
93f660da 348 assert_return(optval[optlen - 1] == '\0', -EINVAL);
f96ccab7
PF
349
350 while (pos < optlen) {
351 _cleanup_free_ char *ret = NULL;
352 size_t n = 0, allocated = 0;
353 bool first = true;
354
355 for (;;) {
356 uint8_t c;
357
358 c = optval[pos++];
359
360 if (c == 0)
361 /* End of name */
362 break;
363 else if (c <= 63) {
f96ccab7
PF
364 const char *label;
365
366 /* Literal label */
367 label = (const char *)&optval[pos];
368 pos += c;
369 if (pos > optlen)
370 return -EMSGSIZE;
371
422baca0 372 if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
f96ccab7
PF
373 r = -ENOMEM;
374 goto fail;
375 }
376
422baca0 377 if (first)
f96ccab7 378 first = false;
422baca0
LP
379 else
380 ret[n++] = '.';
381
382 r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
383 if (r < 0)
384 goto fail;
f96ccab7 385
f96ccab7
PF
386 n += r;
387 continue;
388 } else {
389 r = -EBADMSG;
390 goto fail;
391 }
392 }
393
394 if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
395 r = -ENOMEM;
396 goto fail;
397 }
398
399 ret[n] = 0;
400
401 r = strv_extend(&names, ret);
402 if (r < 0)
403 goto fail;
404
f96ccab7
PF
405 idx++;
406 }
407
408 *str_arr = names;
409 names = NULL;
410
411 return idx;
412
413fail:
414 return r;
415}