Home | History | Annotate | Line # | Download | only in wgconfig
wgconfig.c revision 1.1
      1 /*	$NetBSD: wgconfig.c,v 1.1 2020/08/20 21:28:02 riastradh Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Ryota Ozaki <ozaki.ryota (at) 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.1 2020/08/20 21:28:02 riastradh 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
     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 *
     80 format_key(prop_object_t key_prop)
     81 {
     82 	int error;
     83 	unsigned char *key;
     84 	size_t key_len;
     85 	static char key_b64[KEY_BASE64_LEN + 1];
     86 	static const char *none = "(none)";
     87 
     88 	if (key_prop == NULL)
     89 		return none;
     90 
     91 	key = prop_data_data(key_prop);
     92 	key_len = prop_data_size(key_prop);
     93 	if (key_len != KEY_LEN)
     94 		errx(EXIT_FAILURE, "invalid key len: %lu", key_len);
     95 	error = b64_ntop(key, key_len, key_b64, KEY_BASE64_LEN + 1);
     96 	if (error == -1)
     97 		errx(EXIT_FAILURE, "b64_ntop failed");
     98 	key_b64[KEY_BASE64_LEN] = '\0'; /* just in case */
     99 
    100 	return key_b64;
    101 }
    102 
    103 static const char *
    104 format_endpoint(prop_object_t endpoint_prop)
    105 {
    106 	int error;
    107 	static char buf[INET6_ADDRSTRLEN];
    108 	struct sockaddr_storage sockaddr;
    109 	char *addr;
    110 	size_t addr_len;
    111 
    112 	addr = prop_data_data(endpoint_prop);
    113 	addr_len = prop_data_size(endpoint_prop);
    114 	memcpy(&sockaddr, addr, addr_len);
    115 
    116 	error = sockaddr_snprintf(buf, sizeof(buf), "%a:%p",
    117 	    (struct sockaddr *)&sockaddr);
    118 	if (error == -1)
    119 		err(EXIT_FAILURE, "sockaddr_snprintf failed");
    120 
    121 	return buf;
    122 }
    123 
    124 static void
    125 handle_allowed_ips(prop_dictionary_t peer, const char *prefix)
    126 {
    127 	prop_array_t allowedips;
    128 	prop_object_iterator_t it;
    129 	prop_dictionary_t allowedip;
    130 	bool first = true;
    131 
    132 	allowedips = prop_dictionary_get(peer, "allowedips");
    133 	if (allowedips == NULL)
    134 		return;
    135 
    136 	printf("%sallowed-ips: ", prefix);
    137 
    138 	it = prop_array_iterator(allowedips);
    139 	while ((allowedip = prop_object_iterator_next(it)) != NULL) {
    140 		prop_object_t prop_obj;
    141 		uint8_t family;
    142 		uint8_t cidr;
    143 		char *addr;
    144 		char ntopbuf[INET6_ADDRSTRLEN];
    145 		const char *ntopret;
    146 
    147 		prop_obj = prop_dictionary_get(allowedip, "family");
    148 		if (prop_obj == NULL) {
    149 			warnx("allowed-ip without family");
    150 			continue;
    151 		}
    152 
    153 		family = (uint8_t)prop_number_unsigned_integer_value(prop_obj);
    154 
    155 		prop_obj = prop_dictionary_get(allowedip, "cidr");
    156 		if (prop_obj == NULL) {
    157 			warnx("allowed-ip without cidr");
    158 			continue;
    159 		}
    160 		cidr = (uint8_t)prop_number_unsigned_integer_value(prop_obj);
    161 
    162 		prop_obj = prop_dictionary_get(allowedip, "ip");
    163 		if (prop_obj == NULL) {
    164 			warnx("allowed-ip without ip");
    165 			continue;
    166 		}
    167 
    168 		addr = prop_data_data(prop_obj);
    169 		ntopret = inet_ntop(family, addr, ntopbuf, sizeof(ntopbuf));
    170 		if (ntopret == NULL)
    171 			errx(EXIT_FAILURE, "inet_ntop failed");
    172 		printf("%s%s/%u", first ? "" : ",", ntopbuf, cidr);
    173 		first = false;
    174 	}
    175 	if (first)
    176 		printf("(none)\n");
    177 	else
    178 		printf("\n");
    179 }
    180 
    181 static prop_dictionary_t
    182 ioctl_get(const char *interface)
    183 {
    184 	int error = 0;
    185 	struct ifdrv ifd;
    186 	int sock;
    187 	char *buf;
    188 	prop_dictionary_t prop_dict;
    189 
    190 	sock = socket(AF_INET, SOCK_DGRAM, 0);
    191 	if (error == -1)
    192 		err(EXIT_FAILURE, "socket");
    193 	buf = malloc(PROP_BUFFER_LEN);
    194 	if (buf == NULL)
    195 		errx(EXIT_FAILURE, "malloc failed");
    196 
    197 	strlcpy(ifd.ifd_name, interface, sizeof(ifd.ifd_name));
    198 	ifd.ifd_cmd = 0;
    199 	ifd.ifd_data = buf;
    200 	ifd.ifd_len = PROP_BUFFER_LEN;
    201 
    202 	error = ioctl(sock, SIOCGDRVSPEC, &ifd);
    203 	if (error == -1)
    204 		err(EXIT_FAILURE, "ioctl(SIOCGDRVSPEC)");
    205 
    206 	prop_dict = prop_dictionary_internalize(buf);
    207 	if (prop_dict == NULL)
    208 		errx(EXIT_FAILURE, "prop_dictionary_internalize failed");
    209 
    210 	free(buf);
    211 	close(sock);
    212 
    213 	return prop_dict;
    214 }
    215 
    216 static void
    217 show_peer(prop_dictionary_t peer, const char *prefix, bool show_psk)
    218 {
    219 	prop_object_t prop_obj;
    220 
    221 	prop_obj = prop_dictionary_get(peer, "public_key");
    222 	if (prop_obj == NULL) {
    223 		warnx("peer without public-key");
    224 		return;
    225 	}
    226 	printf("%spublic-key: %s\n", prefix, format_key(prop_obj));
    227 
    228 	prop_obj = prop_dictionary_get(peer, "endpoint");
    229 	if (prop_obj == NULL)
    230 		printf("%sendpoint: (none)\n", prefix);
    231 	else
    232 		printf("%sendpoint: %s\n", prefix, format_endpoint(prop_obj));
    233 
    234 	if (show_psk) {
    235 		prop_obj = prop_dictionary_get(peer, "preshared_key");
    236 		printf("%spreshared-key: %s\n", prefix, format_key(prop_obj));
    237 	} else {
    238 		printf("%spreshared-key: (hidden)\n", prefix);
    239 	}
    240 
    241 	handle_allowed_ips(peer, prefix);
    242 
    243 	prop_obj = prop_dictionary_get(peer, "last_handshake_time_sec");
    244 	if (prop_obj != NULL) {
    245 		uint64_t sec = prop_number_unsigned_integer_value(prop_obj);
    246 		printf("%slatest-handshake: %"PRIu64"\n", prefix, sec);
    247 	} else
    248 		printf("%slatest-handshake: (none)\n", prefix);
    249 #if 0
    250 	prop_obj = prop_dictionary_get(peer, "last_handshake_time_nsec");
    251 #endif
    252 }
    253 
    254 static int
    255 cmd_show_all(const char *interface, int argc, char *argv[])
    256 {
    257 	prop_dictionary_t prop_dict;
    258 	prop_object_t prop_obj;
    259 
    260 	prop_dict = ioctl_get(interface);
    261 
    262 	printf("interface: %s\n", interface);
    263 
    264 #if 0
    265 	prop_obj = prop_dictionary_get(prop_dict, "private_key");
    266 	printf("\tprivate-key: %s\n", format_key(prop_obj));
    267 #else
    268 	printf("\tprivate-key: (hidden)\n");
    269 #endif
    270 
    271 	prop_obj = prop_dictionary_get(prop_dict, "listen_port");
    272 	if (prop_obj != NULL) {
    273 		uint64_t port = prop_number_unsigned_integer_value(prop_obj);
    274 		if (port != (uint64_t)(uint16_t)port)
    275 			errx(EXIT_FAILURE, "invalid port: %" PRIu64, port);
    276 		printf("\tlisten-port: %u\n", (uint16_t)port);
    277 	} else {
    278 		printf("\tlisten-port: (none)\n");
    279 	}
    280 
    281 	prop_array_t peers = prop_dictionary_get(prop_dict, "peers");
    282 	if (peers == NULL)
    283 		return EXIT_SUCCESS;
    284 
    285 	prop_object_iterator_t it = prop_array_iterator(peers);
    286 	prop_dictionary_t peer;
    287 	while ((peer = prop_object_iterator_next(it)) != NULL) {
    288 		prop_obj = prop_dictionary_get(peer, "name");
    289 		if (prop_obj != NULL) {
    290 			const char *name = prop_string_cstring_nocopy(prop_obj);
    291 			printf("\tpeer: %s\n", name);
    292 		} else
    293 			printf("\tpeer: (none)\n");
    294 
    295 		show_peer(peer, "\t\t", false);
    296 	}
    297 
    298 	return EXIT_SUCCESS;
    299 }
    300 
    301 static int
    302 cmd_show_peer(const char *interface, int argc, char *argv[])
    303 {
    304 	prop_dictionary_t prop_dict;
    305 	const char *target;
    306 	const char *opt = "--show-preshared-key";
    307 	bool show_psk = false;
    308 
    309 	if (argc != 1 && argc != 2)
    310 		usage();
    311 	target = argv[0];
    312 	if (argc == 2) {
    313 		if (strncmp(argv[1], opt, strlen(opt)) != 0)
    314 			usage();
    315 		show_psk = true;
    316 	}
    317 
    318 	prop_dict = ioctl_get(interface);
    319 
    320 	prop_array_t peers = prop_dictionary_get(prop_dict, "peers");
    321 	if (peers == NULL)
    322 		return EXIT_SUCCESS;
    323 
    324 	prop_object_iterator_t it = prop_array_iterator(peers);
    325 	prop_dictionary_t peer;
    326 	while ((peer = prop_object_iterator_next(it)) != NULL) {
    327 		prop_object_t prop_obj;
    328 		prop_obj = prop_dictionary_get(peer, "name");
    329 		if (prop_obj == NULL)
    330 			continue;
    331 		const char *name = prop_string_cstring_nocopy(prop_obj);
    332 		if (strcmp(name, target) == 0) {
    333 			printf("peer: %s\n", name);
    334 			show_peer(peer, "\t", show_psk);
    335 			break;
    336 		}
    337 	}
    338 
    339 	return EXIT_SUCCESS;
    340 }
    341 
    342 static int
    343 cmd_show_private_key(const char *interface, int argc, char *argv[])
    344 {
    345 	prop_dictionary_t prop_dict;
    346 	prop_object_t prop_obj;
    347 
    348 	prop_dict = ioctl_get(interface);
    349 
    350 	prop_obj = prop_dictionary_get(prop_dict, "private_key");
    351 	printf("private-key: %s\n", format_key(prop_obj));
    352 
    353 	return EXIT_SUCCESS;
    354 }
    355 
    356 static void
    357 ioctl_set(const char *interface, int cmd, char *propstr)
    358 {
    359 	int error;
    360 	struct ifdrv ifd;
    361 	int sock;
    362 
    363 	strlcpy(ifd.ifd_name, interface, sizeof(ifd.ifd_name));
    364 	ifd.ifd_cmd = cmd;
    365 	ifd.ifd_data = propstr;
    366 	ifd.ifd_len = strlen(propstr);
    367 	sock = socket(AF_INET, SOCK_DGRAM, 0);
    368 	error = ioctl(sock, SIOCSDRVSPEC, &ifd);
    369 	if (error == -1)
    370 		err(EXIT_FAILURE, "ioctl(SIOCSDRVSPEC): cmd=%d", cmd);
    371 	close(sock);
    372 }
    373 
    374 static void
    375 base64_decode(const char keyb64buf[KEY_BASE64_LEN + 1],
    376     unsigned char keybuf[KEY_LEN])
    377 {
    378 	int ret;
    379 
    380 	ret = b64_pton(keyb64buf, keybuf, KEY_LEN);
    381 	if (ret == -1)
    382 		errx(EXIT_FAILURE, "b64_pton failed");
    383 }
    384 
    385 static void
    386 read_key(const char *path, unsigned char keybuf[KEY_LEN])
    387 {
    388 	FILE *fp;
    389 	char keyb64buf[KEY_BASE64_LEN + 1];
    390 	size_t n;
    391 
    392 	fp = fopen(path, "r");
    393 	if (fp == NULL)
    394 		err(EXIT_FAILURE, "fopen");
    395 
    396 	n = fread(keyb64buf, 1, KEY_BASE64_LEN, fp);
    397 	if (n != KEY_BASE64_LEN)
    398 		errx(EXIT_FAILURE, "base64 key len is short: %lu", n);
    399 	keyb64buf[KEY_BASE64_LEN] = '\0';
    400 
    401 	base64_decode(keyb64buf, keybuf);
    402 }
    403 
    404 static int
    405 cmd_set_private_key(const char *interface, int argc, char *argv[])
    406 {
    407 	unsigned char keybuf[KEY_LEN];
    408 
    409 	if (argc != 1)
    410 		usage();
    411 
    412 	read_key(argv[0], keybuf);
    413 
    414 	prop_dictionary_t prop_dict;
    415 	prop_dict = prop_dictionary_create();
    416 	prop_data_t privkey = prop_data_create_data(keybuf, sizeof(keybuf));
    417 	prop_dictionary_set(prop_dict, "private_key", privkey);
    418 	prop_object_release(privkey);
    419 
    420 	char *buf = prop_dictionary_externalize(prop_dict);
    421 	if (buf == NULL)
    422 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
    423 	ioctl_set(interface, WG_IOCTL_SET_PRIVATE_KEY, buf);
    424 
    425 	return EXIT_SUCCESS;
    426 }
    427 
    428 static uint16_t
    429 strtouint16(const char *str)
    430 {
    431 	char *ep;
    432 	long val;
    433 
    434 	errno = 0;
    435 	val = strtol(str, &ep, 10);
    436 	if (ep == str)
    437 		errx(EXIT_FAILURE, "strtol: not a number");
    438 	if (*ep != '\0')
    439 		errx(EXIT_FAILURE, "strtol: trailing garbage");
    440 	if (errno != 0)
    441 		err(EXIT_FAILURE, "strtol");
    442 	if (val < 0 || val > USHRT_MAX)
    443 		errx(EXIT_FAILURE, "out of range");
    444 
    445 	return (uint16_t)val;
    446 }
    447 
    448 static int
    449 cmd_set_listen_port(const char *interface, int argc, char *argv[])
    450 {
    451 	uint16_t port;
    452 
    453 	if (argc != 1)
    454 		usage();
    455 
    456 	port = strtouint16(argv[0]);
    457 	if (port == 0)
    458 		errx(EXIT_FAILURE, "port 0 is not allowed");
    459 
    460 	prop_dictionary_t prop_dict;
    461 	prop_dict = prop_dictionary_create();
    462 	prop_number_t prop_port = prop_number_create_unsigned_integer(port);
    463 	prop_dictionary_set(prop_dict, "listen_port", prop_port);
    464 	prop_object_release(prop_port);
    465 
    466 	char *buf = prop_dictionary_externalize(prop_dict);
    467 	if (buf == NULL)
    468 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
    469 	ioctl_set(interface, WG_IOCTL_SET_LISTEN_PORT, buf);
    470 
    471 	return EXIT_SUCCESS;
    472 }
    473 
    474 static void
    475 handle_option_endpoint(const char *_addr_port, prop_dictionary_t prop_dict)
    476 {
    477 	int error;
    478 	prop_data_t prop_addr;
    479 	char *port;
    480 	struct addrinfo hints, *res;
    481 	char *addr_port, *addr;
    482 
    483 	addr = addr_port = strdup(_addr_port);
    484 
    485 	if (addr_port[0] == '[') {
    486 		/* [<ipv6>]:<port> */
    487 		/* Accept [<ipv4>]:<port> too, but it's not a big deal. */
    488 		char *bracket, *colon;
    489 		if (strlen(addr_port) < strlen("[::]:0"))
    490 			errx(EXIT_FAILURE, "invalid endpoint format");
    491 		addr = addr_port + 1;
    492 		bracket = strchr(addr, ']');
    493 		if (bracket == NULL)
    494 			errx(EXIT_FAILURE, "invalid endpoint format");
    495 		*bracket = '\0';
    496 		colon = bracket + 1;
    497 		if (*colon != ':')
    498 			errx(EXIT_FAILURE, "invalid endpoint format");
    499 		*colon = '\0';
    500 		port = colon + 1;
    501 	} else {
    502 		char *colon, *tmp;
    503 		colon = strchr(addr_port, ':');
    504 		if (colon == NULL)
    505 			errx(EXIT_FAILURE, "no ':' found in endpoint");
    506 		tmp = strchr(colon + 1, ':');
    507 		if (tmp != NULL) {
    508 			/* <ipv6>:<port> */
    509 			/* Assume the last colon is a separator */
    510 			char *last_colon = tmp;
    511 			while ((tmp = strchr(tmp + 1, ':')) != NULL)
    512 				last_colon = tmp;
    513 			colon = last_colon;
    514 			*colon = '\0';
    515 			port = colon + 1;
    516 		} else {
    517 			/* <ipv4>:<port> */
    518 			*colon = '\0';
    519 			port = colon + 1;
    520 		}
    521 	}
    522 
    523 	memset(&hints, 0, sizeof(hints));
    524 	hints.ai_family = AF_UNSPEC;
    525 	hints.ai_flags = AI_NUMERICHOST;
    526 	error = getaddrinfo(addr, port, &hints, &res);
    527 	if (error != 0)
    528 		err(EXIT_FAILURE, "getaddrinfo");
    529 
    530 	prop_addr = prop_data_create_data(res->ai_addr, res->ai_addrlen);
    531 	prop_dictionary_set(prop_dict, "endpoint", prop_addr);
    532 	prop_object_release(prop_addr);
    533 
    534 	freeaddrinfo(res);
    535 	free(addr_port);
    536 }
    537 
    538 static void
    539 handle_option_allowed_ips(const char *_allowed_ips, prop_dictionary_t prop_dict)
    540 {
    541 	prop_array_t allowedips;
    542 	int i;
    543 	char *allowed_ips, *ip;
    544 
    545 	allowed_ips = strdup(_allowed_ips);
    546 
    547 	allowedips = prop_array_create();
    548 	i = 0;
    549 	while ((ip = strsep(&allowed_ips, ",")) != NULL) {
    550 		prop_dictionary_t prop_allowedip;
    551 		prop_allowedip = prop_dictionary_create();
    552 		uint16_t cidr;
    553 		char *cidrp;
    554 		struct addrinfo hints, *res;
    555 		int error;
    556 
    557 		cidrp = strchr(ip, '/');
    558 		if (cidrp == NULL)
    559 			errx(EXIT_FAILURE, "no '/' found in allowed-ip");
    560 		*cidrp = '\0';
    561 		cidrp++;
    562 
    563 		cidr = strtouint16(cidrp);
    564 
    565 		memset(&hints, 0, sizeof(hints));
    566 		hints.ai_family = AF_UNSPEC;
    567 		hints.ai_flags = AI_NUMERICHOST;
    568 		error = getaddrinfo(ip, 0, &hints, &res);
    569 		if (error != 0)
    570 			err(EXIT_FAILURE, "getaddrinfo");
    571 
    572 		sa_family_t family = res->ai_addr->sa_family;
    573 		prop_number_t prop_family = prop_number_create_unsigned_integer(family);
    574 		prop_dictionary_set(prop_allowedip, "family", prop_family);
    575 		prop_object_release(prop_family);
    576 
    577 		prop_data_t addr;
    578 		if (family == AF_INET) {
    579 			struct sockaddr_in *sin = (struct sockaddr_in *)res->ai_addr;
    580 			addr = prop_data_create_data(&sin->sin_addr, sizeof(sin->sin_addr));
    581 		} else if (family == AF_INET6) {
    582 			struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr;
    583 			addr = prop_data_create_data(&sin6->sin6_addr, sizeof(sin6->sin6_addr));
    584 		} else
    585 			errx(EXIT_FAILURE, "invalid family: %d", family);
    586 		prop_dictionary_set(prop_allowedip, "ip", addr);
    587 		prop_object_release(addr);
    588 
    589 		prop_number_t prop_cidr = prop_number_create_unsigned_integer(cidr);
    590 		prop_dictionary_set(prop_allowedip, "cidr", prop_cidr);
    591 		prop_object_release(prop_cidr);
    592 
    593 		freeaddrinfo(res);
    594 		prop_array_set(allowedips, i, prop_allowedip);
    595 		i++;
    596 	}
    597 	prop_dictionary_set(prop_dict, "allowedips", allowedips);
    598 
    599 	free(allowed_ips);
    600 }
    601 
    602 static void
    603 handle_option_preshared_key(const char *path, prop_dictionary_t prop_dict)
    604 {
    605 	unsigned char keybuf[KEY_LEN];
    606 	prop_data_t psk;
    607 
    608 	read_key(path, keybuf);
    609 	psk = prop_data_create_data(keybuf, sizeof(keybuf));
    610 	prop_dictionary_set(prop_dict, "preshared_key", psk);
    611 	prop_object_release(psk);
    612 }
    613 
    614 static const struct option {
    615 	const char	*option;
    616 	void		(*func)(const char *, prop_dictionary_t);
    617 } options[] = {
    618 	{"--endpoint=",		handle_option_endpoint},
    619 	{"--allowed-ips=",	handle_option_allowed_ips},
    620 	{"--preshared-key=",	handle_option_preshared_key},
    621 };
    622 
    623 static void
    624 handle_options(int argc, char *argv[], prop_dictionary_t prop_dict)
    625 {
    626 
    627 	while (argc > 0) {
    628 		for (size_t i = 0; i < __arraycount(options); i++) {
    629 			const struct option *opt = &options[i];
    630 			size_t optlen = strlen(opt->option);
    631 			if (strncmp(argv[0], opt->option, optlen) == 0) {
    632 				opt->func(argv[0] + optlen, prop_dict);
    633 				break;
    634 			}
    635 		}
    636 		argc -= 1;
    637 		argv += 1;
    638 	}
    639 
    640 	if (argc != 0)
    641 		usage();
    642 }
    643 
    644 static int
    645 cmd_add_peer(const char *interface, int argc, char *argv[])
    646 {
    647 	const char *name;
    648 	unsigned char keybuf[KEY_LEN];
    649 
    650 	if (argc < 2)
    651 		usage();
    652 
    653 	prop_dictionary_t prop_dict;
    654 	prop_dict = prop_dictionary_create();
    655 
    656 	name = argv[0];
    657 	if (strlen(name) > WG_PEER_NAME_MAXLEN)
    658 		errx(EXIT_FAILURE, "peer name too long");
    659 	if (strnlen(argv[1], KEY_BASE64_LEN + 1) != KEY_BASE64_LEN)
    660 		errx(EXIT_FAILURE, "invalid public-key length: %lu", strlen(argv[1]));
    661 	base64_decode(argv[1], keybuf);
    662 
    663 	prop_string_t prop_name = prop_string_create_cstring(name);
    664 	prop_dictionary_set(prop_dict, "name", prop_name);
    665 	prop_object_release(prop_name);
    666 
    667 	prop_data_t pubkey = prop_data_create_data(keybuf, sizeof(keybuf));
    668 	prop_dictionary_set(prop_dict, "public_key", pubkey);
    669 	prop_object_release(pubkey);
    670 
    671 	argc -= 2;
    672 	argv += 2;
    673 
    674 	handle_options(argc, argv, prop_dict);
    675 
    676 	char *buf = prop_dictionary_externalize(prop_dict);
    677 	if (buf == NULL)
    678 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
    679 	ioctl_set(interface, WG_IOCTL_ADD_PEER, buf);
    680 
    681 	return EXIT_SUCCESS;
    682 }
    683 
    684 static int
    685 cmd_delete_peer(const char *interface, int argc, char *argv[])
    686 {
    687 	const char *name;
    688 
    689 	if (argc != 1)
    690 		usage();
    691 
    692 	prop_dictionary_t prop_dict;
    693 	prop_dict = prop_dictionary_create();
    694 
    695 	name = argv[0];
    696 	if (strlen(name) > WG_PEER_NAME_MAXLEN)
    697 		errx(EXIT_FAILURE, "peer name too long");
    698 
    699 	prop_string_t prop_name = prop_string_create_cstring(name);
    700 	prop_dictionary_set(prop_dict, "name", prop_name);
    701 	prop_object_release(prop_name);
    702 
    703 	char *buf = prop_dictionary_externalize(prop_dict);
    704 	if (buf == NULL)
    705 		err(EXIT_FAILURE, "prop_dictionary_externalize failed");
    706 	ioctl_set(interface, WG_IOCTL_DELETE_PEER, buf);
    707 
    708 	return EXIT_SUCCESS;
    709 }
    710 
    711 static const struct command {
    712 	const char	*command;
    713 	const char	*target;
    714 	int		(*func)(const char *, int, char **);
    715 } commands[] = {
    716 	{"show",	"all",		cmd_show_all},
    717 	{"show",	"peer",		cmd_show_peer},
    718 	{"show",	"private-key",	cmd_show_private_key},
    719 	{"set",		"private-key",	cmd_set_private_key},
    720 	{"set",		"listen-port",	cmd_set_listen_port},
    721 	{"add",		"peer",		cmd_add_peer},
    722 	{"delete",	"peer",		cmd_delete_peer},
    723 };
    724 
    725 int
    726 main(int argc, char *argv[])
    727 {
    728 	const char *interface;
    729 	const char *command;
    730 	const char *target;
    731 
    732 	if (argc < 2) {
    733 		usage();
    734 	}
    735 
    736 	interface = argv[1];
    737 	if (strlen(interface) > IFNAMSIZ)
    738 		errx(EXIT_FAILURE, "interface name too long");
    739 	if (argc == 2) {
    740 		return cmd_show_all(interface, 0, NULL);
    741 	}
    742 	if (argc < 4) {
    743 		usage();
    744 	}
    745 	command = argv[2];
    746 	target = argv[3];
    747 
    748 	argc -= 4;
    749 	argv += 4;
    750 
    751 	for (size_t i = 0; i < __arraycount(commands); i++) {
    752 		const struct command *cmd = &commands[i];
    753 		if (strncmp(command, cmd->command, strlen(cmd->command)) == 0 &&
    754 		    strncmp(target, cmd->target, strlen(cmd->target)) == 0) {
    755 			return cmd->func(interface, argc, argv);
    756 		}
    757 	}
    758 
    759 	usage();
    760 }
    761