1// SPDX-License-Identifier: GPL-2.0 OR MIT
2/*
3 * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
4 */
5
6#include <arpa/inet.h>
7#include <inttypes.h>
8#include <netinet/in.h>
9#include <sys/socket.h>
10#include <net/if.h>
11#include <stdbool.h>
12#include <stddef.h>
13#include <stdint.h>
14#include <stdlib.h>
15#include <stdio.h>
16#include <string.h>
17#include <errno.h>
18#include <time.h>
19#include <netdb.h>
20
21#include "containers.h"
22#include "ipc.h"
23#include "terminal.h"
24#include "encoding.h"
25#include "subcommands.h"
26
27static int peer_cmp(const void *first, const void *second)
28{
29	time_t diff;
30	const struct wgpeer *a = *(void *const *)first, *b = *(void *const *)second;
31
32	if (!a->last_handshake_time.tv_sec && !a->last_handshake_time.tv_nsec && (b->last_handshake_time.tv_sec || b->last_handshake_time.tv_nsec))
33		return 1;
34	if (!b->last_handshake_time.tv_sec && !b->last_handshake_time.tv_nsec && (a->last_handshake_time.tv_sec || a->last_handshake_time.tv_nsec))
35		return -1;
36	diff = a->last_handshake_time.tv_sec - b->last_handshake_time.tv_sec;
37	if (!diff)
38		diff = a->last_handshake_time.tv_nsec - b->last_handshake_time.tv_nsec;
39	if (diff < 0)
40		return 1;
41	if (diff > 0)
42		return -1;
43	return 0;
44}
45
46/* This, hilariously, is not the right way to sort a linked list... */
47static void sort_peers(struct wgdevice *device)
48{
49	size_t peer_count = 0, i = 0;
50	struct wgpeer *peer, **peers;
51
52	for_each_wgpeer(device, peer)
53		++peer_count;
54	if (!peer_count)
55		return;
56	peers = calloc(peer_count, sizeof(*peers));
57	if (!peers)
58		return;
59	for_each_wgpeer(device, peer)
60		peers[i++] = peer;
61	qsort(peers, peer_count, sizeof(*peers), peer_cmp);
62	device->first_peer = peers[0];
63	for (i = 1; i < peer_count; ++i) {
64		peers[i - 1]->next_peer = peers[i];
65	}
66	peers[peer_count - 1]->next_peer = NULL;
67	free(peers);
68}
69
70static char *key(const uint8_t key[static WG_KEY_LEN])
71{
72	static char base64[WG_KEY_LEN_BASE64];
73
74	key_to_base64(base64, key);
75	return base64;
76}
77
78static const char *maybe_key(const uint8_t maybe_key[static WG_KEY_LEN], bool have_it)
79{
80	if (!have_it)
81		return "(none)";
82	return key(maybe_key);
83}
84
85static const char *masked_key(const uint8_t masked_key[static WG_KEY_LEN])
86{
87	const char *var = getenv("WG_HIDE_KEYS");
88
89	if (var && !strcmp(var, "never"))
90		return key(masked_key);
91	return "(hidden)";
92}
93
94static char *ip(const struct wgallowedip *ip)
95{
96	static char buf[INET6_ADDRSTRLEN + 1];
97
98	memset(buf, 0, INET6_ADDRSTRLEN + 1);
99	if (ip->family == AF_INET)
100		inet_ntop(AF_INET, &ip->ip4, buf, INET6_ADDRSTRLEN);
101	else if (ip->family == AF_INET6)
102		inet_ntop(AF_INET6, &ip->ip6, buf, INET6_ADDRSTRLEN);
103	return buf;
104}
105
106static char *endpoint(const struct sockaddr *addr)
107{
108	char host[4096 + 1];
109	char service[512 + 1];
110	static char buf[sizeof(host) + sizeof(service) + 4];
111	int ret;
112	socklen_t addr_len = 0;
113
114	memset(buf, 0, sizeof(buf));
115	if (addr->sa_family == AF_INET)
116		addr_len = sizeof(struct sockaddr_in);
117	else if (addr->sa_family == AF_INET6)
118		addr_len = sizeof(struct sockaddr_in6);
119
120	ret = getnameinfo(addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST);
121	if (ret) {
122		strncpy(buf, gai_strerror(ret), sizeof(buf) - 1);
123		buf[sizeof(buf) - 1] = '\0';
124	} else
125		snprintf(buf, sizeof(buf), (addr->sa_family == AF_INET6 && strchr(host, ':')) ? "[%s]:%s" : "%s:%s", host, service);
126	return buf;
127}
128
129static size_t pretty_time(char *buf, const size_t len, unsigned long long left)
130{
131	size_t offset = 0;
132	unsigned long long years, days, hours, minutes, seconds;
133
134	years = left / (365 * 24 * 60 * 60);
135	left = left % (365 * 24 * 60 * 60);
136	days = left / (24 * 60 * 60);
137	left = left % (24 * 60 * 60);
138	hours = left / (60 * 60);
139	left = left % (60 * 60);
140	minutes = left / 60;
141	seconds = left % 60;
142
143	if (years)
144		offset += snprintf(buf + offset, len - offset, "%s%llu " TERMINAL_FG_CYAN "year%s" TERMINAL_RESET, offset ? ", " : "", years, years == 1 ? "" : "s");
145	if (days)
146		offset += snprintf(buf + offset, len - offset, "%s%llu " TERMINAL_FG_CYAN  "day%s" TERMINAL_RESET, offset ? ", " : "", days, days == 1 ? "" : "s");
147	if (hours)
148		offset += snprintf(buf + offset, len - offset, "%s%llu " TERMINAL_FG_CYAN  "hour%s" TERMINAL_RESET, offset ? ", " : "", hours, hours == 1 ? "" : "s");
149	if (minutes)
150		offset += snprintf(buf + offset, len - offset, "%s%llu " TERMINAL_FG_CYAN "minute%s" TERMINAL_RESET, offset ? ", " : "", minutes, minutes == 1 ? "" : "s");
151	if (seconds)
152		offset += snprintf(buf + offset, len - offset, "%s%llu " TERMINAL_FG_CYAN  "second%s" TERMINAL_RESET, offset ? ", " : "", seconds, seconds == 1 ? "" : "s");
153
154	return offset;
155}
156
157static char *ago(const struct timespec64 *t)
158{
159	static char buf[1024];
160	size_t offset;
161	time_t now = time(NULL);
162
163	if (now == t->tv_sec)
164		strncpy(buf, "Now", sizeof(buf) - 1);
165	else if (now < t->tv_sec)
166		strncpy(buf, "(" TERMINAL_FG_RED "System clock wound backward; connection problems may ensue." TERMINAL_RESET ")", sizeof(buf) - 1);
167	else {
168		offset = pretty_time(buf, sizeof(buf), now - t->tv_sec);
169		strncpy(buf + offset, " ago", sizeof(buf) - offset - 1);
170	}
171	buf[sizeof(buf) - 1] = '\0';
172
173	return buf;
174}
175
176static char *every(uint16_t seconds)
177{
178	static char buf[1024] = "every ";
179
180	pretty_time(buf + strlen("every "), sizeof(buf) - strlen("every ") - 1, seconds);
181	return buf;
182}
183
184static char *bytes(uint64_t b)
185{
186	static char buf[1024];
187
188	if (b < 1024ULL)
189		snprintf(buf, sizeof(buf), "%u " TERMINAL_FG_CYAN "B" TERMINAL_RESET, (unsigned int)b);
190	else if (b < 1024ULL * 1024ULL)
191		snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "KiB" TERMINAL_RESET, (double)b / 1024);
192	else if (b < 1024ULL * 1024ULL * 1024ULL)
193		snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "MiB" TERMINAL_RESET, (double)b / (1024 * 1024));
194	else if (b < 1024ULL * 1024ULL * 1024ULL * 1024ULL)
195		snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "GiB" TERMINAL_RESET, (double)b / (1024 * 1024 * 1024));
196	else
197		snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "TiB" TERMINAL_RESET, (double)b / (1024 * 1024 * 1024) / 1024);
198
199	return buf;
200}
201
202static const char *COMMAND_NAME;
203static void show_usage(void)
204{
205	fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } [public-key | private-key | listen-port | fwmark | peers | preshared-keys | endpoints | allowed-ips | latest-handshakes | transfer | persistent-keepalive | dump]\n", PROG_NAME, COMMAND_NAME);
206}
207
208static void pretty_print(struct wgdevice *device)
209{
210	struct wgpeer *peer;
211	struct wgallowedip *allowedip;
212
213	terminal_printf(TERMINAL_RESET);
214	terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "interface" TERMINAL_RESET ": " TERMINAL_FG_GREEN "%s" TERMINAL_RESET "\n", device->name);
215	if (device->flags & WGDEVICE_HAS_PUBLIC_KEY)
216		terminal_printf("  " TERMINAL_BOLD "public key" TERMINAL_RESET ": %s\n", key(device->public_key));
217	if (device->flags & WGDEVICE_HAS_PRIVATE_KEY)
218		terminal_printf("  " TERMINAL_BOLD "private key" TERMINAL_RESET ": %s\n", masked_key(device->private_key));
219	if (device->listen_port)
220		terminal_printf("  " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->listen_port);
221	if (device->fwmark)
222		terminal_printf("  " TERMINAL_BOLD "fwmark" TERMINAL_RESET ": 0x%x\n", device->fwmark);
223	if (device->first_peer) {
224		sort_peers(device);
225		terminal_printf("\n");
226	}
227	for_each_wgpeer(device, peer) {
228		terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "peer" TERMINAL_RESET ": " TERMINAL_FG_YELLOW "%s" TERMINAL_RESET "\n", key(peer->public_key));
229		if (peer->flags & WGPEER_HAS_PRESHARED_KEY)
230			terminal_printf("  " TERMINAL_BOLD "preshared key" TERMINAL_RESET ": %s\n", masked_key(peer->preshared_key));
231		if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
232			terminal_printf("  " TERMINAL_BOLD "endpoint" TERMINAL_RESET ": %s\n", endpoint(&peer->endpoint.addr));
233		terminal_printf("  " TERMINAL_BOLD "allowed ips" TERMINAL_RESET ": ");
234		if (peer->first_allowedip) {
235			for_each_wgallowedip(peer, allowedip)
236				terminal_printf("%s" TERMINAL_FG_CYAN "/" TERMINAL_RESET "%u%s", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ", " : "\n");
237		} else
238			terminal_printf("(none)\n");
239		if (peer->last_handshake_time.tv_sec)
240			terminal_printf("  " TERMINAL_BOLD "latest handshake" TERMINAL_RESET ": %s\n", ago(&peer->last_handshake_time));
241		if (peer->rx_bytes || peer->tx_bytes) {
242			terminal_printf("  " TERMINAL_BOLD "transfer" TERMINAL_RESET ": ");
243			terminal_printf("%s received, ", bytes(peer->rx_bytes));
244			terminal_printf("%s sent\n", bytes(peer->tx_bytes));
245		}
246		if (peer->persistent_keepalive_interval)
247			terminal_printf("  " TERMINAL_BOLD "persistent keepalive" TERMINAL_RESET ": %s\n", every(peer->persistent_keepalive_interval));
248		if (peer->next_peer)
249			terminal_printf("\n");
250	}
251}
252
253static void dump_print(struct wgdevice *device, bool with_interface)
254{
255	struct wgpeer *peer;
256	struct wgallowedip *allowedip;
257
258	if (with_interface)
259		printf("%s\t", device->name);
260	printf("%s\t", maybe_key(device->private_key, device->flags & WGDEVICE_HAS_PRIVATE_KEY));
261	printf("%s\t", maybe_key(device->public_key, device->flags & WGDEVICE_HAS_PUBLIC_KEY));
262	printf("%u\t", device->listen_port);
263	if (device->fwmark)
264		printf("0x%x\n", device->fwmark);
265	else
266		printf("off\n");
267	for_each_wgpeer(device, peer) {
268		if (with_interface)
269			printf("%s\t", device->name);
270		printf("%s\t", key(peer->public_key));
271		printf("%s\t", maybe_key(peer->preshared_key, peer->flags & WGPEER_HAS_PRESHARED_KEY));
272		if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
273			printf("%s\t", endpoint(&peer->endpoint.addr));
274		else
275			printf("(none)\t");
276		if (peer->first_allowedip) {
277			for_each_wgallowedip(peer, allowedip)
278				printf("%s/%u%c", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ',' : '\t');
279		} else
280			printf("(none)\t");
281		printf("%llu\t", (unsigned long long)peer->last_handshake_time.tv_sec);
282		printf("%" PRIu64 "\t%" PRIu64 "\t", (uint64_t)peer->rx_bytes, (uint64_t)peer->tx_bytes);
283		if (peer->persistent_keepalive_interval)
284			printf("%u\n", peer->persistent_keepalive_interval);
285		else
286			printf("off\n");
287	}
288}
289
290static bool ugly_print(struct wgdevice *device, const char *param, bool with_interface)
291{
292	struct wgpeer *peer;
293	struct wgallowedip *allowedip;
294
295	if (!strcmp(param, "public-key")) {
296		if (with_interface)
297			printf("%s\t", device->name);
298		printf("%s\n", maybe_key(device->public_key, device->flags & WGDEVICE_HAS_PUBLIC_KEY));
299	} else if (!strcmp(param, "private-key")) {
300		if (with_interface)
301			printf("%s\t", device->name);
302		printf("%s\n", maybe_key(device->private_key, device->flags & WGDEVICE_HAS_PRIVATE_KEY));
303	} else if (!strcmp(param, "listen-port")) {
304		if (with_interface)
305			printf("%s\t", device->name);
306		printf("%u\n", device->listen_port);
307	} else if (!strcmp(param, "fwmark")) {
308		if (with_interface)
309			printf("%s\t", device->name);
310		if (device->fwmark)
311			printf("0x%x\n", device->fwmark);
312		else
313			printf("off\n");
314	} else if (!strcmp(param, "endpoints")) {
315		if (with_interface)
316			printf("%s\t", device->name);
317		for_each_wgpeer(device, peer) {
318			printf("%s\t", key(peer->public_key));
319			if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
320				printf("%s\n", endpoint(&peer->endpoint.addr));
321			else
322				printf("(none)\n");
323		}
324	} else if (!strcmp(param, "allowed-ips")) {
325		for_each_wgpeer(device, peer) {
326			if (with_interface)
327				printf("%s\t", device->name);
328			printf("%s\t", key(peer->public_key));
329			if (peer->first_allowedip) {
330				for_each_wgallowedip(peer, allowedip)
331					printf("%s/%u%c", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ' ' : '\n');
332			} else
333				printf("(none)\n");
334		}
335	} else if (!strcmp(param, "latest-handshakes")) {
336		for_each_wgpeer(device, peer) {
337			if (with_interface)
338				printf("%s\t", device->name);
339			printf("%s\t%llu\n", key(peer->public_key), (unsigned long long)peer->last_handshake_time.tv_sec);
340		}
341	} else if (!strcmp(param, "transfer")) {
342		for_each_wgpeer(device, peer) {
343			if (with_interface)
344				printf("%s\t", device->name);
345			printf("%s\t%" PRIu64 "\t%" PRIu64 "\n", key(peer->public_key), (uint64_t)peer->rx_bytes, (uint64_t)peer->tx_bytes);
346		}
347	} else if (!strcmp(param, "persistent-keepalive")) {
348		for_each_wgpeer(device, peer) {
349			if (with_interface)
350				printf("%s\t", device->name);
351			if (peer->persistent_keepalive_interval)
352				printf("%s\t%u\n", key(peer->public_key), peer->persistent_keepalive_interval);
353			else
354				printf("%s\toff\n", key(peer->public_key));
355		}
356	} else if (!strcmp(param, "preshared-keys")) {
357		for_each_wgpeer(device, peer) {
358			if (with_interface)
359				printf("%s\t", device->name);
360			printf("%s\t", key(peer->public_key));
361			printf("%s\n", maybe_key(peer->preshared_key, peer->flags & WGPEER_HAS_PRESHARED_KEY));
362		}
363	} else if (!strcmp(param, "peers")) {
364		for_each_wgpeer(device, peer) {
365			if (with_interface)
366				printf("%s\t", device->name);
367			printf("%s\n", key(peer->public_key));
368		}
369	} else if (!strcmp(param, "dump"))
370		dump_print(device, with_interface);
371	else {
372		fprintf(stderr, "Invalid parameter: `%s'\n", param);
373		show_usage();
374		return false;
375	}
376	return true;
377}
378
379int show_main(int argc, const char *argv[])
380{
381	int ret = 0;
382
383	COMMAND_NAME = argv[0];
384
385	if (argc > 3) {
386		show_usage();
387		return 1;
388	}
389
390	if (argc == 1 || !strcmp(argv[1], "all")) {
391		char *interfaces = ipc_list_devices(), *interface;
392
393		if (!interfaces) {
394			perror("Unable to list interfaces");
395			return 1;
396		}
397		ret = !!*interfaces;
398		interface = interfaces;
399		for (size_t len = 0; (len = strlen(interface)); interface += len + 1) {
400			struct wgdevice *device = NULL;
401
402			if (ipc_get_device(&device, interface) < 0) {
403				fprintf(stderr, "Unable to access interface %s: %s\n", interface, strerror(errno));
404				continue;
405			}
406			if (argc == 3) {
407				if (!ugly_print(device, argv[2], true)) {
408					ret = 1;
409					free_wgdevice(device);
410					break;
411				}
412			} else {
413				pretty_print(device);
414				if (strlen(interface + len + 1))
415					printf("\n");
416			}
417			free_wgdevice(device);
418			ret = 0;
419		}
420		free(interfaces);
421	} else if (!strcmp(argv[1], "interfaces")) {
422		char *interfaces, *interface;
423
424		if (argc > 2) {
425			show_usage();
426			return 1;
427		}
428		interfaces = ipc_list_devices();
429		if (!interfaces) {
430			perror("Unable to list interfaces");
431			return 1;
432		}
433		interface = interfaces;
434		for (size_t len = 0; (len = strlen(interface)); interface += len + 1)
435			printf("%s%c", interface, strlen(interface + len + 1) ? ' ' : '\n');
436		free(interfaces);
437	} else if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "help")))
438		show_usage();
439	else {
440		struct wgdevice *device = NULL;
441
442		if (ipc_get_device(&device, argv[1]) < 0) {
443			perror("Unable to access interface");
444			return 1;
445		}
446		if (argc == 3) {
447			if (!ugly_print(device, argv[2], false))
448				ret = 1;
449		} else
450			pretty_print(device);
451		free_wgdevice(device);
452	}
453	return ret;
454}
455