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