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