From 99d303ac2739e65a02fbbc325b74ad6fcac63cc2 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 5 Jun 2015 15:58:00 +0200 Subject: Initial commit --- src/tools/config.c | 518 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 src/tools/config.c (limited to 'src/tools/config.c') 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 . All Rights Reserved. */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} -- cgit v1.2.3-59-g8ed1b