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 59usage(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 79static const char * 80format_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 104static const char * 105format_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 128static void 129handle_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 205static prop_dictionary_t 206ioctl_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 240static void 241show_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 278static int 279cmd_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 329static int 330cmd_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 376static int 377cmd_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 390static void 391ioctl_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 408static void 409base64_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 419static void 420read_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 438static int 439cmd_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 465static uint16_t 466strtouint16(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 485static int 486cmd_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 513static void 514handle_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 576static void 577handle_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 658static void 659handle_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 669static 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 678static void 679handle_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 703static int 704cmd_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 744static int 745cmd_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 772static 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 786int 787main(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