xref: /netbsd-src/usr.sbin/wgconfig/wgconfig.c (revision 3e0cc22b3739e7ea4ffd3909d563f663ab122d9a)
1 /*	$NetBSD: wgconfig.c,v 1.6 2023/05/07 16:05:07 oster Exp $	*/
2 
3 /*
4  * Copyright (C) Ryota Ozaki <ozaki.ryota@gmail.com>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the project nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 __RCSID("$NetBSD: wgconfig.c,v 1.6 2023/05/07 16:05:07 oster Exp $");
34 
35 #include <sys/ioctl.h>
36 
37 #include <net/if.h>
38 #include <net/if_wg.h>
39 
40 #include <arpa/inet.h>
41 
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <err.h>
46 #include <unistd.h>
47 #include <errno.h>
48 #include <resolv.h>
49 #include <util.h>
50 #include <netdb.h>
51 
52 #include <prop/proplib.h>
53 
54 #define PROP_BUFFER_LEN	4096
55 #define KEY_LEN			32
56 #define KEY_BASE64_LEN		44
57 
58 __dead static void
usage(void)59 usage(void)
60 {
61 	const char *progname = getprogname();
62 #define P(str) fprintf(stderr, "\t%s <interface> %s\n", progname, str)
63 
64 	fprintf(stderr, "Usage:\n");
65 	P("[show all]");
66 	P("show peer <peer name> [--show-preshared-key]");
67 	P("show private-key");
68 	P("set private-key <file path>");
69 	P("set listen-port <port>");
70 	P("add peer <peer name> <base64 public key>\n"
71 	"\t                     [--preshared-key=<file path>] [--endpoint=<ip>:<port>]\n"
72 	"\t                     [--allowed-ips=<ip1>/<cidr1>[,<ip2>/<cidr2>]...]");
73 	P("delete peer <peer name>");
74 
75 	exit(EXIT_FAILURE);
76 #undef P
77 }
78 
79 static const char *
format_key(prop_object_t key_prop)80 format_key(prop_object_t key_prop)
81 {
82 	int error;
83 	const void *key;
84 	size_t key_len;
85 	static char key_b64[KEY_BASE64_LEN + 1];
86 
87 	if (key_prop == NULL)
88 		return "(none)";
89 	if (prop_object_type(key_prop) != PROP_TYPE_DATA)
90 		errx(EXIT_FAILURE, "invalid key");
91 
92 	key = prop_data_value(key_prop);
93 	key_len = prop_data_size(key_prop);
94 	if (key_len != KEY_LEN)
95 		errx(EXIT_FAILURE, "invalid key len: %zu", key_len);
96 	error = b64_ntop(key, key_len, key_b64, KEY_BASE64_LEN + 1);
97 	if (error == -1)
98 		errx(EXIT_FAILURE, "b64_ntop failed");
99 	key_b64[KEY_BASE64_LEN] = '\0'; /* just in case */
100 
101 	return key_b64;
102 }
103 
104 static const char *
format_endpoint(prop_object_t endpoint_prop)105 format_endpoint(prop_object_t endpoint_prop)
106 {
107 	int error;
108 	static char buf[INET6_ADDRSTRLEN];
109 	struct sockaddr_storage sockaddr;
110 	const void *addr;
111 	size_t addr_len;
112 
113 	if (prop_object_type(endpoint_prop) != PROP_TYPE_DATA)
114 		errx(EXIT_FAILURE, "invalid endpoint");
115 
116 	addr = prop_data_value(endpoint_prop);
117 	addr_len = prop_data_size(endpoint_prop);
118 	memcpy(&sockaddr, addr, addr_len);
119 
120 	error = sockaddr_snprintf(buf, sizeof(buf), "%a:%p",
121 	    (struct sockaddr *)&sockaddr);
122 	if (error == -1)
123 		err(EXIT_FAILURE, "sockaddr_snprintf failed");
124 
125 	return buf;
126 }
127 
128 static void
handle_allowed_ips(prop_dictionary_t peer,const char * prefix)129 handle_allowed_ips(prop_dictionary_t peer, const char *prefix)
130 {
131 	prop_object_t prop_obj;
132 	prop_array_t allowedips;
133 	prop_object_iterator_t it;
134 	prop_dictionary_t allowedip;
135 	bool first = true;
136 
137 	prop_obj = prop_dictionary_get(peer, "allowedips");
138 	if (prop_obj == NULL)
139 		return;
140 	if (prop_object_type(prop_obj) != PROP_TYPE_ARRAY)
141 		errx(EXIT_FAILURE, "invalid allowedips");
142 	allowedips = prop_obj;
143 
144 	printf("%sallowed-ips: ", prefix);
145 
146 	it = prop_array_iterator(allowedips);
147 	while ((prop_obj = prop_object_iterator_next(it)) != NULL) {
148 		uint8_t family;
149 		uint8_t cidr;
150 		const void *addr;
151 		size_t addrlen, famaddrlen;
152 		char ntopbuf[INET6_ADDRSTRLEN];
153 		const char *ntopret;
154 
155 		if (prop_object_type(prop_obj) != PROP_TYPE_DICTIONARY) {
156 			warnx("invalid allowedip");
157 			continue;
158 		}
159 		allowedip = prop_obj;
160 
161 		if (!prop_dictionary_get_uint8(allowedip, "family", &family)) {
162 			warnx("allowed-ip without family");
163 			continue;
164 		}
165 
166 		if (!prop_dictionary_get_uint8(allowedip, "cidr", &cidr)) {
167 			warnx("allowed-ip without cidr");
168 			continue;
169 		}
170 
171 		if (!prop_dictionary_get_data(allowedip, "ip",
172 			&addr, &addrlen)) {
173 			warnx("allowed-ip without ip");
174 			continue;
175 		}
176 
177 		switch (family) {
178 		case AF_INET:
179 			famaddrlen = sizeof(struct in_addr);
180 			break;
181 		case AF_INET6:
182 			famaddrlen = sizeof(struct in6_addr);
183 			break;
184 		default:
185 			warnx("unknown family %d", family);
186 			continue;
187 		}
188 		if (addrlen != famaddrlen) {
189 			warnx("allowed-ip bad ip length");
190 			continue;
191 		}
192 
193 		ntopret = inet_ntop(family, addr, ntopbuf, sizeof(ntopbuf));
194 		if (ntopret == NULL)
195 			errx(EXIT_FAILURE, "inet_ntop failed");
196 		printf("%s%s/%u", first ? "" : ",", ntopbuf, cidr);
197 		first = false;
198 	}
199 	if (first)
200 		printf("(none)\n");
201 	else
202 		printf("\n");
203 }
204 
205 static prop_dictionary_t
ioctl_get(const char * interface)206 ioctl_get(const char *interface)
207 {
208 	int error = 0;
209 	struct ifdrv ifd;
210 	int sock;
211 	char *buf;
212 	prop_dictionary_t prop_dict;
213 
214 	sock = socket(AF_INET, SOCK_DGRAM, 0);
215 	if (error == -1)
216 		err(EXIT_FAILURE, "socket");
217 	buf = malloc(PROP_BUFFER_LEN);
218 	if (buf == NULL)
219 		errx(EXIT_FAILURE, "malloc failed");
220 
221 	strlcpy(ifd.ifd_name, interface, sizeof(ifd.ifd_name));
222 	ifd.ifd_cmd = 0;
223 	ifd.ifd_data = buf;
224 	ifd.ifd_len = PROP_BUFFER_LEN;
225 
226 	error = ioctl(sock, SIOCGDRVSPEC, &ifd);
227 	if (error == -1)
228 		err(EXIT_FAILURE, "ioctl(SIOCGDRVSPEC)");
229 
230 	prop_dict = prop_dictionary_internalize(buf);
231 	if (prop_dict == NULL)
232 		errx(EXIT_FAILURE, "prop_dictionary_internalize failed");
233 
234 	free(buf);
235 	close(sock);
236 
237 	return prop_dict;
238 }
239 
240 static void
show_peer(prop_dictionary_t peer,const char * prefix,bool show_psk)241 show_peer(prop_dictionary_t peer, const char *prefix, bool show_psk)
242 {
243 	prop_object_t prop_obj;
244 	time_t sec;
245 
246 	prop_obj = prop_dictionary_get(peer, "public_key");
247 	if (prop_obj == NULL) {
248 		warnx("peer without public-key");
249 		return;
250 	}
251 	printf("%spublic-key: %s\n", prefix, format_key(prop_obj));
252 
253 	prop_obj = prop_dictionary_get(peer, "endpoint");
254 	if (prop_obj == NULL)
255 		printf("%sendpoint: (none)\n", prefix);
256 	else
257 		printf("%sendpoint: %s\n", prefix, format_endpoint(prop_obj));
258 
259 	if (show_psk) {
260 		prop_obj = prop_dictionary_get(peer, "preshared_key");
261 		printf("%spreshared-key: %s\n", prefix, format_key(prop_obj));
262 	} else {
263 		printf("%spreshared-key: (hidden)\n", prefix);
264 	}
265 
266 	handle_allowed_ips(peer, prefix);
267 
268 	if (prop_dictionary_get_int64(peer, "last_handshake_time_sec", &sec)) {
269 		if (sec > 0)
270 			printf("%slatest-handshake: %s", prefix, ctime(&sec));
271 		else
272 			printf("%slatest-handshake: (never)\n", prefix);
273 	} else {
274 		printf("%slatest-handshake: (none)\n", prefix);
275 	}
276 }
277 
278 static int
cmd_show_all(const char * interface,int argc,char * argv[])279 cmd_show_all(const char *interface, int argc, char *argv[])
280 {
281 	prop_dictionary_t prop_dict;
282 	prop_object_t prop_obj;
283 	uint16_t port;
284 	prop_array_t peers;
285 
286 	prop_dict = ioctl_get(interface);
287 
288 	printf("interface: %s\n", interface);
289 
290 #if 0
291 	prop_obj = prop_dictionary_get(prop_dict, "private_key");
292 	printf("\tprivate-key: %s\n", format_key(prop_obj));
293 #else
294 	printf("\tprivate-key: (hidden)\n");
295 #endif
296 
297 	if (prop_dictionary_get_uint16(prop_dict, "listen_port", &port)) {
298 		printf("\tlisten-port: %u\n", port);
299 	} else {
300 		printf("\tlisten-port: (none)\n");
301 	}
302 
303 	prop_obj = prop_dictionary_get(prop_dict, "peers");
304 	if (prop_obj == NULL)
305 		return EXIT_SUCCESS;
306 	if (prop_object_type(prop_obj) != PROP_TYPE_ARRAY)
307 		errx(EXIT_FAILURE, "invalid peers");
308 	peers = prop_obj;
309 
310 	prop_object_iterator_t it = prop_array_iterator(peers);
311 	while ((prop_obj = prop_object_iterator_next(it)) != NULL) {
312 		const char *name;
313 
314 		if (prop_object_type(prop_obj) != PROP_TYPE_DICTIONARY)
315 			errx(EXIT_FAILURE, "invalid peer");
316 		prop_dictionary_t peer = prop_obj;
317 
318 		if (prop_dictionary_get_string(peer, "name", &name)) {
319 			printf("\tpeer: %s\n", name);
320 		} else
321 			printf("\tpeer: (none)\n");
322 
323 		show_peer(peer, "\t\t", false);
324 	}
325 
326 	return EXIT_SUCCESS;
327 }
328 
329 static int
cmd_show_peer(const char * interface,int argc,char * argv[])330 cmd_show_peer(const char *interface, int argc, char *argv[])
331 {
332 	prop_dictionary_t prop_dict;
333 	prop_object_t prop_obj;
334 	const char *target;
335 	const char *opt = "--show-preshared-key";
336 	bool show_psk = false;
337 
338 	if (argc != 1 && argc != 2)
339 		usage();
340 	target = argv[0];
341 	if (argc == 2) {
342 		if (strncmp(argv[1], opt, strlen(opt)) != 0)
343 			usage();
344 		show_psk = true;
345 	}
346 
347 	prop_dict = ioctl_get(interface);
348 
349 	prop_obj = prop_dictionary_get(prop_dict, "peers");
350 	if (prop_obj == NULL)
351 		return EXIT_SUCCESS;
352 	if (prop_object_type(prop_obj) != PROP_TYPE_ARRAY)
353 		errx(EXIT_FAILURE, "invalid peers");
354 
355 	prop_array_t peers = prop_obj;
356 	prop_object_iterator_t it = prop_array_iterator(peers);
357 	while ((prop_obj = prop_object_iterator_next(it)) != NULL) {
358 		const char *name;
359 
360 		if (prop_object_type(prop_obj) != PROP_TYPE_DICTIONARY)
361 			errx(EXIT_FAILURE, "invalid peer");
362 		prop_dictionary_t peer = prop_obj;
363 
364 		if (!prop_dictionary_get_string(peer, "name", &name))
365 			continue;
366 		if (strcmp(name, target) == 0) {
367 			printf("peer: %s\n", name);
368 			show_peer(peer, "\t", show_psk);
369 			return EXIT_SUCCESS;
370 		}
371 	}
372 
373 	return EXIT_FAILURE;
374 }
375 
376 static int
cmd_show_private_key(const char * interface,int argc,char * argv[])377 cmd_show_private_key(const char *interface, int argc, char *argv[])
378 {
379 	prop_dictionary_t prop_dict;
380 	prop_object_t prop_obj;
381 
382 	prop_dict = ioctl_get(interface);
383 
384 	prop_obj = prop_dictionary_get(prop_dict, "private_key");
385 	printf("private-key: %s\n", format_key(prop_obj));
386 
387 	return EXIT_SUCCESS;
388 }
389 
390 static void
ioctl_set(const char * interface,int cmd,char * propstr)391 ioctl_set(const char *interface, int cmd, char *propstr)
392 {
393 	int error;
394 	struct ifdrv ifd;
395 	int sock;
396 
397 	strlcpy(ifd.ifd_name, interface, sizeof(ifd.ifd_name));
398 	ifd.ifd_cmd = cmd;
399 	ifd.ifd_data = propstr;
400 	ifd.ifd_len = strlen(propstr);
401 	sock = socket(AF_INET, SOCK_DGRAM, 0);
402 	error = ioctl(sock, SIOCSDRVSPEC, &ifd);
403 	if (error == -1)
404 		err(EXIT_FAILURE, "ioctl(SIOCSDRVSPEC): cmd=%d", cmd);
405 	close(sock);
406 }
407 
408 static void
base64_decode(const char keyb64buf[KEY_BASE64_LEN+1],unsigned char keybuf[KEY_LEN])409 base64_decode(const char keyb64buf[KEY_BASE64_LEN + 1],
410     unsigned char keybuf[KEY_LEN])
411 {
412 	int ret;
413 
414 	ret = b64_pton(keyb64buf, keybuf, KEY_LEN);
415 	if (ret == -1)
416 		errx(EXIT_FAILURE, "b64_pton failed");
417 }
418 
419 static void
read_key(const char * path,unsigned char keybuf[KEY_LEN])420 read_key(const char *path, unsigned char keybuf[KEY_LEN])
421 {
422 	FILE *fp;
423 	char keyb64buf[KEY_BASE64_LEN + 1];
424 	size_t n;
425 
426 	fp = fopen(path, "r");
427 	if (fp == NULL)
428 		err(EXIT_FAILURE, "fopen");
429 
430 	n = fread(keyb64buf, 1, KEY_BASE64_LEN, fp);
431 	if (n != KEY_BASE64_LEN)
432 		errx(EXIT_FAILURE, "base64 key len is short: %zu", n);
433 	keyb64buf[KEY_BASE64_LEN] = '\0';
434 
435 	base64_decode(keyb64buf, keybuf);
436 }
437 
438 static int
cmd_set_private_key(const char * interface,int argc,char * argv[])439 cmd_set_private_key(const char *interface, int argc, char *argv[])
440 {
441 	unsigned char keybuf[KEY_LEN];
442 
443 	if (argc != 1)
444 		usage();
445 
446 	read_key(argv[0], keybuf);
447 
448 	prop_dictionary_t prop_dict;
449 	prop_dict = prop_dictionary_create();
450 	if (prop_dict == NULL)
451 		errx(EXIT_FAILURE, "prop_dictionary_create");
452 
453 	if (!prop_dictionary_set_data(prop_dict, "private_key",
454 		keybuf, sizeof(keybuf)))
455 		errx(EXIT_FAILURE, "prop_dictionary_set_data");
456 
457 	char *buf = prop_dictionary_externalize(prop_dict);
458 	if (buf == NULL)
459 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
460 	ioctl_set(interface, WG_IOCTL_SET_PRIVATE_KEY, buf);
461 
462 	return EXIT_SUCCESS;
463 }
464 
465 static uint16_t
strtouint16(const char * str)466 strtouint16(const char *str)
467 {
468 	char *ep;
469 	long val;
470 
471 	errno = 0;
472 	val = strtol(str, &ep, 10);
473 	if (ep == str)
474 		errx(EXIT_FAILURE, "strtol: not a number");
475 	if (*ep != '\0')
476 		errx(EXIT_FAILURE, "strtol: trailing garbage");
477 	if (errno != 0)
478 		err(EXIT_FAILURE, "strtol");
479 	if (val < 0 || val > USHRT_MAX)
480 		errx(EXIT_FAILURE, "out of range");
481 
482 	return (uint16_t)val;
483 }
484 
485 static int
cmd_set_listen_port(const char * interface,int argc,char * argv[])486 cmd_set_listen_port(const char *interface, int argc, char *argv[])
487 {
488 	uint16_t port;
489 
490 	if (argc != 1)
491 		usage();
492 
493 	port = strtouint16(argv[0]);
494 	if (port == 0)
495 		errx(EXIT_FAILURE, "port 0 is not allowed");
496 
497 	prop_dictionary_t prop_dict;
498 	prop_dict = prop_dictionary_create();
499 	if (prop_dict == NULL)
500 		errx(EXIT_FAILURE, "prop_dictionary_create");
501 
502 	if (!prop_dictionary_set_uint16(prop_dict, "listen_port", port))
503 		errx(EXIT_FAILURE, "prop_dictionary_set_uint16");
504 
505 	char *buf = prop_dictionary_externalize(prop_dict);
506 	if (buf == NULL)
507 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
508 	ioctl_set(interface, WG_IOCTL_SET_LISTEN_PORT, buf);
509 
510 	return EXIT_SUCCESS;
511 }
512 
513 static void
handle_option_endpoint(const char * _addr_port,prop_dictionary_t prop_dict)514 handle_option_endpoint(const char *_addr_port, prop_dictionary_t prop_dict)
515 {
516 	int error;
517 	char *port;
518 	struct addrinfo hints, *res;
519 	char *addr_port, *addr;
520 
521 	addr = addr_port = strdup(_addr_port);
522 
523 	if (addr_port[0] == '[') {
524 		/* [<ipv6>]:<port> */
525 		/* Accept [<ipv4>]:<port> too, but it's not a big deal. */
526 		char *bracket, *colon;
527 		if (strlen(addr_port) < strlen("[::]:0"))
528 			errx(EXIT_FAILURE, "invalid endpoint format");
529 		addr = addr_port + 1;
530 		bracket = strchr(addr, ']');
531 		if (bracket == NULL)
532 			errx(EXIT_FAILURE, "invalid endpoint format");
533 		*bracket = '\0';
534 		colon = bracket + 1;
535 		if (*colon != ':')
536 			errx(EXIT_FAILURE, "invalid endpoint format");
537 		*colon = '\0';
538 		port = colon + 1;
539 	} else {
540 		char *colon, *tmp;
541 		colon = strchr(addr_port, ':');
542 		if (colon == NULL)
543 			errx(EXIT_FAILURE, "no ':' found in endpoint");
544 		tmp = strchr(colon + 1, ':');
545 		if (tmp != NULL) {
546 			/* <ipv6>:<port> */
547 			/* Assume the last colon is a separator */
548 			char *last_colon = tmp;
549 			while ((tmp = strchr(tmp + 1, ':')) != NULL)
550 				last_colon = tmp;
551 			colon = last_colon;
552 			*colon = '\0';
553 			port = colon + 1;
554 		} else {
555 			/* <ipv4>:<port> */
556 			*colon = '\0';
557 			port = colon + 1;
558 		}
559 	}
560 
561 	memset(&hints, 0, sizeof(hints));
562 	hints.ai_family = AF_UNSPEC;
563 	hints.ai_flags = AI_NUMERICHOST;
564 	error = getaddrinfo(addr, port, &hints, &res);
565 	if (error)
566 		errx(EXIT_FAILURE, "getaddrinfo: %s", gai_strerror(error));
567 
568 	if (!prop_dictionary_set_data(prop_dict, "endpoint",
569 		res->ai_addr, res->ai_addrlen))
570 		errx(EXIT_FAILURE, "prop_dictionary_set_data");
571 
572 	freeaddrinfo(res);
573 	free(addr_port);
574 }
575 
576 static void
handle_option_allowed_ips(const char * _allowed_ips,prop_dictionary_t prop_dict)577 handle_option_allowed_ips(const char *_allowed_ips, prop_dictionary_t prop_dict)
578 {
579 	prop_array_t allowedips;
580 	int i;
581 	char *allowed_ips, *ip;
582 
583 	allowed_ips = strdup(_allowed_ips);
584 	if (allowed_ips == NULL)
585 		errx(EXIT_FAILURE, "strdup");
586 
587 	allowedips = prop_array_create();
588 	if (allowedips == NULL)
589 		errx(EXIT_FAILURE, "prop_array_create");
590 
591 	for (i = 0; (ip = strsep(&allowed_ips, ",")) != NULL; i++) {
592 		prop_dictionary_t prop_allowedip;
593 		uint16_t cidr;
594 		char *cidrp;
595 		struct addrinfo hints, *res;
596 		int error;
597 
598 		prop_allowedip = prop_dictionary_create();
599 		if (prop_allowedip == NULL)
600 			errx(EXIT_FAILURE, "prop_dictionary_create");
601 
602 		cidrp = strchr(ip, '/');
603 		if (cidrp == NULL)
604 			errx(EXIT_FAILURE, "no '/' found in allowed-ip");
605 		*cidrp = '\0';
606 		cidrp++;
607 
608 		cidr = strtouint16(cidrp);
609 
610 		memset(&hints, 0, sizeof(hints));
611 		hints.ai_family = AF_UNSPEC;
612 		hints.ai_flags = AI_NUMERICHOST;
613 		error = getaddrinfo(ip, 0, &hints, &res);
614 		if (error)
615 			errx(EXIT_FAILURE, "getaddrinfo: %s",
616 			    gai_strerror(errno));
617 
618 		sa_family_t family = res->ai_addr->sa_family;
619 		if (!prop_dictionary_set_uint8(prop_allowedip, "family",
620 			family))
621 			errx(EXIT_FAILURE, "prop_dictionary_set_uint8");
622 
623 		const void *addr;
624 		size_t addrlen;
625 		switch (family) {
626 		case AF_INET: {
627 			const struct sockaddr_in *sin =
628 			    (const struct sockaddr_in *)res->ai_addr;
629 			addr = &sin->sin_addr;
630 			addrlen = sizeof(sin->sin_addr);
631 			break;
632 		}
633 		case AF_INET6: {
634 			const struct sockaddr_in6 *sin6 =
635 			    (const struct sockaddr_in6 *)res->ai_addr;
636 			addr = &sin6->sin6_addr;
637 			addrlen = sizeof(sin6->sin6_addr);
638 			break;
639 		}
640 		default:
641 			errx(EXIT_FAILURE, "invalid family: %d", family);
642 		}
643 		if (!prop_dictionary_set_data(prop_allowedip, "ip",
644 			addr, addrlen))
645 			errx(EXIT_FAILURE, "prop_dictionary_set_data");
646 		if (!prop_dictionary_set_uint16(prop_allowedip, "cidr", cidr))
647 			errx(EXIT_FAILURE, "prop_dictionary_set_uint16");
648 
649 		freeaddrinfo(res);
650 		prop_array_set(allowedips, i, prop_allowedip);
651 	}
652 	prop_dictionary_set(prop_dict, "allowedips", allowedips);
653 	prop_object_release(allowedips);
654 
655 	free(allowed_ips);
656 }
657 
658 static void
handle_option_preshared_key(const char * path,prop_dictionary_t prop_dict)659 handle_option_preshared_key(const char *path, prop_dictionary_t prop_dict)
660 {
661 	unsigned char keybuf[KEY_LEN];
662 
663 	read_key(path, keybuf);
664 	if (!prop_dictionary_set_data(prop_dict, "preshared_key",
665 		keybuf, sizeof(keybuf)))
666 		errx(EXIT_FAILURE, "prop_dictionary_set_data");
667 }
668 
669 static const struct option {
670 	const char	*option;
671 	void		(*func)(const char *, prop_dictionary_t);
672 } options[] = {
673 	{"--endpoint=",		handle_option_endpoint},
674 	{"--allowed-ips=",	handle_option_allowed_ips},
675 	{"--preshared-key=",	handle_option_preshared_key},
676 };
677 
678 static void
handle_options(int argc,char * argv[],prop_dictionary_t prop_dict)679 handle_options(int argc, char *argv[], prop_dictionary_t prop_dict)
680 {
681 
682 	while (argc > 0) {
683 		int found = 0;
684 		for (size_t i = 0; i < __arraycount(options); i++) {
685 			const struct option *opt = &options[i];
686 			size_t optlen = strlen(opt->option);
687 			if (strncmp(argv[0], opt->option, optlen) == 0) {
688 				opt->func(argv[0] + optlen, prop_dict);
689 				found = 1;
690 				break;
691 			}
692 		}
693 		if (found == 0)
694 			errx(EXIT_FAILURE, "invalid option: %s", argv[0]);
695 		argc -= 1;
696 		argv += 1;
697 	}
698 
699 	if (argc != 0)
700 		usage();
701 }
702 
703 static int
cmd_add_peer(const char * interface,int argc,char * argv[])704 cmd_add_peer(const char *interface, int argc, char *argv[])
705 {
706 	const char *name;
707 	unsigned char keybuf[KEY_LEN];
708 
709 	if (argc < 2)
710 		usage();
711 
712 	prop_dictionary_t prop_dict;
713 	prop_dict = prop_dictionary_create();
714 	if (prop_dict == NULL)
715 		errx(EXIT_FAILURE, "prop_dictionary_create");
716 
717 	name = argv[0];
718 	if (strlen(name) > WG_PEER_NAME_MAXLEN)
719 		errx(EXIT_FAILURE, "peer name too long");
720 	if (strnlen(argv[1], KEY_BASE64_LEN + 1) != KEY_BASE64_LEN)
721 		errx(EXIT_FAILURE, "invalid public-key length: %zu",
722 		    strlen(argv[1]));
723 	base64_decode(argv[1], keybuf);
724 
725 	if (!prop_dictionary_set_string(prop_dict, "name", name))
726 		errx(EXIT_FAILURE, "prop_dictionary_set_string");
727 	if (!prop_dictionary_set_data(prop_dict, "public_key",
728 		keybuf, sizeof(keybuf)))
729 		errx(EXIT_FAILURE, "prop_dictionary_set_data");
730 
731 	argc -= 2;
732 	argv += 2;
733 
734 	handle_options(argc, argv, prop_dict);
735 
736 	char *buf = prop_dictionary_externalize(prop_dict);
737 	if (buf == NULL)
738 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
739 	ioctl_set(interface, WG_IOCTL_ADD_PEER, buf);
740 
741 	return EXIT_SUCCESS;
742 }
743 
744 static int
cmd_delete_peer(const char * interface,int argc,char * argv[])745 cmd_delete_peer(const char *interface, int argc, char *argv[])
746 {
747 	const char *name;
748 
749 	if (argc != 1)
750 		usage();
751 
752 	prop_dictionary_t prop_dict;
753 	prop_dict = prop_dictionary_create();
754 	if (prop_dict == NULL)
755 		errx(EXIT_FAILURE, "prop_dictionary_create");
756 
757 	name = argv[0];
758 	if (strlen(name) > WG_PEER_NAME_MAXLEN)
759 		errx(EXIT_FAILURE, "peer name too long");
760 
761 	if (!prop_dictionary_set_string(prop_dict, "name", name))
762 		errx(EXIT_FAILURE, "prop_dictionary_set_string");
763 
764 	char *buf = prop_dictionary_externalize(prop_dict);
765 	if (buf == NULL)
766 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
767 	ioctl_set(interface, WG_IOCTL_DELETE_PEER, buf);
768 
769 	return EXIT_SUCCESS;
770 }
771 
772 static const struct command {
773 	const char	*command;
774 	const char	*target;
775 	int		(*func)(const char *, int, char **);
776 } commands[] = {
777 	{"show",	"all",		cmd_show_all},
778 	{"show",	"peer",		cmd_show_peer},
779 	{"show",	"private-key",	cmd_show_private_key},
780 	{"set",		"private-key",	cmd_set_private_key},
781 	{"set",		"listen-port",	cmd_set_listen_port},
782 	{"add",		"peer",		cmd_add_peer},
783 	{"delete",	"peer",		cmd_delete_peer},
784 };
785 
786 int
main(int argc,char * argv[])787 main(int argc, char *argv[])
788 {
789 	const char *interface;
790 	const char *command;
791 	const char *target;
792 
793 	if (argc < 2 ||
794 	    strcmp(argv[1], "-h") == 0 ||
795 	    strcmp(argv[1], "-?") == 0 ||
796 	    strcmp(argv[1], "--help") == 0) {
797 		usage();
798 	}
799 
800 	interface = argv[1];
801 	if (strlen(interface) > IFNAMSIZ)
802 		errx(EXIT_FAILURE, "interface name too long");
803 	if (argc == 2) {
804 		return cmd_show_all(interface, 0, NULL);
805 	}
806 	if (argc < 4) {
807 		usage();
808 	}
809 	command = argv[2];
810 	target = argv[3];
811 
812 	argc -= 4;
813 	argv += 4;
814 
815 	for (size_t i = 0; i < __arraycount(commands); i++) {
816 		const struct command *cmd = &commands[i];
817 		if (strncmp(command, cmd->command, strlen(cmd->command)) == 0 &&
818 		    strncmp(target, cmd->target, strlen(cmd->target)) == 0) {
819 			return cmd->func(interface, argc, argv);
820 		}
821 	}
822 
823 	usage();
824 }
825