summaryrefslogtreecommitdiffstats
path: root/nhrpd/vici.c
diff options
context:
space:
mode:
Diffstat (limited to 'nhrpd/vici.c')
-rw-r--r--nhrpd/vici.c621
1 files changed, 621 insertions, 0 deletions
diff --git a/nhrpd/vici.c b/nhrpd/vici.c
new file mode 100644
index 0000000..6ba2399
--- /dev/null
+++ b/nhrpd/vici.c
@@ -0,0 +1,621 @@
+/* strongSwan VICI protocol implementation for NHRP
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "thread.h"
+#include "zbuf.h"
+#include "log.h"
+#include "lib_errors.h"
+
+#include "nhrpd.h"
+#include "vici.h"
+#include "nhrp_errors.h"
+
+#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+struct blob {
+ char *ptr;
+ int len;
+};
+
+static int blob_equal(const struct blob *b, const char *str)
+{
+ if (!b || b->len != (int)strlen(str))
+ return 0;
+ return memcmp(b->ptr, str, b->len) == 0;
+}
+
+static int blob2buf(const struct blob *b, char *buf, size_t n)
+{
+ if (!b || b->len >= (int)n)
+ return 0;
+ memcpy(buf, b->ptr, b->len);
+ buf[b->len] = 0;
+ return 1;
+}
+
+struct vici_conn {
+ struct thread *t_reconnect, *t_read, *t_write;
+ struct zbuf ibuf;
+ struct zbuf_queue obuf;
+ int fd;
+ uint8_t ibuf_data[VICI_MAX_MSGLEN];
+};
+
+struct vici_message_ctx {
+ const char *sections[8];
+ int nsections;
+};
+
+static void vici_reconnect(struct thread *t);
+static void vici_submit_request(struct vici_conn *vici, const char *name, ...);
+
+static void vici_zbuf_puts(struct zbuf *obuf, const char *str)
+{
+ size_t len = strlen(str);
+ zbuf_put8(obuf, len);
+ zbuf_put(obuf, str, len);
+}
+
+static void vici_connection_error(struct vici_conn *vici)
+{
+ nhrp_vc_reset();
+
+ THREAD_OFF(vici->t_read);
+ THREAD_OFF(vici->t_write);
+ zbuf_reset(&vici->ibuf);
+ zbufq_reset(&vici->obuf);
+
+ close(vici->fd);
+ vici->fd = -1;
+ thread_add_timer(master, vici_reconnect, vici, 2, &vici->t_reconnect);
+}
+
+static void vici_parse_message(struct vici_conn *vici, struct zbuf *msg,
+ void (*parser)(struct vici_message_ctx *ctx,
+ enum vici_type_t msgtype,
+ const struct blob *key,
+ const struct blob *val),
+ struct vici_message_ctx *ctx)
+{
+ uint8_t *type;
+ struct blob key = {0};
+ struct blob val = {0};
+
+ while ((type = zbuf_may_pull(msg, uint8_t)) != NULL) {
+ switch (*type) {
+ case VICI_SECTION_START:
+ key.len = zbuf_get8(msg);
+ key.ptr = zbuf_pulln(msg, key.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: Section start '%.*s'",
+ key.len, key.ptr);
+ parser(ctx, *type, &key, NULL);
+ ctx->nsections++;
+ break;
+ case VICI_SECTION_END:
+ debugf(NHRP_DEBUG_VICI, "VICI: Section end");
+ parser(ctx, *type, NULL, NULL);
+ ctx->nsections--;
+ break;
+ case VICI_KEY_VALUE:
+ key.len = zbuf_get8(msg);
+ key.ptr = zbuf_pulln(msg, key.len);
+ val.len = zbuf_get_be16(msg);
+ val.ptr = zbuf_pulln(msg, val.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: Key '%.*s'='%.*s'",
+ key.len, key.ptr, val.len, val.ptr);
+ parser(ctx, *type, &key, &val);
+ break;
+ case VICI_LIST_START:
+ key.len = zbuf_get8(msg);
+ key.ptr = zbuf_pulln(msg, key.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: List start '%.*s'",
+ key.len, key.ptr);
+ break;
+ case VICI_LIST_ITEM:
+ val.len = zbuf_get_be16(msg);
+ val.ptr = zbuf_pulln(msg, val.len);
+ debugf(NHRP_DEBUG_VICI, "VICI: List item: '%.*s'",
+ val.len, val.ptr);
+ parser(ctx, *type, &key, &val);
+ break;
+ case VICI_LIST_END:
+ debugf(NHRP_DEBUG_VICI, "VICI: List end");
+ break;
+ default:
+ debugf(NHRP_DEBUG_VICI,
+ "VICI: Unsupported message component type %d",
+ *type);
+ return;
+ }
+ }
+}
+
+struct handle_sa_ctx {
+ struct vici_message_ctx msgctx;
+ int event;
+ int child_ok;
+ int kill_ikesa;
+ uint32_t child_uniqueid, ike_uniqueid;
+ struct {
+ union sockunion host;
+ struct blob id, cert;
+ } local, remote;
+};
+
+static void parse_sa_message(struct vici_message_ctx *ctx,
+ enum vici_type_t msgtype, const struct blob *key,
+ const struct blob *val)
+{
+ struct handle_sa_ctx *sactx =
+ container_of(ctx, struct handle_sa_ctx, msgctx);
+ struct nhrp_vc *vc;
+ char buf[512];
+
+ switch (msgtype) {
+ case VICI_SECTION_START:
+ if (ctx->nsections == 3) {
+ /* Begin of child-sa section, reset child vars */
+ sactx->child_uniqueid = 0;
+ sactx->child_ok = 0;
+ }
+ break;
+ case VICI_SECTION_END:
+ if (ctx->nsections == 3) {
+ /* End of child-sa section, update nhrp_vc */
+ int up = sactx->child_ok || sactx->event == 1;
+ if (up) {
+ vc = nhrp_vc_get(&sactx->local.host,
+ &sactx->remote.host, up);
+ if (vc) {
+ blob2buf(&sactx->local.id, vc->local.id,
+ sizeof(vc->local.id));
+ if (blob2buf(&sactx->local.cert,
+ (char *)vc->local.cert,
+ sizeof(vc->local.cert)))
+ vc->local.certlen =
+ sactx->local.cert.len;
+ blob2buf(&sactx->remote.id,
+ vc->remote.id,
+ sizeof(vc->remote.id));
+ if (blob2buf(&sactx->remote.cert,
+ (char *)vc->remote.cert,
+ sizeof(vc->remote.cert)))
+ vc->remote.certlen =
+ sactx->remote.cert.len;
+ sactx->kill_ikesa |=
+ nhrp_vc_ipsec_updown(
+ sactx->child_uniqueid,
+ vc);
+ vc->ike_uniqueid = sactx->ike_uniqueid;
+ }
+ } else {
+ nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0);
+ }
+ }
+ break;
+ default:
+ if (!key || !key->ptr)
+ break;
+
+ switch (key->ptr[0]) {
+ case 'l':
+ if (blob_equal(key, "local-host")
+ && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ if (str2sockunion(buf,
+ &sactx->local.host)
+ < 0)
+ flog_err(
+ EC_NHRP_SWAN,
+ "VICI: bad strongSwan local-host: %s",
+ buf);
+ } else if (blob_equal(key, "local-id")
+ && ctx->nsections == 1) {
+ sactx->local.id = *val;
+ } else if (blob_equal(key, "local-cert-data")
+ && ctx->nsections == 1) {
+ sactx->local.cert = *val;
+ }
+ break;
+ case 'r':
+ if (blob_equal(key, "remote-host")
+ && ctx->nsections == 1) {
+ if (blob2buf(val, buf, sizeof(buf)))
+ if (str2sockunion(buf,
+ &sactx->remote.host)
+ < 0)
+ flog_err(
+ EC_NHRP_SWAN,
+ "VICI: bad strongSwan remote-host: %s",
+ buf);
+ } else if (blob_equal(key, "remote-id")
+ && ctx->nsections == 1) {
+ sactx->remote.id = *val;
+ } else if (blob_equal(key, "remote-cert-data")
+ && ctx->nsections == 1) {
+ sactx->remote.cert = *val;
+ }
+ break;
+ case 'u':
+ if (blob_equal(key, "uniqueid")
+ && blob2buf(val, buf, sizeof(buf))) {
+ if (ctx->nsections == 3)
+ sactx->child_uniqueid =
+ strtoul(buf, NULL, 0);
+ else if (ctx->nsections == 1)
+ sactx->ike_uniqueid =
+ strtoul(buf, NULL, 0);
+ }
+ break;
+ case 's':
+ if (blob_equal(key, "state") && ctx->nsections == 3) {
+ sactx->child_ok =
+ (sactx->event == 0
+ && (blob_equal(val, "INSTALLED")
+ || blob_equal(val, "REKEYED")));
+ }
+ break;
+ }
+ break;
+ }
+}
+
+static void parse_cmd_response(struct vici_message_ctx *ctx,
+ enum vici_type_t msgtype, const struct blob *key,
+ const struct blob *val)
+{
+ char buf[512];
+
+ switch (msgtype) {
+ case VICI_KEY_VALUE:
+ if (blob_equal(key, "errmsg")
+ && blob2buf(val, buf, sizeof(buf)))
+ flog_err(EC_NHRP_SWAN, "VICI: strongSwan: %s", buf);
+ break;
+ default:
+ break;
+ }
+}
+
+static void vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event)
+{
+ char buf[32];
+ struct handle_sa_ctx ctx = {
+ .event = event,
+ .msgctx.nsections = 0
+ };
+
+ vici_parse_message(vici, msg, parse_sa_message, &ctx.msgctx);
+
+ if (ctx.kill_ikesa && ctx.ike_uniqueid) {
+ debugf(NHRP_DEBUG_COMMON, "VICI: Deleting IKE_SA %u",
+ ctx.ike_uniqueid);
+ snprintf(buf, sizeof(buf), "%u", ctx.ike_uniqueid);
+ vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id",
+ strlen(buf), buf, VICI_END);
+ }
+}
+
+static void vici_recv_message(struct vici_conn *vici, struct zbuf *msg)
+{
+ uint32_t msglen;
+ uint8_t msgtype;
+ struct blob name;
+ struct vici_message_ctx ctx = { .nsections = 0 };
+
+ msglen = zbuf_get_be32(msg);
+ msgtype = zbuf_get8(msg);
+ debugf(NHRP_DEBUG_VICI, "VICI: Message %d, %d bytes", msgtype, msglen);
+
+ switch (msgtype) {
+ case VICI_EVENT:
+ name.len = zbuf_get8(msg);
+ name.ptr = zbuf_pulln(msg, name.len);
+
+ debugf(NHRP_DEBUG_VICI, "VICI: Event '%.*s'", name.len,
+ name.ptr);
+ if (blob_equal(&name, "list-sa")
+ || blob_equal(&name, "child-updown")
+ || blob_equal(&name, "child-rekey"))
+ vici_recv_sa(vici, msg, 0);
+ else if (blob_equal(&name, "child-state-installed")
+ || blob_equal(&name, "child-state-rekeyed"))
+ vici_recv_sa(vici, msg, 1);
+ else if (blob_equal(&name, "child-state-destroying"))
+ vici_recv_sa(vici, msg, 2);
+ break;
+ case VICI_CMD_RESPONSE:
+ vici_parse_message(vici, msg, parse_cmd_response, &ctx);
+ break;
+ case VICI_EVENT_UNKNOWN:
+ case VICI_CMD_UNKNOWN:
+ flog_err(
+ EC_NHRP_SWAN,
+ "VICI: StrongSwan does not support mandatory events (unpatched?)");
+ break;
+ case VICI_EVENT_CONFIRM:
+ break;
+ default:
+ zlog_notice("VICI: Unrecognized message type %d", msgtype);
+ break;
+ }
+}
+
+static void vici_read(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ struct zbuf *ibuf = &vici->ibuf;
+ struct zbuf pktbuf;
+
+ if (zbuf_read(ibuf, vici->fd, (size_t)-1) < 0) {
+ vici_connection_error(vici);
+ return;
+ }
+
+ /* Process all messages in buffer */
+ do {
+ uint32_t *hdrlen = zbuf_may_pull(ibuf, uint32_t);
+ if (!hdrlen)
+ break;
+ if (!zbuf_may_pulln(ibuf, ntohl(*hdrlen))) {
+ zbuf_reset_head(ibuf, hdrlen);
+ break;
+ }
+
+ /* Handle packet */
+ zbuf_init(&pktbuf, hdrlen, htonl(*hdrlen) + 4,
+ htonl(*hdrlen) + 4);
+ vici_recv_message(vici, &pktbuf);
+ } while (1);
+
+ thread_add_read(master, vici_read, vici, vici->fd, &vici->t_read);
+}
+
+static void vici_write(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int r;
+
+ r = zbufq_write(&vici->obuf, vici->fd);
+ if (r > 0) {
+ thread_add_write(master, vici_write, vici, vici->fd,
+ &vici->t_write);
+ } else if (r < 0) {
+ vici_connection_error(vici);
+ }
+}
+
+static void vici_submit(struct vici_conn *vici, struct zbuf *obuf)
+{
+ if (vici->fd < 0) {
+ zbuf_free(obuf);
+ return;
+ }
+
+ zbufq_queue(&vici->obuf, obuf);
+ thread_add_write(master, vici_write, vici, vici->fd, &vici->t_write);
+}
+
+static void vici_submit_request(struct vici_conn *vici, const char *name, ...)
+{
+ struct zbuf *obuf;
+ uint32_t *hdrlen;
+ va_list va;
+ size_t len;
+ int type;
+
+ obuf = zbuf_alloc(256);
+ if (!obuf)
+ return;
+
+ hdrlen = zbuf_push(obuf, uint32_t);
+ zbuf_put8(obuf, VICI_CMD_REQUEST);
+ vici_zbuf_puts(obuf, name);
+
+ va_start(va, name);
+ for (type = va_arg(va, int); type != VICI_END; type = va_arg(va, int)) {
+ zbuf_put8(obuf, type);
+ switch (type) {
+ case VICI_KEY_VALUE:
+ vici_zbuf_puts(obuf, va_arg(va, const char *));
+ len = va_arg(va, size_t);
+ zbuf_put_be16(obuf, len);
+ zbuf_put(obuf, va_arg(va, void *), len);
+ break;
+ default:
+ break;
+ }
+ }
+ va_end(va);
+ *hdrlen = htonl(zbuf_used(obuf) - 4);
+ vici_submit(vici, obuf);
+}
+
+static void vici_register_event(struct vici_conn *vici, const char *name)
+{
+ struct zbuf *obuf;
+ uint32_t *hdrlen;
+ uint8_t namelen;
+
+ namelen = strlen(name);
+ obuf = zbuf_alloc(4 + 1 + 1 + namelen);
+ if (!obuf)
+ return;
+
+ hdrlen = zbuf_push(obuf, uint32_t);
+ zbuf_put8(obuf, VICI_EVENT_REGISTER);
+ zbuf_put8(obuf, namelen);
+ zbuf_put(obuf, name, namelen);
+ *hdrlen = htonl(zbuf_used(obuf) - 4);
+
+ vici_submit(vici, obuf);
+}
+
+static bool vici_charon_filepath_done;
+static bool vici_charon_not_found;
+
+static char *vici_get_charon_filepath(void)
+{
+ static char buff[1200];
+ FILE *fp;
+ char *ptr;
+ char line[1024];
+
+ if (vici_charon_filepath_done)
+ return (char *)buff;
+ fp = popen("ipsec --piddir", "r");
+ if (!fp) {
+ if (!vici_charon_not_found) {
+ flog_err(EC_NHRP_SWAN,
+ "VICI: Failed to retrieve charon file path");
+ vici_charon_not_found = true;
+ }
+ return NULL;
+ }
+ /* last line of output is used to get vici path */
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ ptr = strchr(line, '\n');
+ if (ptr)
+ *ptr = '\0';
+ snprintf(buff, sizeof(buff), "%s/charon.vici", line);
+ }
+ pclose(fp);
+ vici_charon_filepath_done = true;
+ return buff;
+}
+
+static void vici_reconnect(struct thread *t)
+{
+ struct vici_conn *vici = THREAD_ARG(t);
+ int fd;
+ char *file_path;
+
+ if (vici->fd >= 0)
+ return;
+
+ fd = sock_open_unix(VICI_SOCKET);
+ if (fd < 0) {
+ file_path = vici_get_charon_filepath();
+ if (file_path)
+ fd = sock_open_unix(file_path);
+ }
+ if (fd < 0) {
+ debugf(NHRP_DEBUG_VICI,
+ "%s: failure connecting VICI socket: %s", __func__,
+ strerror(errno));
+ thread_add_timer(master, vici_reconnect, vici, 2,
+ &vici->t_reconnect);
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "VICI: Connected");
+ vici->fd = fd;
+ thread_add_read(master, vici_read, vici, vici->fd, &vici->t_read);
+
+ /* Send event subscribtions */
+ // vici_register_event(vici, "child-updown");
+ // vici_register_event(vici, "child-rekey");
+ vici_register_event(vici, "child-state-installed");
+ vici_register_event(vici, "child-state-rekeyed");
+ vici_register_event(vici, "child-state-destroying");
+ vici_register_event(vici, "list-sa");
+ vici_submit_request(vici, "list-sas", VICI_END);
+}
+
+static struct vici_conn vici_connection;
+
+void vici_init(void)
+{
+ struct vici_conn *vici = &vici_connection;
+
+ vici->fd = -1;
+ zbuf_init(&vici->ibuf, vici->ibuf_data, sizeof(vici->ibuf_data), 0);
+ zbufq_init(&vici->obuf);
+ thread_add_timer_msec(master, vici_reconnect, vici, 10,
+ &vici->t_reconnect);
+}
+
+void vici_terminate(void)
+{
+}
+
+void vici_terminate_vc_by_profile_name(char *profile_name)
+{
+ struct vici_conn *vici = &vici_connection;
+
+ debugf(NHRP_DEBUG_VICI, "Terminate profile = %s", profile_name);
+ vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike",
+ strlen(profile_name), profile_name, VICI_END);
+}
+
+void vici_terminate_vc_by_ike_id(unsigned int ike_id)
+{
+ struct vici_conn *vici = &vici_connection;
+ char ike_id_str[10];
+
+ snprintf(ike_id_str, sizeof(ike_id_str), "%d", ike_id);
+ debugf(NHRP_DEBUG_VICI, "Terminate ike_id_str = %s", ike_id_str);
+ vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id",
+ strlen(ike_id_str), ike_id_str, VICI_END);
+}
+
+void vici_request_vc(const char *profile, union sockunion *src,
+ union sockunion *dst, int prio)
+{
+ struct vici_conn *vici = &vici_connection;
+ char buf[2][SU_ADDRSTRLEN];
+
+ sockunion2str(src, buf[0], sizeof(buf[0]));
+ sockunion2str(dst, buf[1], sizeof(buf[1]));
+
+ vici_submit_request(vici, "initiate", VICI_KEY_VALUE, "child",
+ strlen(profile), profile, VICI_KEY_VALUE, "timeout",
+ (size_t)2, "-1", VICI_KEY_VALUE, "async", (size_t)1,
+ "1", VICI_KEY_VALUE, "init-limits", (size_t)1,
+ prio ? "0" : "1", VICI_KEY_VALUE, "my-host",
+ strlen(buf[0]), buf[0], VICI_KEY_VALUE,
+ "other-host", strlen(buf[1]), buf[1], VICI_END);
+}
+
+int sock_open_unix(const char *path)
+{
+ int ret, fd;
+ struct sockaddr_un addr;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -1;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+ ret = connect(fd, (struct sockaddr *)&addr,
+ sizeof(addr.sun_family) + strlen(addr.sun_path));
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}