strongswan/src/libcharon/plugins/vici/vici_message.c

770 lines
15 KiB
C

/*
* Copyright (C) 2015 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* 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 && section)
{
DBG1(DBG_ENC, "'%N' within section", 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, va_list args)
{
vici_type_t *out;
chunk_t *value;
char **name;
uint8_t type;
chunk_t data;
VA_ARGS_VGET(args, out, name, value);
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 = enumerator_enumerate_default,
.venumerate = _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 (value.len == 0)
{
return def;
}
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_bool, bool,
private_vici_message_t *this, bool def, char *fmt, va_list args)
{
chunk_t value;
bool found;
char buf[16];
found = find_value(this, &value, fmt, args);
if (found)
{
if (value.len == 0)
{
return def;
}
if (chunk_printable(value, NULL, 0))
{
snprintf(buf, sizeof(buf), "%.*s", (int)value.len, value.ptr);
return settings_value_as_bool(buf, def);
}
}
return def;
}
METHOD(vici_message_t, get_bool, bool,
private_vici_message_t *this, bool def, char *fmt, ...)
{
va_list args;
bool val;
va_start(args, fmt);
val = vget_bool(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_bool = _get_bool,
.vget_bool = _vget_bool,
.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);
}