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 <stddef.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include "containers.h"
12#include "config.h"
13#include "ipc.h"
14#include "subcommands.h"
15
16struct pubkey_origin {
17	uint8_t *pubkey;
18	bool from_file;
19};
20
21static int pubkey_cmp(const void *first, const void *second)
22{
23	const struct pubkey_origin *a = first, *b = second;
24	int ret = memcmp(a->pubkey, b->pubkey, WG_KEY_LEN);
25	if (ret)
26		return ret;
27	return a->from_file - b->from_file;
28}
29
30static bool sync_conf(struct wgdevice *file)
31{
32	struct wgdevice *runtime;
33	struct wgpeer *peer;
34	struct pubkey_origin *pubkeys;
35	size_t peer_count = 0, i = 0;
36
37	if (!file->first_peer)
38		return true;
39
40	for_each_wgpeer(file, peer)
41		++peer_count;
42
43	if (ipc_get_device(&runtime, file->name) != 0) {
44		perror("Unable to retrieve current interface configuration");
45		return false;
46	}
47
48	if (!runtime->first_peer) {
49		free_wgdevice(runtime);
50		return true;
51	}
52
53	file->flags &= ~WGDEVICE_REPLACE_PEERS;
54
55	for_each_wgpeer(runtime, peer)
56		++peer_count;
57
58	pubkeys = calloc(peer_count, sizeof(*pubkeys));
59	if (!pubkeys) {
60		free_wgdevice(runtime);
61		perror("Public key allocation");
62		return false;
63	}
64
65	for_each_wgpeer(file, peer) {
66		pubkeys[i].pubkey = peer->public_key;
67		pubkeys[i].from_file = true;
68		++i;
69	}
70	for_each_wgpeer(runtime, peer) {
71		pubkeys[i].pubkey = peer->public_key;
72		pubkeys[i].from_file = false;
73		++i;
74	}
75	qsort(pubkeys, peer_count, sizeof(*pubkeys), pubkey_cmp);
76
77	for (i = 0; i < peer_count; ++i) {
78		if (pubkeys[i].from_file)
79			continue;
80		if (i == peer_count - 1 || !pubkeys[i + 1].from_file || memcmp(pubkeys[i].pubkey, pubkeys[i + 1].pubkey, WG_KEY_LEN)) {
81			peer = calloc(1, sizeof(struct wgpeer));
82			if (!peer) {
83				free_wgdevice(runtime);
84				free(pubkeys);
85				perror("Peer allocation");
86				return false;
87			}
88			peer->flags = WGPEER_REMOVE_ME;
89			memcpy(peer->public_key, pubkeys[i].pubkey, WG_KEY_LEN);
90			peer->next_peer = file->first_peer;
91			file->first_peer = peer;
92			if (!file->last_peer)
93				file->last_peer = peer;
94		}
95	}
96	free_wgdevice(runtime);
97	free(pubkeys);
98	return true;
99}
100
101int setconf_main(int argc, const char *argv[])
102{
103	struct wgdevice *device = NULL;
104	struct config_ctx ctx;
105	FILE *config_input = NULL;
106	char *config_buffer = NULL;
107	size_t config_buffer_len = 0;
108	int ret = 1;
109
110	if (argc != 3) {
111		fprintf(stderr, "Usage: %s %s <interface> <configuration filename>\n", PROG_NAME, argv[0]);
112		return 1;
113	}
114
115	config_input = fopen(argv[2], "r");
116	if (!config_input) {
117		perror("fopen");
118		return 1;
119	}
120	if (!config_read_init(&ctx, !strcmp(argv[0], "addconf"))) {
121		fclose(config_input);
122		return 1;
123	}
124	while (getline(&config_buffer, &config_buffer_len, config_input) >= 0) {
125		if (!config_read_line(&ctx, config_buffer)) {
126			fprintf(stderr, "Configuration parsing error\n");
127			goto cleanup;
128		}
129	}
130	device = config_read_finish(&ctx);
131	if (!device) {
132		fprintf(stderr, "Invalid configuration\n");
133		goto cleanup;
134	}
135	strncpy(device->name, argv[1], IFNAMSIZ - 1);
136	device->name[IFNAMSIZ - 1] = '\0';
137
138	if (!strcmp(argv[0], "syncconf")) {
139		if (!sync_conf(device))
140			goto cleanup;
141	}
142
143	if (ipc_set_device(device) != 0) {
144		perror("Unable to modify interface");
145		goto cleanup;
146	}
147
148	ret = 0;
149
150cleanup:
151	if (config_input)
152		fclose(config_input);
153	free(config_buffer);
154	free_wgdevice(device);
155	return ret;
156}
157