util  Diff

Differences From Artifact [dbf75c60c6]:

To Artifact [40df359611]:


            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   }