770 lines
15 KiB
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, ¤t))
|
|
{
|
|
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, ¤t))
|
|
{
|
|
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);
|
|
}
|