aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/tools/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/config.c')
-rw-r--r--src/tools/config.c518
1 files changed, 518 insertions, 0 deletions
diff --git a/src/tools/config.c b/src/tools/config.c
new file mode 100644
index 0000000..0cec30e
--- /dev/null
+++ b/src/tools/config.c
@@ -0,0 +1,518 @@
+/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
+
+
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "config.h"
+#include "kernel.h"
+#include "base64.h"
+
+#define COMMENT_CHAR '#'
+
+#define max(a, b) (a > b ? a : b)
+
+static inline struct wgpeer *peer_from_offset(struct wgdevice *dev, size_t offset)
+{
+ return (struct wgpeer *)((uint8_t *)dev + sizeof(struct wgdevice) + offset);
+}
+
+static int use_space(struct inflatable_device *buf, size_t space)
+{
+ size_t expand_to;
+ uint8_t *new_dev;
+
+ if (buf->len - buf->pos < space) {
+ expand_to = max(buf->len * 2, buf->len + space);
+ new_dev = realloc(buf->dev, expand_to + sizeof(struct wgdevice));
+ if (!new_dev)
+ return -errno;
+ memset(&new_dev[buf->len + sizeof(struct wgdevice)], 0, expand_to - buf->len);
+ buf->dev = (struct wgdevice *)new_dev;
+ buf->len = expand_to;
+ }
+ buf->pos += space;
+ return 0;
+}
+
+static const char *get_value(const char *line, const char *key)
+{
+ size_t linelen = strlen(line);
+ size_t keylen = strlen(key);
+
+ if (keylen >= linelen)
+ return NULL;
+
+ if (strncasecmp(line, key, keylen))
+ return NULL;
+
+ return line + keylen;
+}
+
+static inline uint16_t parse_port(const char *value)
+{
+ int ret;
+ uint16_t port = 0;
+ struct addrinfo *resolved;
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP,
+ .ai_flags = AI_ADDRCONFIG | AI_PASSIVE
+ };
+
+ if (!strlen(value)) {
+ fprintf(stderr, "Unable to parse empty port\n");
+ return 0;
+ }
+
+ ret = getaddrinfo(NULL, value, &hints, &resolved);
+ if (ret != 0) {
+ fprintf(stderr, "%s: `%s`\n", gai_strerror(ret), value);
+ return 0;
+ }
+
+ if (resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in))
+ port = ntohs(((struct sockaddr_in *)resolved->ai_addr)->sin_port);
+ else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6))
+ port = ntohs(((struct sockaddr_in6 *)resolved->ai_addr)->sin6_port);
+ else
+ fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s`\n", value);
+
+ freeaddrinfo(resolved);
+ return port;
+}
+
+static inline bool parse_key(uint8_t key[WG_KEY_LEN], const char *value)
+{
+ uint8_t tmp[WG_KEY_LEN + 1];
+ if (strlen(value) != b64_len(WG_KEY_LEN) - 1) {
+ fprintf(stderr, "Key is not the correct length: `%s`\n", value);
+ return false;
+ }
+ if (b64_pton(value, tmp, WG_KEY_LEN + 1) < 0) {
+ fprintf(stderr, "Could not parse base64 key: `%s`\n", value);
+ return false;
+ }
+ memcpy(key, tmp, WG_KEY_LEN);
+ return true;
+}
+
+static inline bool parse_ip(struct wgipmask *ipmask, const char *value)
+{
+ ipmask->family = AF_UNSPEC;
+ if (strchr(value, ':')) {
+ if (inet_pton(AF_INET6, value, &ipmask->ip6) == 1)
+ ipmask->family = AF_INET6;
+ } else {
+ if (inet_pton(AF_INET, value, &ipmask->ip4) == 1)
+ ipmask->family = AF_INET;
+ }
+ if (ipmask->family == AF_UNSPEC) {
+ fprintf(stderr, "Unable to parse IP address: `%s`\n", value);
+ return false;
+ }
+ return true;
+}
+
+static inline bool parse_endpoint(struct sockaddr_storage *endpoint, const char *value)
+{
+ char *mutable = strdup(value);
+ char *begin, *end;
+ int ret;
+ struct addrinfo *resolved;
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP,
+ .ai_flags = AI_ADDRCONFIG
+ };
+ if (!strlen(value)) {
+ free(mutable);
+ fprintf(stderr, "Unable to parse empty endpoint\n");
+ return false;
+ }
+ if (mutable[0] == '[') {
+ begin = &mutable[1];
+ end = strchr(mutable, ']');
+ if (!end) {
+ free(mutable);
+ fprintf(stderr, "Unable to find matching brace of endpoint: `%s`\n", value);
+ return false;
+ }
+ *end = '\0';
+ ++end;
+ if (*end != ':' || !*(end + 1)) {
+ free(mutable);
+ fprintf(stderr, "Unable to find port of endpoint: `%s`\n", value);
+ return false;
+ }
+ ++end;
+ } else {
+ begin = mutable;
+ end = strrchr(mutable, ':');
+ if (!end || !*(end + 1)) {
+ free(mutable);
+ fprintf(stderr, "Unable to find port of endpoint: `%s`\n", value);
+ return false;
+ }
+ *end = '\0';
+ ++end;
+ }
+ ret = getaddrinfo(begin, end, &hints, &resolved);
+ if (ret != 0) {
+ free(mutable);
+ fprintf(stderr, "%s: `%s`\n", gai_strerror(ret), value);
+ return false;
+ }
+ if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) ||
+ (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)))
+ memcpy(endpoint, resolved->ai_addr, resolved->ai_addrlen);
+ else {
+ freeaddrinfo(resolved);
+ free(mutable);
+ fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s`\n", value);
+ return false;
+ }
+ freeaddrinfo(resolved);
+ free(mutable);
+ return true;
+}
+
+static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offset, const char *value)
+{
+ struct wgpeer *peer;
+ struct wgipmask *ipmask;
+ char *mask, *mutable = strdup(value), *sep;
+ if (!mutable) {
+ perror("strdup");
+ return false;
+ };
+ peer = peer_from_offset(buf->dev, peer_offset);
+ peer->num_ipmasks = 0;
+ peer->replace_ipmasks = true;
+ if (!strlen(value)) {
+ free(mutable);
+ return true;
+ }
+ sep = mutable;
+ while ((mask = strsep(&sep, ","))) {
+ unsigned long cidr;
+ char *end, *ip = strsep(&mask, "/");
+ if (use_space(buf, sizeof(struct wgipmask)) < 0) {
+ perror("use_space");
+ free(mutable);
+ return false;
+ }
+ peer = peer_from_offset(buf->dev, peer_offset);
+ ipmask = (struct wgipmask *)((uint8_t *)peer + sizeof(struct wgpeer) + (sizeof(struct wgipmask) * peer->num_ipmasks));
+
+ if (!parse_ip(ipmask, ip)) {
+ free(mutable);
+ return false;
+ }
+ if (ipmask->family == AF_INET) {
+ if (mask) {
+ cidr = strtoul(mask, &end, 10);
+ if (*end)
+ mask = NULL;
+ if (cidr > 32)
+ mask = NULL;
+ }
+ if (!mask)
+ cidr = 32;
+ } else if (ipmask->family == AF_INET6) {
+ if (mask) {
+ cidr = strtoul(mask, &end, 10);
+ if (*end)
+ mask = NULL;
+ if (cidr > 128)
+ mask = NULL;
+ }
+ if (!mask)
+ cidr = 128;
+ } else
+ continue;
+ ipmask->cidr = cidr;
+ ++peer->num_ipmasks;
+ }
+ free(mutable);
+ return true;
+}
+
+static bool process_line(struct config_ctx *ctx, const char *line)
+{
+ const char *value;
+ bool ret = true;
+
+ if (!strcasecmp(line, "[Interface]")) {
+ ctx->is_peer_section = false;
+ ctx->is_device_section = true;
+ return true;
+ }
+ if (!strcasecmp(line, "[Peer]")) {
+ ctx->peer_offset = ctx->buf.pos;
+ if (use_space(&ctx->buf, sizeof(struct wgpeer)) < 0) {
+ perror("use_space");
+ return false;
+ }
+ ++ctx->buf.dev->num_peers;
+ ctx->is_peer_section = true;
+ ctx->is_device_section = false;
+ peer_from_offset(ctx->buf.dev, ctx->peer_offset)->replace_ipmasks = true;
+ return true;
+ }
+
+#define key_match(key) (value = get_value(line, key "="))
+
+ if (ctx->is_device_section) {
+ if (key_match("ListenPort"))
+ ret = !!(ctx->buf.dev->port = parse_port(value));
+ else if (key_match("PrivateKey")) {
+ ret = parse_key(ctx->buf.dev->private_key, value);
+ if (!ret)
+ memset(ctx->buf.dev->private_key, 0, WG_KEY_LEN);
+ } else if (key_match("PresharedKey")) {
+ ret = parse_key(ctx->buf.dev->preshared_key, value);
+ if (!ret)
+ memset(ctx->buf.dev->preshared_key, 0, WG_KEY_LEN);
+ } else
+ goto error;
+ } else if (ctx->is_peer_section) {
+ if (key_match("Endpoint"))
+ ret = parse_endpoint(&peer_from_offset(ctx->buf.dev, ctx->peer_offset)->endpoint, value);
+ else if (key_match("PublicKey"))
+ ret = parse_key(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->public_key, value);
+ else if (key_match("AllowedIPs"))
+ ret = parse_ipmasks(&ctx->buf, ctx->peer_offset, value);
+ else
+ goto error;
+ } else
+ goto error;
+ return ret;
+
+#undef key_match
+
+error:
+ fprintf(stderr, "Line unrecognized: `%s'\n", line);
+ return false;
+}
+
+bool config_read_line(struct config_ctx *ctx, const char *input)
+{
+ size_t len = strlen(input), cleaned_len = 0;
+ char *line = calloc(len + 1, sizeof(char));
+ bool ret = true;
+ if (!line) {
+ perror("calloc");
+ return false;
+ }
+ if (!len)
+ goto out;
+ for (size_t i = 0; i < len; ++i) {
+ if (!isspace(input[i]))
+ line[cleaned_len++] = input[i];
+ }
+ if (!cleaned_len)
+ goto out;
+ if (line[0] == COMMENT_CHAR)
+ goto out;
+ ret = process_line(ctx, line);
+out:
+ free(line);
+ return ret;
+}
+
+bool config_read_init(struct config_ctx *ctx, struct wgdevice **device, bool append)
+{
+ memset(ctx, 0, sizeof(struct config_ctx));
+ ctx->device = device;
+ ctx->buf.dev = calloc(1, sizeof(struct wgdevice));
+ if (!ctx->buf.dev) {
+ perror("calloc");
+ return false;
+ }
+ ctx->buf.dev->replace_peer_list = !append;
+ return true;
+}
+
+static inline bool key_is_valid(uint8_t key[WG_KEY_LEN])
+{
+ static const uint8_t zero[WG_KEY_LEN] = { 0 };
+ return !!memcmp(key, zero, WG_KEY_LEN);
+}
+
+bool config_read_finish(struct config_ctx *ctx)
+{
+ size_t i;
+ struct wgpeer *peer;
+ if (ctx->buf.dev->replace_peer_list && !ctx->buf.dev->num_peers) {
+ fprintf(stderr, "No peers configured\n");
+ goto err;
+ }
+ if (ctx->buf.dev->replace_peer_list && !key_is_valid(ctx->buf.dev->private_key)) {
+ fprintf(stderr, "No private key configured\n");
+ goto err;
+ }
+ for_each_wgpeer(ctx->buf.dev, peer, i) {
+ if (!key_is_valid(peer->public_key)) {
+ fprintf(stderr, "A peer is missing a public key\n");
+ goto err;
+ }
+ }
+ *ctx->device = ctx->buf.dev;
+ return true;
+err:
+ free(ctx->buf.dev);
+ return false;
+}
+
+static int read_line(char **dst, const char *path)
+{
+ FILE *f;
+ size_t n = 0;
+ struct stat stat;
+
+ *dst = NULL;
+
+ f = fopen(path, "r");
+ if (!f) {
+ perror("fopen");
+ return -1;
+ }
+ if (fstat(fileno(f), &stat) < 0) {
+ perror("fstat");
+ fclose(f);
+ return -1;
+ }
+ if (S_ISCHR(stat.st_mode) && stat.st_rdev == makedev(1, 3)) {
+ fclose(f);
+ return 1;
+ }
+ if (getline(dst, &n, f) < 0) {
+ perror("getline");
+ fclose(f);
+ return -1;
+ }
+ fclose(f);
+ n = strlen(*dst);
+ while (--n) {
+ if (isspace((*dst)[n]))
+ (*dst)[n] = '\0';
+ }
+ return 0;
+}
+
+static char *strip_spaces(const char *in)
+{
+ char *out;
+ size_t t, l, i;
+
+ t = strlen(in);
+ out = calloc(t + 1, sizeof(char));
+ if (!out) {
+ perror("calloc");
+ return NULL;
+ }
+ for (i = 0, l = 0; i < t; ++i) {
+ if (!isspace(in[i]))
+ out[l++] = in[i];
+ }
+ return out;
+}
+
+bool config_read_cmd(struct wgdevice **device, char *argv[], int argc)
+{
+ struct inflatable_device buf = { 0 };
+ size_t peer_offset = 0;
+ buf.dev = calloc(sizeof(struct wgdevice), 1);
+ if (!buf.dev) {
+ perror("calloc");
+ return false;
+ }
+ while (argc > 0) {
+ if (!strcmp(argv[0], "listen-port") && argc >= 2 && !buf.dev->num_peers) {
+ buf.dev->port = parse_port(argv[1]);
+ if (!buf.dev->port)
+ goto error;
+ argv += 2;
+ argc -= 2;
+ } else if (!strcmp(argv[0], "private-key") && argc >= 2 && !buf.dev->num_peers) {
+ char *line;
+ int ret = read_line(&line, argv[1]);
+ if (ret == 0) {
+ if (!parse_key(buf.dev->private_key, line)) {
+ free(line);
+ goto error;
+ }
+ free(line);
+ } else if (ret == 1)
+ buf.dev->remove_private_key = true;
+ else
+ goto error;
+ argv += 2;
+ argc -= 2;
+ } else if (!strcmp(argv[0], "preshared-key") && argc >= 2 && !buf.dev->num_peers) {
+ char *line;
+ int ret = read_line(&line, argv[1]);
+ if (ret == 0) {
+ if (!parse_key(buf.dev->preshared_key, line)) {
+ free(line);
+ goto error;
+ }
+ free(line);
+ } else if (ret == 1)
+ buf.dev->remove_preshared_key = true;
+ else
+ goto error;
+ argv += 2;
+ argc -= 2;
+ } else if (!strcmp(argv[0], "peer") && argc >= 2) {
+ peer_offset = buf.pos;
+ if (use_space(&buf, sizeof(struct wgpeer)) < 0) {
+ perror("use_space");
+ goto error;
+ }
+ ++buf.dev->num_peers;
+ if (!parse_key(peer_from_offset(buf.dev, peer_offset)->public_key, argv[1]))
+ goto error;
+ argv += 2;
+ argc -= 2;
+ } else if (!strcmp(argv[0], "remove") && argc >= 1 && buf.dev->num_peers) {
+ peer_from_offset(buf.dev, peer_offset)->remove_me = true;
+ argv += 1;
+ argc -= 1;
+ } else if (!strcmp(argv[0], "endpoint") && argc >= 2 && buf.dev->num_peers) {
+ if (!parse_endpoint(&peer_from_offset(buf.dev, peer_offset)->endpoint, argv[1]))
+ goto error;
+ argv += 2;
+ argc -= 2;
+ } else if (!strcmp(argv[0], "allowed-ips") && argc >= 2 && buf.dev->num_peers) {
+ char *line = strip_spaces(argv[1]);
+ if (!line)
+ goto error;
+ if (!parse_ipmasks(&buf, peer_offset, line)) {
+ free(line);
+ goto error;
+ }
+ free(line);
+ argv += 2;
+ argc -= 2;
+ } else {
+ fprintf(stderr, "Invalid argument: %s\n", argv[0]);
+ goto error;
+ }
+ }
+ *device = buf.dev;
+ return true;
+error:
+ free(buf.dev);
+ return false;
+}