summaryrefslogtreecommitdiff
path: root/src/libcharon/plugins/vici/vici_message.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcharon/plugins/vici/vici_message.c')
-rw-r--r--src/libcharon/plugins/vici/vici_message.c727
1 files changed, 727 insertions, 0 deletions
diff --git a/src/libcharon/plugins/vici/vici_message.c b/src/libcharon/plugins/vici/vici_message.c
new file mode 100644
index 000000000..dcc175f67
--- /dev/null
+++ b/src/libcharon/plugins/vici/vici_message.c
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 2014 Martin Willi
+ * Copyright (C) 2014 revosec AG
+ *
+ * This program is free software; you can redistribute it 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. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "vici_message.h"
+#include "vici_builder.h"
+
+#include <bio/bio_reader.h>
+#include <bio/bio_writer.h>
+
+#include <errno.h>
+
+typedef struct private_vici_message_t private_vici_message_t;
+
+/**
+ * Private data of an vici_message_t object.
+ */
+struct private_vici_message_t {
+
+ /**
+ * Public vici_message_t interface.
+ */
+ vici_message_t public;
+
+ /**
+ * Message encoding
+ */
+ chunk_t encoding;
+
+ /**
+ * Free encoding during destruction?
+ */
+ bool cleanup;
+
+ /**
+ * Allocated strings we maintain for get_str()
+ */
+ linked_list_t *strings;
+};
+
+ENUM(vici_type_names, VICI_START, VICI_END,
+ "start",
+ "section-start",
+ "section-end",
+ "key-value",
+ "list-start",
+ "list-item",
+ "list-end",
+ "end"
+);
+
+/**
+ * See header.
+ */
+bool vici_stringify(chunk_t chunk, char *buf, size_t size)
+{
+ if (!chunk_printable(chunk, NULL, 0))
+ {
+ return FALSE;
+ }
+ snprintf(buf, size, "%.*s", (int)chunk.len, chunk.ptr);
+ return TRUE;
+}
+
+/**
+ * See header.
+ */
+bool vici_verify_type(vici_type_t type, u_int section, bool list)
+{
+ if (list)
+ {
+ if (type != VICI_LIST_END && type != VICI_LIST_ITEM)
+ {
+ DBG1(DBG_ENC, "'%N' within list", vici_type_names, type);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (type == VICI_LIST_ITEM || type == VICI_LIST_END)
+ {
+ DBG1(DBG_ENC, "'%N' outside list", vici_type_names, type);
+ return FALSE;
+ }
+ }
+ if (type == VICI_SECTION_END && section == 0)
+ {
+ DBG1(DBG_ENC, "'%N' outside of section", vici_type_names, type);
+ return FALSE;
+ }
+ if (type == VICI_END)
+ {
+ if (section)
+ {
+ DBG1(DBG_ENC, "'%N' within section", vici_type_names, type);
+ return FALSE;
+ }
+ if (list)
+ {
+ DBG1(DBG_ENC, "'%N' within list", vici_type_names, type);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Enumerator parsing message
+ */
+typedef struct {
+ /* implements enumerator */
+ enumerator_t public;
+ /** reader to parse from */
+ bio_reader_t *reader;
+ /** section nesting level */
+ int section;
+ /** currently parsing list? */
+ bool list;
+ /** string currently enumerating */
+ char name[257];
+} parse_enumerator_t;
+
+METHOD(enumerator_t, parse_enumerate, bool,
+ parse_enumerator_t *this, vici_type_t *out, char **name, chunk_t *value)
+{
+ u_int8_t type;
+ chunk_t data;
+
+ if (!this->reader->remaining(this->reader) ||
+ !this->reader->read_uint8(this->reader, &type))
+ {
+ *out = VICI_END;
+ return TRUE;
+ }
+ if (!vici_verify_type(type, this->section, this->list))
+ {
+ return FALSE;
+ }
+
+ switch (type)
+ {
+ case VICI_SECTION_START:
+ if (!this->reader->read_data8(this->reader, &data) ||
+ !vici_stringify(data, this->name, sizeof(this->name)))
+ {
+ DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type);
+ return FALSE;
+ }
+ *name = this->name;
+ this->section++;
+ break;
+ case VICI_SECTION_END:
+ this->section--;
+ break;
+ case VICI_KEY_VALUE:
+ if (!this->reader->read_data8(this->reader, &data) ||
+ !vici_stringify(data, this->name, sizeof(this->name)) ||
+ !this->reader->read_data16(this->reader, value))
+ {
+ DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type);
+ return FALSE;
+ }
+ *name = this->name;
+ break;
+ case VICI_LIST_START:
+ if (!this->reader->read_data8(this->reader, &data) ||
+ !vici_stringify(data, this->name, sizeof(this->name)))
+ {
+ DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type);
+ return FALSE;
+ }
+ *name = this->name;
+ this->list = TRUE;
+ break;
+ case VICI_LIST_ITEM:
+ this->reader->read_data16(this->reader, value);
+ break;
+ case VICI_LIST_END:
+ this->list = FALSE;
+ break;
+ case VICI_END:
+ return TRUE;
+ default:
+ DBG1(DBG_ENC, "unknown encoding type: %u", type);
+ return FALSE;
+ }
+
+ *out = type;
+
+ return TRUE;
+}
+
+METHOD(enumerator_t, parse_destroy, void,
+ parse_enumerator_t *this)
+{
+ this->reader->destroy(this->reader);
+ free(this);
+}
+
+METHOD(vici_message_t, create_enumerator, enumerator_t*,
+ private_vici_message_t *this)
+{
+ parse_enumerator_t *enumerator;
+
+ INIT(enumerator,
+ .public = {
+ .enumerate = (void*)_parse_enumerate,
+ .destroy = _parse_destroy,
+ },
+ .reader = bio_reader_create(this->encoding),
+ );
+
+ return &enumerator->public;
+}
+
+/**
+ * Find a value for given vararg key
+ */
+static bool find_value(private_vici_message_t *this, chunk_t *value,
+ char *fmt, va_list args)
+{
+ enumerator_t *enumerator;
+ char buf[128], *name, *key, *dot, *next;
+ int section = 0, keysection = 0;
+ bool found = FALSE;
+ chunk_t current;
+ vici_type_t type;
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ next = buf;
+
+ enumerator = create_enumerator(this);
+
+ /* descent into section */
+ while (TRUE)
+ {
+ dot = strchr(next, '.');
+ if (!dot)
+ {
+ key = next;
+ break;
+ }
+ *dot = '\0';
+ key = next;
+ next = dot + 1;
+ keysection++;
+
+ while (enumerator->enumerate(enumerator, &type, &name, &current))
+ {
+ switch (type)
+ {
+ case VICI_SECTION_START:
+ section++;
+ if (section == keysection && streq(name, key))
+ {
+ break;
+ }
+ continue;
+ case VICI_SECTION_END:
+ section--;
+ continue;
+ case VICI_END:
+ break;
+ default:
+ continue;
+ }
+ break;
+ }
+ }
+
+ /* find key/value in current section */
+ while (enumerator->enumerate(enumerator, &type, &name, &current))
+ {
+ switch (type)
+ {
+ case VICI_KEY_VALUE:
+ if (section == keysection && streq(key, name))
+ {
+ *value = current;
+ found = TRUE;
+ break;
+ }
+ continue;
+ case VICI_SECTION_START:
+ section++;
+ continue;
+ case VICI_SECTION_END:
+ section--;
+ continue;
+ case VICI_END:
+ break;
+ default:
+ continue;
+ }
+ break;
+ }
+
+ enumerator->destroy(enumerator);
+
+ return found;
+}
+
+METHOD(vici_message_t, vget_str, char*,
+ private_vici_message_t *this, char *def, char *fmt, va_list args)
+{
+ chunk_t value;
+ bool found;
+ char *str;
+
+ found = find_value(this, &value, fmt, args);
+ if (found)
+ {
+ if (chunk_printable(value, NULL, 0))
+ {
+ str = strndup(value.ptr, value.len);
+ /* keep a reference to string, so caller doesn't have to care */
+ this->strings->insert_last(this->strings, str);
+ return str;
+ }
+ }
+ return def;
+}
+
+METHOD(vici_message_t, get_str, char*,
+ private_vici_message_t *this, char *def, char *fmt, ...)
+{
+ va_list args;
+ char *str;
+
+ va_start(args, fmt);
+ str = vget_str(this, def, fmt, args);
+ va_end(args);
+ return str;
+}
+
+METHOD(vici_message_t, vget_int, int,
+ private_vici_message_t *this, int def, char *fmt, va_list args)
+{
+ chunk_t value;
+ bool found;
+ char buf[32], *pos;
+ int ret;
+
+ found = find_value(this, &value, fmt, args);
+ if (found)
+ {
+ if (chunk_printable(value, NULL, 0))
+ {
+ snprintf(buf, sizeof(buf), "%.*s", (int)value.len, value.ptr);
+ errno = 0;
+ ret = strtol(buf, &pos, 0);
+ if (errno == 0 && pos == buf + strlen(buf))
+ {
+ return ret;
+ }
+ }
+ }
+ return def;
+}
+
+METHOD(vici_message_t, get_int, int,
+ private_vici_message_t *this, int def, char *fmt, ...)
+{
+ va_list args;
+ int val;
+
+ va_start(args, fmt);
+ val = vget_int(this, def, fmt, args);
+ va_end(args);
+ return val;
+}
+
+METHOD(vici_message_t, vget_value, chunk_t,
+ private_vici_message_t *this, chunk_t def, char *fmt, va_list args)
+{
+ chunk_t value;
+ bool found;
+
+ found = find_value(this, &value, fmt, args);
+ if (found)
+ {
+ return value;
+ }
+ return def;
+}
+
+METHOD(vici_message_t, get_value, chunk_t,
+ private_vici_message_t *this, chunk_t def, char *fmt, ...)
+{
+ va_list args;
+ chunk_t value;
+
+ va_start(args, fmt);
+ value = vget_value(this, def, fmt, args);
+ va_end(args);
+ return value;
+}
+
+METHOD(vici_message_t, get_encoding, chunk_t,
+ private_vici_message_t *this)
+{
+ return this->encoding;
+}
+
+/**
+ * Private parse context data
+ */
+struct vici_parse_context_t {
+ /** current section nesting level */
+ int level;
+ /** parse enumerator */
+ enumerator_t *e;
+};
+
+METHOD(vici_message_t, parse, bool,
+ private_vici_message_t *this, vici_parse_context_t *ctx,
+ vici_section_cb_t section, vici_value_cb_t kv, vici_value_cb_t li,
+ void *user)
+{
+ vici_parse_context_t root = {};
+ char *name, *list = NULL;
+ vici_type_t type;
+ chunk_t value;
+ int base;
+ bool ok = TRUE;
+
+ if (!ctx)
+ {
+ ctx = &root;
+ root.e = create_enumerator(this);
+ }
+
+ base = ctx->level;
+
+ while (ok)
+ {
+ ok = ctx->e->enumerate(ctx->e, &type, &name, &value);
+ if (ok)
+ {
+ switch (type)
+ {
+ case VICI_START:
+ /* should never occur */
+ continue;
+ case VICI_KEY_VALUE:
+ if (ctx->level == base && kv)
+ {
+ name = strdup(name);
+ this->strings->insert_last(this->strings, name);
+ ok = kv(user, &this->public, name, value);
+ }
+ continue;
+ case VICI_LIST_START:
+ if (ctx->level == base)
+ {
+ list = strdup(name);
+ this->strings->insert_last(this->strings, list);
+ }
+ continue;
+ case VICI_LIST_ITEM:
+ if (list && li)
+ {
+ name = strdup(name);
+ this->strings->insert_last(this->strings, name);
+ ok = li(user, &this->public, list, value);
+ }
+ continue;
+ case VICI_LIST_END:
+ if (ctx->level == base)
+ {
+ list = NULL;
+ }
+ continue;
+ case VICI_SECTION_START:
+ if (ctx->level++ == base && section)
+ {
+ name = strdup(name);
+ this->strings->insert_last(this->strings, name);
+ ok = section(user, &this->public, ctx, name);
+ }
+ continue;
+ case VICI_SECTION_END:
+ if (ctx->level-- == base)
+ {
+ break;
+ }
+ continue;
+ case VICI_END:
+ break;
+ }
+ }
+ break;
+ }
+
+ if (ctx == &root)
+ {
+ root.e->destroy(root.e);
+ }
+ return ok;
+}
+
+METHOD(vici_message_t, dump, bool,
+ private_vici_message_t *this, char *label, bool pretty, FILE *out)
+{
+ enumerator_t *enumerator;
+ int ident = 0, delta;
+ vici_type_t type, last_type = VICI_START;
+ char *name, *term, *sep, *separ, *assign;
+ chunk_t value;
+
+ /* pretty print uses indentation on multiple lines */
+ if (pretty)
+ {
+ delta = 2;
+ term = "\n";
+ separ = "";
+ assign = " = ";
+ }
+ else
+ {
+ delta = 0;
+ term = "";
+ separ = " ";
+ assign = "=";
+ }
+
+ fprintf(out, "%s {%s", label, term);
+ ident += delta;
+
+ enumerator = create_enumerator(this);
+ while (enumerator->enumerate(enumerator, &type, &name, &value))
+ {
+ switch (type)
+ {
+ case VICI_START:
+ /* should never occur */
+ break;
+ case VICI_SECTION_START:
+ sep = (last_type != VICI_SECTION_START &&
+ last_type != VICI_START) ? separ : "";
+ fprintf(out, "%*s%s%s {%s", ident, "", sep, name, term);
+ ident += delta;
+ break;
+ case VICI_SECTION_END:
+ ident -= delta;
+ fprintf(out, "%*s}%s", ident, "", term);
+ break;
+ case VICI_KEY_VALUE:
+ sep = (last_type != VICI_SECTION_START &&
+ last_type != VICI_START) ? separ : "";
+ if (chunk_printable(value, NULL, ' '))
+ {
+ fprintf(out, "%*s%s%s%s%.*s%s", ident, "", sep, name,
+ assign, (int)value.len, value.ptr, term);
+ }
+ else
+ {
+ fprintf(out, "%*s%s%s%s0x%+#B%s", ident, "", sep, name,
+ assign, &value, term);
+ }
+ break;
+ case VICI_LIST_START:
+ sep = (last_type != VICI_SECTION_START &&
+ last_type != VICI_START) ? separ : "";
+ fprintf(out, "%*s%s%s%s[%s", ident, "", sep, name, assign, term);
+ ident += delta;
+ break;
+ case VICI_LIST_END:
+ ident -= delta;
+ fprintf(out, "%*s]%s", ident, "", term);
+ break;
+ case VICI_LIST_ITEM:
+ sep = (last_type != VICI_LIST_START) ? separ : "";
+ if (chunk_printable(value, NULL, ' '))
+ {
+ fprintf(out, "%*s%s%.*s%s", ident, "", sep,
+ (int)value.len, value.ptr, term);
+ }
+ else
+ {
+ fprintf(out, "%*s%s0x%+#B%s", ident, "", sep,
+ &value, term);
+ }
+ break;
+ case VICI_END:
+ fprintf(out, "}\n");
+ enumerator->destroy(enumerator);
+ return TRUE;
+ }
+ last_type = type;
+ }
+ enumerator->destroy(enumerator);
+ return FALSE;
+}
+
+METHOD(vici_message_t, destroy, void,
+ private_vici_message_t *this)
+{
+ if (this->cleanup)
+ {
+ chunk_clear(&this->encoding);
+ }
+ this->strings->destroy_function(this->strings, free);
+ free(this);
+}
+
+/**
+ * See header
+ */
+vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup)
+{
+ private_vici_message_t *this;
+
+ INIT(this,
+ .public = {
+ .create_enumerator = _create_enumerator,
+ .get_str = _get_str,
+ .vget_str = _vget_str,
+ .get_int = _get_int,
+ .vget_int = _vget_int,
+ .get_value = _get_value,
+ .vget_value = _vget_value,
+ .get_encoding = _get_encoding,
+ .parse = _parse,
+ .dump = _dump,
+ .destroy = _destroy,
+ },
+ .strings = linked_list_create(),
+ .encoding = data,
+ .cleanup = cleanup,
+ );
+
+ return &this->public;
+}
+
+/**
+ * See header
+ */
+vici_message_t *vici_message_create_from_enumerator(enumerator_t *enumerator)
+{
+ vici_builder_t *builder;
+ vici_type_t type;
+ char *name;
+ chunk_t value;
+
+ builder = vici_builder_create();
+ while (enumerator->enumerate(enumerator, &type, &name, &value))
+ {
+ switch (type)
+ {
+ case VICI_SECTION_START:
+ case VICI_LIST_START:
+ builder->add(builder, type, name);
+ continue;
+ case VICI_KEY_VALUE:
+ builder->add(builder, type, name, value);
+ continue;
+ case VICI_LIST_ITEM:
+ builder->add(builder, type, value);
+ continue;
+ case VICI_SECTION_END:
+ case VICI_LIST_END:
+ default:
+ builder->add(builder, type);
+ continue;
+ case VICI_END:
+ break;
+ }
+ break;
+ }
+ enumerator->destroy(enumerator);
+
+ return builder->finalize(builder);
+}
+
+/**
+ * See header
+ */
+vici_message_t *vici_message_create_from_args(vici_type_t type, ...)
+{
+ vici_builder_t *builder;
+ va_list args;
+ char *name;
+ chunk_t value;
+
+ builder = vici_builder_create();
+ va_start(args, type);
+ while (type != VICI_END)
+ {
+ switch (type)
+ {
+ case VICI_LIST_START:
+ case VICI_SECTION_START:
+ name = va_arg(args, char*);
+ builder->add(builder, type, name);
+ break;
+ case VICI_KEY_VALUE:
+ name = va_arg(args, char*);
+ value = va_arg(args, chunk_t);
+ builder->add(builder, type, name, value);
+ break;
+ case VICI_LIST_ITEM:
+ value = va_arg(args, chunk_t);
+ builder->add(builder, type, value);
+ break;
+ case VICI_SECTION_END:
+ case VICI_LIST_END:
+ default:
+ builder->add(builder, type);
+ break;
+ }
+ type = va_arg(args, vici_type_t);
+ }
+ va_end(args);
+ return builder->finalize(builder);
+}