xref: /freebsd/contrib/wireguard-tools/config.c (revision 137de4b34d45192985e21f6d6163533da547fbac)
1 // SPDX-License-Identifier: GPL-2.0 OR MIT
2 /*
3  * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
4  */
5 
6 #include <arpa/inet.h>
7 #include <limits.h>
8 #include <netdb.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <sys/socket.h>
14 #include <sys/stat.h>
15 #include <errno.h>
16 
17 #include "config.h"
18 #include "containers.h"
19 #include "ipc.h"
20 #include "encoding.h"
21 #include "ctype.h"
22 
23 #define COMMENT_CHAR '#'
24 
get_value(const char * line,const char * key)25 static const char *get_value(const char *line, const char *key)
26 {
27 	size_t linelen = strlen(line);
28 	size_t keylen = strlen(key);
29 
30 	if (keylen >= linelen)
31 		return NULL;
32 
33 	if (strncasecmp(line, key, keylen))
34 		return NULL;
35 
36 	return line + keylen;
37 }
38 
parse_port(uint16_t * port,uint32_t * flags,const char * value)39 static inline bool parse_port(uint16_t *port, uint32_t *flags, const char *value)
40 {
41 	int ret;
42 	struct addrinfo *resolved;
43 	struct addrinfo hints = {
44 		.ai_family = AF_UNSPEC,
45 		.ai_socktype = SOCK_DGRAM,
46 		.ai_protocol = IPPROTO_UDP,
47 		.ai_flags = AI_PASSIVE
48 	};
49 
50 	if (!strlen(value)) {
51 		fprintf(stderr, "Unable to parse empty port\n");
52 		return false;
53 	}
54 
55 	ret = getaddrinfo(NULL, value, &hints, &resolved);
56 	if (ret) {
57 		fprintf(stderr, "%s: `%s'\n", ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret), value);
58 		return false;
59 	}
60 
61 	ret = -1;
62 	if (resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) {
63 		*port = ntohs(((struct sockaddr_in *)resolved->ai_addr)->sin_port);
64 		ret = 0;
65 	} else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)) {
66 		*port = ntohs(((struct sockaddr_in6 *)resolved->ai_addr)->sin6_port);
67 		ret = 0;
68 	} else
69 		fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s'\n", value);
70 
71 	freeaddrinfo(resolved);
72 	if (!ret)
73 		*flags |= WGDEVICE_HAS_LISTEN_PORT;
74 	return ret == 0;
75 }
76 
parse_fwmark(uint32_t * fwmark,uint32_t * flags,const char * value)77 static inline bool parse_fwmark(uint32_t *fwmark, uint32_t *flags, const char *value)
78 {
79 	unsigned long ret;
80 	char *end;
81 	int base = 10;
82 
83 	if (!strcasecmp(value, "off")) {
84 		*fwmark = 0;
85 		*flags |= WGDEVICE_HAS_FWMARK;
86 		return true;
87 	}
88 
89 	if (!char_is_digit(value[0]))
90 		goto err;
91 
92 	if (strlen(value) > 2 && value[0] == '0' && value[1] == 'x')
93 		base = 16;
94 
95 	ret = strtoul(value, &end, base);
96 	if (*end || ret > UINT32_MAX)
97 		goto err;
98 
99 	*fwmark = ret;
100 	*flags |= WGDEVICE_HAS_FWMARK;
101 	return true;
102 err:
103 	fprintf(stderr, "Fwmark is neither 0/off nor 0-0xffffffff: `%s'\n", value);
104 	return false;
105 }
106 
parse_key(uint8_t key[static WG_KEY_LEN],const char * value)107 static inline bool parse_key(uint8_t key[static WG_KEY_LEN], const char *value)
108 {
109 	if (!key_from_base64(key, value)) {
110 		fprintf(stderr, "Key is not the correct length or format: `%s'\n", value);
111 		memset(key, 0, WG_KEY_LEN);
112 		return false;
113 	}
114 	return true;
115 }
116 
parse_keyfile(uint8_t key[static WG_KEY_LEN],const char * path)117 static bool parse_keyfile(uint8_t key[static WG_KEY_LEN], const char *path)
118 {
119 	FILE *f;
120 	int c;
121 	char dst[WG_KEY_LEN_BASE64];
122 	bool ret = false;
123 
124 	f = fopen(path, "r");
125 	if (!f) {
126 		perror("fopen");
127 		return false;
128 	}
129 
130 	if (fread(dst, WG_KEY_LEN_BASE64 - 1, 1, f) != 1) {
131 		/* If we're at the end and we didn't read anything, we're /dev/null or an empty file. */
132 		if (!ferror(f) && feof(f) && !ftell(f)) {
133 			memset(key, 0, WG_KEY_LEN);
134 			ret = true;
135 			goto out;
136 		}
137 
138 		fprintf(stderr, "Invalid length key in key file\n");
139 		goto out;
140 	}
141 	dst[WG_KEY_LEN_BASE64 - 1] = '\0';
142 
143 	while ((c = getc(f)) != EOF) {
144 		if (!char_is_space(c)) {
145 			fprintf(stderr, "Found trailing character in key file: `%c'\n", c);
146 			goto out;
147 		}
148 	}
149 	if (ferror(f) && errno) {
150 		perror("getc");
151 		goto out;
152 	}
153 	ret = parse_key(key, dst);
154 
155 out:
156 	fclose(f);
157 	return ret;
158 }
159 
parse_ip(struct wgallowedip * allowedip,const char * value)160 static inline bool parse_ip(struct wgallowedip *allowedip, const char *value)
161 {
162 	allowedip->family = AF_UNSPEC;
163 	if (strchr(value, ':')) {
164 		if (inet_pton(AF_INET6, value, &allowedip->ip6) == 1)
165 			allowedip->family = AF_INET6;
166 	} else {
167 		if (inet_pton(AF_INET, value, &allowedip->ip4) == 1)
168 			allowedip->family = AF_INET;
169 	}
170 	if (allowedip->family == AF_UNSPEC) {
171 		fprintf(stderr, "Unable to parse IP address: `%s'\n", value);
172 		return false;
173 	}
174 	return true;
175 }
176 
parse_dns_retries(void)177 static inline int parse_dns_retries(void)
178 {
179 	unsigned long ret;
180 	char *retries = getenv("WG_ENDPOINT_RESOLUTION_RETRIES"), *end;
181 
182 	if (!retries)
183 		return 15;
184 	if (!strcmp(retries, "infinity"))
185 		return -1;
186 
187 	ret = strtoul(retries, &end, 10);
188 	if (*end || ret > INT_MAX) {
189 		fprintf(stderr, "Unable to parse WG_ENDPOINT_RESOLUTION_RETRIES: `%s'\n", retries);
190 		exit(1);
191 	}
192 	return (int)ret;
193 }
194 
parse_endpoint(struct sockaddr * endpoint,const char * value)195 static inline bool parse_endpoint(struct sockaddr *endpoint, const char *value)
196 {
197 	char *mutable = strdup(value);
198 	char *begin, *end;
199 	int ret, retries = parse_dns_retries();
200 	struct addrinfo *resolved;
201 	struct addrinfo hints = {
202 		.ai_family = AF_UNSPEC,
203 		.ai_socktype = SOCK_DGRAM,
204 		.ai_protocol = IPPROTO_UDP
205 	};
206 	if (!mutable) {
207 		perror("strdup");
208 		return false;
209 	}
210 	if (!strlen(value)) {
211 		free(mutable);
212 		fprintf(stderr, "Unable to parse empty endpoint\n");
213 		return false;
214 	}
215 	if (mutable[0] == '[') {
216 		begin = &mutable[1];
217 		end = strchr(mutable, ']');
218 		if (!end) {
219 			free(mutable);
220 			fprintf(stderr, "Unable to find matching brace of endpoint: `%s'\n", value);
221 			return false;
222 		}
223 		*end++ = '\0';
224 		if (*end++ != ':' || !*end) {
225 			free(mutable);
226 			fprintf(stderr, "Unable to find port of endpoint: `%s'\n", value);
227 			return false;
228 		}
229 	} else {
230 		begin = mutable;
231 		end = strrchr(mutable, ':');
232 		if (!end || !*(end + 1)) {
233 			free(mutable);
234 			fprintf(stderr, "Unable to find port of endpoint: `%s'\n", value);
235 			return false;
236 		}
237 		*end++ = '\0';
238 	}
239 
240 	#define min(a, b) ((a) < (b) ? (a) : (b))
241 	for (unsigned int timeout = 1000000;; timeout = min(20000000, timeout * 6 / 5)) {
242 		ret = getaddrinfo(begin, end, &hints, &resolved);
243 		if (!ret)
244 			break;
245 		/* The set of return codes that are "permanent failures". All other possibilities are potentially transient.
246 		 *
247 		 * This is according to https://sourceware.org/glibc/wiki/NameResolver which states:
248 		 *	"From the perspective of the application that calls getaddrinfo() it perhaps
249 		 *	 doesn't matter that much since EAI_FAIL, EAI_NONAME and EAI_NODATA are all
250 		 *	 permanent failure codes and the causes are all permanent failures in the
251 		 *	 sense that there is no point in retrying later."
252 		 *
253 		 * So this is what we do, except FreeBSD removed EAI_NODATA some time ago, so that's conditional.
254 		 */
255 		if (ret == EAI_NONAME || ret == EAI_FAIL ||
256 			#ifdef EAI_NODATA
257 				ret == EAI_NODATA ||
258 			#endif
259 				(retries >= 0 && !retries--)) {
260 			free(mutable);
261 			fprintf(stderr, "%s: `%s'\n", ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret), value);
262 			return false;
263 		}
264 		fprintf(stderr, "%s: `%s'. Trying again in %.2f seconds...\n", ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret), value, timeout / 1000000.0);
265 		usleep(timeout);
266 	}
267 
268 	if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) ||
269 	    (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)))
270 		memcpy(endpoint, resolved->ai_addr, resolved->ai_addrlen);
271 	else {
272 		freeaddrinfo(resolved);
273 		free(mutable);
274 		fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s'\n", value);
275 		return false;
276 	}
277 	freeaddrinfo(resolved);
278 	free(mutable);
279 	return true;
280 }
281 
parse_persistent_keepalive(uint16_t * interval,uint32_t * flags,const char * value)282 static inline bool parse_persistent_keepalive(uint16_t *interval, uint32_t *flags, const char *value)
283 {
284 	unsigned long ret;
285 	char *end;
286 
287 	if (!strcasecmp(value, "off")) {
288 		*interval = 0;
289 		*flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
290 		return true;
291 	}
292 
293 	if (!char_is_digit(value[0]))
294 		goto err;
295 
296 	ret = strtoul(value, &end, 10);
297 	if (*end || ret > 65535)
298 		goto err;
299 
300 	*interval = (uint16_t)ret;
301 	*flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
302 	return true;
303 err:
304 	fprintf(stderr, "Persistent keepalive interval is neither 0/off nor 1-65535: `%s'\n", value);
305 	return false;
306 }
307 
validate_netmask(struct wgallowedip * allowedip)308 static bool validate_netmask(struct wgallowedip *allowedip)
309 {
310 	uint32_t *ip;
311 	int last;
312 
313 	switch (allowedip->family) {
314 		case AF_INET:
315 			last = 0;
316 			ip = (uint32_t *)&allowedip->ip4;
317 			break;
318 		case AF_INET6:
319 			last = 3;
320 			ip = (uint32_t *)&allowedip->ip6;
321 			break;
322 		default:
323 			return true; /* We don't know how to validate it, so say 'okay'. */
324 	}
325 
326 	for (int i = last; i >= 0; --i) {
327 		uint32_t mask = ~0;
328 
329 		if (allowedip->cidr >= 32 * (i + 1))
330 			break;
331 		if (allowedip->cidr > 32 * i)
332 			mask >>= (allowedip->cidr - 32 * i);
333 		if (ntohl(ip[i]) & mask)
334 			return false;
335 	}
336 
337 	return true;
338 }
339 
parse_ip_prefix(struct wgpeer * peer,uint32_t * flags,char ** mask)340 static inline void parse_ip_prefix(struct wgpeer *peer, uint32_t *flags, char **mask)
341 {
342 	/* If the IP is prefixed with either '+' or '-' consider this an
343 	 * incremental change. Disable WGPEER_REPLACE_ALLOWEDIPS. */
344 	switch ((*mask)[0]) {
345 	case '-':
346 		*flags |= WGALLOWEDIP_REMOVE_ME;
347 		/* fall through */
348 	case '+':
349 		peer->flags &= ~WGPEER_REPLACE_ALLOWEDIPS;
350 		++(*mask);
351 	}
352 }
353 
parse_allowedips(struct wgpeer * peer,struct wgallowedip ** last_allowedip,const char * value)354 static inline bool parse_allowedips(struct wgpeer *peer, struct wgallowedip **last_allowedip, const char *value)
355 {
356 	struct wgallowedip *allowedip = *last_allowedip, *new_allowedip;
357 	char *mask, *mutable = strdup(value), *sep, *saved_entry;
358 
359 	if (!mutable) {
360 		perror("strdup");
361 		return false;
362 	}
363 	peer->flags |= WGPEER_REPLACE_ALLOWEDIPS;
364 	if (!strlen(value)) {
365 		free(mutable);
366 		return true;
367 	}
368 	sep = mutable;
369 	while ((mask = strsep(&sep, ","))) {
370 		uint32_t flags = 0;
371 		unsigned long cidr;
372 		char *end, *ip;
373 
374 		parse_ip_prefix(peer, &flags, &mask);
375 
376 		saved_entry = strdup(mask);
377 		if (!saved_entry) {
378 			perror("strdup");
379 			free(mutable);
380 			return false;
381 		}
382 		ip = strsep(&mask, "/");
383 
384 		new_allowedip = calloc(1, sizeof(*new_allowedip));
385 		if (!new_allowedip) {
386 			perror("calloc");
387 			free(saved_entry);
388 			free(mutable);
389 			return false;
390 		}
391 
392 		if (!parse_ip(new_allowedip, ip)) {
393 			free(new_allowedip);
394 			free(saved_entry);
395 			free(mutable);
396 			return false;
397 		}
398 
399 		if (mask) {
400 			if (!char_is_digit(mask[0]))
401 				goto err;
402 			cidr = strtoul(mask, &end, 10);
403 			if (*end || (cidr > 32 && new_allowedip->family == AF_INET) || (cidr > 128 && new_allowedip->family == AF_INET6))
404 				goto err;
405 		} else if (new_allowedip->family == AF_INET)
406 			cidr = 32;
407 		else if (new_allowedip->family == AF_INET6)
408 			cidr = 128;
409 		else
410 			goto err;
411 		new_allowedip->cidr = cidr;
412 		new_allowedip->flags = flags;
413 
414 		if (!validate_netmask(new_allowedip))
415 			fprintf(stderr, "Warning: AllowedIP has nonzero host part: %s/%s\n", ip, mask);
416 
417 		if (allowedip)
418 			allowedip->next_allowedip = new_allowedip;
419 		else
420 			peer->first_allowedip = new_allowedip;
421 		allowedip = new_allowedip;
422 		free(saved_entry);
423 	}
424 	free(mutable);
425 	*last_allowedip = allowedip;
426 	return true;
427 
428 err:
429 	free(new_allowedip);
430 	free(mutable);
431 	fprintf(stderr, "AllowedIP is not in the correct format: `%s'\n", saved_entry);
432 	free(saved_entry);
433 	return false;
434 }
435 
process_line(struct config_ctx * ctx,const char * line)436 static bool process_line(struct config_ctx *ctx, const char *line)
437 {
438 	const char *value;
439 	bool ret = true;
440 
441 	if (!strcasecmp(line, "[Interface]")) {
442 		ctx->is_peer_section = false;
443 		ctx->is_device_section = true;
444 		return true;
445 	}
446 	if (!strcasecmp(line, "[Peer]")) {
447 		struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
448 
449 		if (!new_peer) {
450 			perror("calloc");
451 			return false;
452 		}
453 		ctx->last_allowedip = NULL;
454 		if (ctx->last_peer)
455 			ctx->last_peer->next_peer = new_peer;
456 		else
457 			ctx->device->first_peer = new_peer;
458 		ctx->last_peer = new_peer;
459 		ctx->is_peer_section = true;
460 		ctx->is_device_section = false;
461 		ctx->last_peer->flags |= WGPEER_REPLACE_ALLOWEDIPS;
462 		return true;
463 	}
464 
465 #define key_match(key) (value = get_value(line, key "="))
466 
467 	if (ctx->is_device_section) {
468 		if (key_match("ListenPort"))
469 			ret = parse_port(&ctx->device->listen_port, &ctx->device->flags, value);
470 		else if (key_match("FwMark"))
471 			ret = parse_fwmark(&ctx->device->fwmark, &ctx->device->flags, value);
472 		else if (key_match("PrivateKey")) {
473 			ret = parse_key(ctx->device->private_key, value);
474 			if (ret)
475 				ctx->device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
476 		} else
477 			goto error;
478 	} else if (ctx->is_peer_section) {
479 		if (key_match("Endpoint"))
480 			ret = parse_endpoint(&ctx->last_peer->endpoint.addr, value);
481 		else if (key_match("PublicKey")) {
482 			ret = parse_key(ctx->last_peer->public_key, value);
483 			if (ret)
484 				ctx->last_peer->flags |= WGPEER_HAS_PUBLIC_KEY;
485 		} else if (key_match("AllowedIPs"))
486 			ret = parse_allowedips(ctx->last_peer, &ctx->last_allowedip, value);
487 		else if (key_match("PersistentKeepalive"))
488 			ret = parse_persistent_keepalive(&ctx->last_peer->persistent_keepalive_interval, &ctx->last_peer->flags, value);
489 		else if (key_match("PresharedKey")) {
490 			ret = parse_key(ctx->last_peer->preshared_key, value);
491 			if (ret)
492 				ctx->last_peer->flags |= WGPEER_HAS_PRESHARED_KEY;
493 		} else
494 			goto error;
495 	} else
496 		goto error;
497 	return ret;
498 
499 #undef key_match
500 
501 error:
502 	fprintf(stderr, "Line unrecognized: `%s'\n", line);
503 	return false;
504 }
505 
config_read_line(struct config_ctx * ctx,const char * input)506 bool config_read_line(struct config_ctx *ctx, const char *input)
507 {
508 	size_t len, cleaned_len = 0;
509 	char *line, *comment;
510 	bool ret = true;
511 
512 	/* This is what strchrnul is for, but that isn't portable. */
513 	comment = strchr(input, COMMENT_CHAR);
514 	if (comment)
515 		len = comment - input;
516 	else
517 		len = strlen(input);
518 
519 	line = calloc(len + 1, sizeof(char));
520 	if (!line) {
521 		perror("calloc");
522 		ret = false;
523 		goto out;
524 	}
525 
526 	for (size_t i = 0; i < len; ++i) {
527 		if (!char_is_space(input[i]))
528 			line[cleaned_len++] = input[i];
529 	}
530 	if (!cleaned_len)
531 		goto out;
532 	ret = process_line(ctx, line);
533 out:
534 	free(line);
535 	if (!ret)
536 		free_wgdevice(ctx->device);
537 	return ret;
538 }
539 
config_read_init(struct config_ctx * ctx,bool append)540 bool config_read_init(struct config_ctx *ctx, bool append)
541 {
542 	memset(ctx, 0, sizeof(*ctx));
543 	ctx->device = calloc(1, sizeof(*ctx->device));
544 	if (!ctx->device) {
545 		perror("calloc");
546 		return false;
547 	}
548 	if (!append)
549 		ctx->device->flags |= WGDEVICE_REPLACE_PEERS | WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_FWMARK | WGDEVICE_HAS_LISTEN_PORT;
550 	return true;
551 }
552 
config_read_finish(struct config_ctx * ctx)553 struct wgdevice *config_read_finish(struct config_ctx *ctx)
554 {
555 	struct wgpeer *peer;
556 
557 	for_each_wgpeer(ctx->device, peer) {
558 		if (!(peer->flags & WGPEER_HAS_PUBLIC_KEY)) {
559 			fprintf(stderr, "A peer is missing a public key\n");
560 			goto err;
561 		}
562 	}
563 	return ctx->device;
564 err:
565 	free_wgdevice(ctx->device);
566 	return NULL;
567 }
568 
strip_spaces(const char * in)569 static char *strip_spaces(const char *in)
570 {
571 	char *out;
572 	size_t t, l, i;
573 
574 	t = strlen(in);
575 	out = calloc(t + 1, sizeof(char));
576 	if (!out) {
577 		perror("calloc");
578 		return NULL;
579 	}
580 	for (i = 0, l = 0; i < t; ++i) {
581 		if (!char_is_space(in[i]))
582 			out[l++] = in[i];
583 	}
584 	return out;
585 }
586 
config_read_cmd(const char * argv[],int argc)587 struct wgdevice *config_read_cmd(const char *argv[], int argc)
588 {
589 	struct wgdevice *device = calloc(1, sizeof(*device));
590 	struct wgpeer *peer = NULL;
591 	struct wgallowedip *allowedip = NULL;
592 
593 	if (!device) {
594 		perror("calloc");
595 		return false;
596 	}
597 	while (argc > 0) {
598 		if (!strcmp(argv[0], "listen-port") && argc >= 2 && !peer) {
599 			if (!parse_port(&device->listen_port, &device->flags, argv[1]))
600 				goto error;
601 			argv += 2;
602 			argc -= 2;
603 		} else if (!strcmp(argv[0], "fwmark") && argc >= 2 && !peer) {
604 			if (!parse_fwmark(&device->fwmark, &device->flags, argv[1]))
605 				goto error;
606 			argv += 2;
607 			argc -= 2;
608 		} else if (!strcmp(argv[0], "private-key") && argc >= 2 && !peer) {
609 			if (!parse_keyfile(device->private_key, argv[1]))
610 				goto error;
611 			device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
612 			argv += 2;
613 			argc -= 2;
614 		} else if (!strcmp(argv[0], "peer") && argc >= 2) {
615 			struct wgpeer *new_peer = calloc(1, sizeof(*new_peer));
616 
617 			allowedip = NULL;
618 			if (!new_peer) {
619 				perror("calloc");
620 				goto error;
621 			}
622 			if (peer)
623 				peer->next_peer = new_peer;
624 			else
625 				device->first_peer = new_peer;
626 			peer = new_peer;
627 			if (!parse_key(peer->public_key, argv[1]))
628 				goto error;
629 			peer->flags |= WGPEER_HAS_PUBLIC_KEY;
630 			argv += 2;
631 			argc -= 2;
632 		} else if (!strcmp(argv[0], "remove") && argc >= 1 && peer) {
633 			peer->flags |= WGPEER_REMOVE_ME;
634 			argv += 1;
635 			argc -= 1;
636 		} else if (!strcmp(argv[0], "endpoint") && argc >= 2 && peer) {
637 			if (!parse_endpoint(&peer->endpoint.addr, argv[1]))
638 				goto error;
639 			argv += 2;
640 			argc -= 2;
641 		} else if (!strcmp(argv[0], "allowed-ips") && argc >= 2 && peer) {
642 			char *line = strip_spaces(argv[1]);
643 
644 			if (!line)
645 				goto error;
646 			if (!parse_allowedips(peer, &allowedip, line)) {
647 				free(line);
648 				goto error;
649 			}
650 			free(line);
651 			argv += 2;
652 			argc -= 2;
653 		} else if (!strcmp(argv[0], "persistent-keepalive") && argc >= 2 && peer) {
654 			if (!parse_persistent_keepalive(&peer->persistent_keepalive_interval, &peer->flags, argv[1]))
655 				goto error;
656 			argv += 2;
657 			argc -= 2;
658 		} else if (!strcmp(argv[0], "preshared-key") && argc >= 2 && peer) {
659 			if (!parse_keyfile(peer->preshared_key, argv[1]))
660 				goto error;
661 			peer->flags |= WGPEER_HAS_PRESHARED_KEY;
662 			argv += 2;
663 			argc -= 2;
664 		} else {
665 			fprintf(stderr, "Invalid argument: %s\n", argv[0]);
666 			goto error;
667 		}
668 	}
669 	return device;
670 error:
671 	free_wgdevice(device);
672 	return false;
673 }
674