Differences From
Artifact [dbf75c60c6]:
1 +#include "def.h"
2 +#include "pqp.h"
3 +
4 +/* libc */
5 +#include <stdlib.h>
6 +#include <stdio.h>
7 +#include <string.h>
8 +
9 +/* posix */
10 +#include <netinet/in.h>
11 +#include <unistd.h>
12 +#include <sys/socket.h>
13 +#include <netdb.h>
14 +
15 +/* libs */
16 +#include <wireguard.h>
17 +
1 18 #include <libpq-fe.h>
2 19
20 +
21 +size_t dumpEndpoint(char* d, const wg_endpoint* const e) {
22 + const struct sockaddr* addr;
23 + size_t len;
24 + switch(e->addr.sa_family) {
25 + case AF_INET: addr = (void*)&(e->addr4); len = sizeof e->addr4; break;
26 + case AF_INET6: addr = (void*)&(e->addr6); len = sizeof e->addr6; break;
27 + case 0: strcpy(d, "<endpoint unset>"); return 16;
28 + default: strcpy(d, "<bad endpoint>"); return 14;
29 + }
30 + char bip[256], bsrv[16];
31 + getnameinfo(addr, len,
32 + bip, sizeof bip,
33 + bsrv, sizeof bsrv,
34 + NI_NUMERICHOST | NI_NUMERICSERV);
35 + return sprintf(d, "%s:%s", bip, bsrv);
36 +}
37 +
38 +size_t dumpAllowedIP(char* d, const wg_allowedip* aip) {
39 + union {
40 + struct sockaddr_in ip4;
41 + struct sockaddr_in6 ip6;
42 + } kinds;
43 + size_t len;
44 + switch(aip->family) {
45 + case AF_INET: {
46 + kinds.ip4 = (struct sockaddr_in) {
47 + .sin_family = AF_INET,
48 + .sin_port = 0,
49 + .sin_addr = aip->ip4,
50 + };
51 + len = sizeof kinds.ip4;
52 + break;}
53 + case AF_INET6: {
54 + kinds.ip6 = (struct sockaddr_in6) {
55 + .sin6_family = AF_INET6,
56 + .sin6_port = 0,
57 + .sin6_flowinfo = 0,
58 + .sin6_scope_id = 0,
59 + .sin6_addr = aip->ip6,
60 + };
61 + len = sizeof kinds.ip6;
62 + break;}
63 + case 0: strcpy(d, "<no IP>"); return 7;
64 + default: strcpy(d, "<non-IP address>"); return 16;
65 + }
66 + char bip[256], bsrv[2];
67 + getnameinfo((void*)&kinds, len,
68 + bip, sizeof bip,
69 + bsrv, sizeof bsrv,
70 + NI_NUMERICHOST | NI_NUMERICSERV);
71 + return sprintf(d, "%s/%u", bip, aip->cidr);
72 +}
73 +
74 +bool compare_allowedip
75 +( const wg_allowedip* const a,
76 + const wg_allowedip* const b
77 +) {
78 + if(a -> family != b -> family) return false;
79 + if(a -> cidr != b -> cidr) return false;
80 + switch(a->family) {
81 + case AF_INET:
82 + if(a -> ip4.s_addr != b -> ip4.s_addr) return false;
83 + break;
84 + case AF_INET6:
85 + if(memcmp(a -> ip6.s6_addr, b -> ip6.s6_addr, sizeof(a->ip6.s6_addr)) != 0)
86 + return false;
87 + break;
88 + }
89 + return true;
90 +}
91 +
92 +wg_allowedip
93 +inet_to_allowedip(const char* data) {
94 + pqp_sockstore ss;
95 + if(!pqp_inet_read(data, &ss.sock))
96 + _fatal("bad IP value in database");
97 +
98 + wg_allowedip wgip = {
99 + .family = ss.sock.sa_family,
100 + .next_allowedip = null,
101 + };
102 + switch(ss.sock.sa_family) {
103 + case AF_INET:
104 + wgip.cidr = 32;
105 + wgip.ip4 = ss.sock_in.sin_addr;
106 + break;
107 + case AF_INET6:
108 + wgip.cidr = 128;
109 + wgip.ip6 = ss.sock_in6.sin6_addr;
110 + break;
111 + default: _fatal("unhandled address family");
112 + }
113 + return wgip;
114 +}
115 +
116 +void wgd_free_peer(wg_peer* peer) {
117 + wg_allowedip *allowedip, *na;
118 + /* from ext/wglib/wireguard.c:1486 */
119 + for (
120 + allowedip = peer->first_allowedip,
121 + na = allowedip ? allowedip->next_allowedip : NULL;
122 + allowedip;
123 + allowedip = na,
124 + na = allowedip ? allowedip->next_allowedip : NULL
125 + ) free(allowedip);
126 + /* end import */
127 + free(peer);
128 +}
129 +
130 +/* linked list manipulation routines */
131 +
132 +#define _ll_rec peer
133 +#define _ll_box wg_device
134 +#define _ll_obj wg_peer
135 +#define _ll_iter wg_for_each_peer
136 +#define _ll_ns wgd
137 +#include "list.h"
138 +
139 +#define _ll_rec allowedip
140 +#define _ll_box wg_peer
141 +#define _ll_obj wg_allowedip
142 +#define _ll_iter wg_for_each_allowedip
143 +#define _ll_ns wgd_peer
144 +#include "list.h"
145 +
146 +#if 0
147 +void wgd_drop_peer(wg_device* dev, wg_peer* peer) {
148 + if(dev -> first_peer == peer) {
149 + if(dev -> last_peer == peer) {
150 + dev -> first_peer = dev -> last_peer = null;
151 + } else {
152 + dev -> first_peer = peer -> next_peer;
153 + }
154 + } else {
155 + wg_peer* p;
156 + if(dev -> last_peer == peer) {
157 + wg_for_each_peer(dev, p) {
158 + if(p->next_peer == peer) {
159 + dev -> last_peer = p;
160 + p->next_peer = null;
161 + goto found1;
162 + }
163 + }
164 + _fatal("BUG in last peer deletion routine");
165 + found1 :;
166 + } else /* in the middle */ {
167 + wg_for_each_peer(dev, p) {
168 + if(p->next_peer == peer) {
169 + p->next_peer = peer -> next_peer;
170 + goto found2;
171 + }
172 + }
173 + _fatal("BUG in peer deletion routine");
174 + found2 :;
175 + }
176 + }
177 + wgd_free_peer(peer);
178 +}
179 +void wgd_peer_drop_ip(wg_peer* peer, wg_allowedip* ip) {
180 + if(peer -> first_allowedip == ip) {
181 + if(peer -> last_allowedip == ip) {
182 + peer -> first_allowedip = peer -> last_allowedip = null;
183 + } else {
184 + peer -> first_allowedip = peer -> next_allowedip;
185 + }
186 + } else {
187 + wg_allowedip* a;
188 + if(peer -> last_allowedip == ip) {
189 + wg_for_each_allowedip(peer, a) {
190 + if(a->next_allowedip == ip) {
191 + peer -> last_allowedip = a;
192 + a->next_allowedip = null;
193 + goto found1;
194 + }
195 + }
196 + _fatal("BUG in last aIP deletion routine");
197 + found1 :;
198 + } else /* in the middle */ {
199 + wg_for_each_allowedip(peer, a) {
200 + if(a->next_allowedip == ip) {
201 + a->next_allowedip = ip -> next_allowedip;
202 + goto found2;
203 + }
204 + }
205 + _fatal("BUG in aIP deletion routine");
206 + found2 :;
207 + }
208 + }
209 + free(ip);
210 +}
211 +#endif
212 +
213 +void syncauth(PGconn* db, const char* wgdev) {
214 + wg_device* wg;
215 + if (wg_get_device(&wg, wgdev))
216 + _fatal("no wireguard device by that name");
217 +
218 + bool dirty = false;
219 + size_t peerc = 0;
220 + { wg_peer* p; wg_for_each_peer(wg, p) ++ peerc; };
221 +
222 + bool valid_peers [peerc];
223 + _zero(valid_peers);
224 +
225 + PGresult* rows = PQexecPrepared(db, "get_hosts",
226 + 0, null, null, null, 1);
227 + if(!(rows && PQresultStatus(rows) == PGRES_TUPLES_OK))
228 + _fatal(PQerrorMessage(db));
229 +
230 + size_t rowc = PQntuples(rows);
231 + for(size_t i = 0; i < rowc; ++i) {
232 + const char* key_b64 = PQgetvalue(rows, i, 0);
233 +
234 + const char* aryraw = PQgetvalue(rows, i, 1);
235 + pqp_array* ips = pqp_array_read(aryraw);
236 + _dbgf("DB has peer %s", key_b64);
237 + if(ips->ty != pq_array_inet)
238 + _fatal("incorrect array type returned from DB");
239 +
240 + wg_key key;
241 + if (wg_key_from_base64(key, key_b64) < 0) {
242 + _warnf("invalid key in database: %s", key_b64);
243 + continue;
244 + }
245 +
246 + wg_peer* found = null;
247 + { size_t j=0; wg_peer* p; wg_for_each_peer(wg, p) {
248 + if(memcmp(p->public_key, key, sizeof key) == 0) {
249 + _dbgf("validating peer %s", key_b64);
250 + valid_peers[j] = true;
251 + found = p;
252 + break;
253 + }
254 + ++j;}}
255 +
256 + if (found) {
257 + /* compare and update IPs if necessary */
258 + bool goodIPs [ips -> sz]; _zero(goodIPs);
259 + /* extant IPs that are not marked good by the
260 + * end of the following loop must be deleted
261 + * from memory */
262 + size_t goodIPc = 0;
263 + for (size_t j = 0; j < ips -> sz; ++j) {
264 + char inetstr[256];
265 + wg_allowedip aip = inet_to_allowedip(ips -> elts[j].data);
266 + dumpAllowedIP(inetstr, &aip);
267 + _dbgf("IP PG%zu :: %s", j, inetstr);
268 +
269 + size_t l = 0;
270 + wg_allowedip* wgip;
271 + bool foundIP = false;
272 + wg_for_each_allowedip(found, wgip) {
273 + if (compare_allowedip(&aip, wgip)) {
274 + ++goodIPc; goodIPs[l] = true;
275 + foundIP = true;
276 + }
277 + ++l;}
278 +
279 + if(!foundIP) {
280 + /* this IP hasn't been loaded into the
281 + * kernel yet; upload it now */
282 + _infof("inserting IP PG%zu %s", j, inetstr);
283 + dirty = true;
284 + }
285 + }
286 +
287 + if(goodIPc < ips -> sz) {
288 + size_t l = 0;
289 + wg_allowedip* wgip;
290 + wg_for_each_allowedip(found, wgip) {
291 + char inetstr[256];
292 + dumpAllowedIP(inetstr, wgip);
293 + _dbgf("IP WG%zu :: %s", l, inetstr);
294 + if(!goodIPs[l]) {
295 + /* this IP is stale, delete it */
296 + _infof("deleting IP WG%zu %s", l, inetstr);
297 + dirty = true;
298 + }
299 + ++l;}
300 + }
301 + } else {
302 + _infof("inserting key %s", key_b64);
303 + dirty = true;
304 + /* install new peer */
305 + for (size_t j = 0; j < ips -> sz; ++j) {
306 + char inetstr[256];
307 + wg_allowedip aip = inet_to_allowedip(ips -> elts[j].data);
308 + dumpAllowedIP(inetstr, &aip);
309 + _dbgf("new IP %zu :: %s", j, inetstr);
310 + }
311 + }
312 +
313 + free(ips);
314 + }
315 + { size_t i=0; wg_peer* p; wg_for_each_peer(wg, p) {
316 + if(valid_peers[i] == false) {
317 + char b64 [128];
318 + wg_key_to_base64(b64, p->public_key);
319 + _infof("dropping peer %s", b64);
320 + wgd_drop_peer(wg, p);
321 + dirty = true;
322 + }
323 + ++i;}}
324 +
325 + _dbg("final peer list:");
326 + { size_t j=0; wg_peer* p; wg_for_each_peer(wg, p) {
327 + char b64 [128];
328 + wg_key_to_base64(b64, p->public_key);
329 + _dbgf("P%zu :: %s", j, b64);
330 + ++j;}}
331 +
332 + if(dirty) wg_set_device(wg);
333 +
334 + PQclear(rows);
335 +}
336 +
3 337 int main(int argc, char** argv) {
338 + setvbuf(stderr, null, _IONBF, 0);
339 + if (argc < 3) {
340 + _fatal("missing device name");
341 + }
342 +
343 + const char* arg_mode = argv[1];
344 + const char* arg_devname = argv[2];
345 +
346 + /* mostly for the sake of debugging, allow the
347 + * binary to be run from sudo without losing
348 + * postgres peer credentials */
349 + if(geteuid() == 0) {
350 + char* suid = getenv("SUDO_UID");
351 + char* susr = getenv("SUDO_USER");
352 + if(suid) seteuid(atoi(suid));
353 + if(susr) setenv("USER",getenv("SUDO_USER"), 1);
354 + }
355 +
356 + PGconn* db = PQconnectdb("dbname=domain");
357 + if(PQstatus(db) != CONNECTION_OK)
358 + _fatal(PQerrorMessage(db));
359 +
360 + PGresult* q_get_hosts = PQprepare(db, "get_hosts",
361 + "select h.ref, array_remove(array_agg(wgv4::inet)"
362 + "|| array_agg(wgv6::inet), null)"
363 + "from ns, hostref h "
364 + "where ns.host = h.host and kind = 'pubkey' "
365 + " group by h.host, h.ref;", 0, null);
366 + /*"select ns.wgv4::inet, ns.wgv6::inet, h.ref from ns "
367 + "right join hostref h "
368 + "on h.host = ns.host "
369 + "where h.kind = 'pubkey';"*/
370 + if(!(q_get_hosts && PQresultStatus(q_get_hosts) == PGRES_COMMAND_OK))
371 + _fatal(PQerrorMessage(db));
372 + PQclear(q_get_hosts);
373 +
374 + /* we're going to interact with WG now;
375 + * get our superpowers back if we lost them */
376 + {uid_t svuid;
377 + getresuid(null, null, &svuid);
378 + if (svuid == 0) setuid(0);}
379 +
380 + if(strcmp(arg_mode, "sync") == 0) {
381 + syncauth(db, arg_devname);
382 + } else if(strcmp(arg_mode, "wait") == 0) {
383 + /* foreground daemon */
384 + } else if(strcmp(arg_mode, "fork") == 0) {
385 + /* background daemon */
386 + } else {
387 + _fatal("valid modes are sync, wait, and fork");
388 + }
389 + /* other possibilities: a mode that generates an eventfd
390 + * and provides it on fd4 to a subordinate process, or
391 + * sends it with SCM_RIGHTS */
392 +
393 + PQfinish(db);
4 394 return 0;
5 395 }