diff options
Diffstat (limited to 'src/libcharon/plugins/vici/libvici.c')
-rw-r--r-- | src/libcharon/plugins/vici/libvici.c | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c new file mode 100644 index 000000000..a2cbb3082 --- /dev/null +++ b/src/libcharon/plugins/vici/libvici.c @@ -0,0 +1,764 @@ +/* + * 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 "libvici.h" +#include "vici_builder.h" +#include "vici_dispatcher.h" +#include "vici_socket.h" + +#include <library.h> +#include <threading/mutex.h> +#include <threading/condvar.h> +#include <collections/hashtable.h> + +#include <errno.h> + +/** + * Event registration + */ +typedef struct { + /** name of event */ + char *name; + /** callback function */ + vici_event_cb_t cb; + /** user data for callback */ + void *user; +} event_t; + +/** + * Wait state signaled by asynchronous on_read callback + */ +typedef enum { + WAIT_IDLE = 0, + WAIT_SUCCESS, + WAIT_FAILURE, + WAIT_READ_ERROR, +} wait_state_t; + +/** + * Private vici connection contex. + */ +struct vici_conn_t { + /** connection stream */ + stream_t *stream; + /** event registrations, as char* => event_t */ + hashtable_t *events; + /** connection lock */ + mutex_t *mutex; + /** condvar to signal incoming response */ + condvar_t *cond; + /** queued response message */ + chunk_t queue; + /** asynchronous read error */ + int error; + /** wait state */ + wait_state_t wait; +}; + +/** + * Private vici request message. + */ +struct vici_req_t { + /** connection context */ + vici_conn_t *conn; + /** name of request message */ + char *name; + /** message builder */ + vici_builder_t *b; +}; + +/** + * Private vici response/event message. + */ +struct vici_res_t { + /** response message */ + vici_message_t *message; + /** allocated strings */ + linked_list_t *strings; + /** item enumerator */ + enumerator_t *enumerator; + /** currently enumerating type */ + vici_type_t type; + /** currently enumerating name */ + char *name; + /** currently enumerating value */ + chunk_t value; + /** section nesting level of callback parser */ + int level; +}; + +/** + * Signal wait result for waiting user thread + */ +static bool wait_result(vici_conn_t *conn, wait_state_t wait) +{ + conn->mutex->lock(conn->mutex); + conn->wait = wait; + conn->mutex->unlock(conn->mutex); + conn->cond->signal(conn->cond); + return FALSE; +} + +/** + * Signal wait error result for waiting user thread + */ +static bool read_error(vici_conn_t *conn, int err) +{ + conn->error = err; + return wait_result(conn, WAIT_READ_ERROR); +} + +/** + * Handle a command response message + */ +static bool handle_response(vici_conn_t *conn, u_int32_t len) +{ + chunk_t buf; + + buf = chunk_alloc(len); + if (!conn->stream->read_all(conn->stream, buf.ptr, buf.len)) + { + free(buf.ptr); + return read_error(conn, errno); + } + conn->queue = buf; + return wait_result(conn, WAIT_SUCCESS); +} + +/** + * Dispatch received event message + */ +static bool handle_event(vici_conn_t *conn, u_int32_t len) +{ + vici_message_t *message; + event_t *event; + u_int8_t namelen; + char name[257], *buf; + + if (len < sizeof(namelen)) + { + return read_error(conn, EBADMSG); + } + if (!conn->stream->read_all(conn->stream, &namelen, sizeof(namelen))) + { + return read_error(conn, errno); + } + if (namelen > len - sizeof(namelen)) + { + return read_error(conn, EBADMSG); + } + if (!conn->stream->read_all(conn->stream, name, namelen)) + { + return read_error(conn, errno); + } + name[namelen] = '\0'; + len -= sizeof(namelen) + namelen; + buf = malloc(len); + if (!conn->stream->read_all(conn->stream, buf, len)) + { + free(buf); + return read_error(conn, errno); + } + message = vici_message_create_from_data(chunk_create(buf, len), TRUE); + + conn->mutex->lock(conn->mutex); + event = conn->events->get(conn->events, name); + if (event) + { + vici_res_t res = { + .message = message, + .enumerator = message->create_enumerator(message), + .strings = linked_list_create(), + }; + + event->cb(event->user, name, &res); + + res.enumerator->destroy(res.enumerator); + res.strings->destroy_function(res.strings, free); + } + conn->mutex->unlock(conn->mutex); + + message->destroy(message); + + return TRUE; +} + +CALLBACK(on_read, bool, + vici_conn_t *conn, stream_t *stream) +{ + u_int32_t len; + u_int8_t op; + ssize_t hlen; + + hlen = stream->read(stream, &len, sizeof(len), FALSE); + if (hlen <= 0) + { + if (errno == EWOULDBLOCK) + { + return TRUE; + } + return read_error(conn, errno); + } + if (hlen < sizeof(len)) + { + if (!stream->read_all(stream, ((void*)&len) + hlen, sizeof(len) - hlen)) + { + return read_error(conn, errno); + } + } + + len = ntohl(len); + if (len > VICI_MESSAGE_SIZE_MAX) + { + return read_error(conn, EBADMSG); + } + if (len-- < sizeof(op)) + { + return read_error(conn, EBADMSG); + } + if (!stream->read_all(stream, &op, sizeof(op))) + { + return read_error(conn, errno); + } + switch (op) + { + case VICI_EVENT: + return handle_event(conn, len); + case VICI_CMD_RESPONSE: + return handle_response(conn, len); + case VICI_EVENT_CONFIRM: + return wait_result(conn, WAIT_SUCCESS); + case VICI_CMD_UNKNOWN: + case VICI_EVENT_UNKNOWN: + return wait_result(conn, WAIT_FAILURE); + case VICI_CMD_REQUEST: + case VICI_EVENT_REGISTER: + case VICI_EVENT_UNREGISTER: + default: + return read_error(conn, EBADMSG); + } +} + +vici_conn_t* vici_connect(char *uri) +{ + vici_conn_t *conn; + stream_t *stream; + + stream = lib->streams->connect(lib->streams, uri ?: VICI_DEFAULT_URI); + if (!stream) + { + return NULL; + } + + INIT(conn, + .stream = stream, + .events = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .cond = condvar_create(CONDVAR_TYPE_DEFAULT), + ); + + stream->on_read(stream, on_read, conn); + + return conn; +} + +void vici_disconnect(vici_conn_t *conn) +{ + enumerator_t *enumerator; + event_t *event; + + conn->stream->destroy(conn->stream); + enumerator = conn->events->create_enumerator(conn->events); + while (enumerator->enumerate(enumerator, NULL, &event)) + { + free(event->name); + free(event); + } + enumerator->destroy(enumerator); + conn->events->destroy(conn->events); + conn->mutex->destroy(conn->mutex); + conn->cond->destroy(conn->cond); + free(conn); +} + +vici_req_t* vici_begin(char *name) +{ + vici_req_t *req; + + INIT(req, + .name = strdup(name), + .b = vici_builder_create(), + ); + + return req; +} + +void vici_begin_section(vici_req_t *req, char *name) +{ + req->b->add(req->b, VICI_SECTION_START, name); +} + +void vici_end_section(vici_req_t *req) +{ + req->b->add(req->b, VICI_SECTION_END); +} + +void vici_add_key_value(vici_req_t *req, char *key, void *buf, int len) +{ + req->b->add(req->b, VICI_KEY_VALUE, key, chunk_create(buf, len)); +} + +void vici_add_key_valuef(vici_req_t *req, char *key, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + req->b->vadd_kv(req->b, key, fmt, args); + va_end(args); +} + +void vici_begin_list(vici_req_t *req, char *name) +{ + req->b->add(req->b, VICI_LIST_START, name); +} + +void vici_add_list_item(vici_req_t *req, void *buf, int len) +{ + req->b->add(req->b, VICI_LIST_ITEM, chunk_create(buf, len)); +} + +void vici_add_list_itemf(vici_req_t *req, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + req->b->vadd_li(req->b, fmt, args); + va_end(args); +} + +void vici_end_list(vici_req_t *req) +{ + req->b->add(req->b, VICI_LIST_END); +} + +vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn) +{ + vici_message_t *message; + vici_res_t *res; + chunk_t data; + u_int32_t len; + u_int8_t namelen, op; + + message = req->b->finalize(req->b); + if (!message) + { + errno = EINVAL; + return NULL; + } + + op = VICI_CMD_REQUEST; + namelen = strlen(req->name); + data = message->get_encoding(message); + len = htonl(sizeof(op) + sizeof(namelen) + namelen + data.len); + + if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || + !conn->stream->write_all(conn->stream, &op, sizeof(op)) || + !conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) || + !conn->stream->write_all(conn->stream, req->name, namelen) || + !conn->stream->write_all(conn->stream, data.ptr, data.len)) + { + free(req->name); + free(req); + message->destroy(message); + return NULL; + } + free(req->name); + free(req); + message->destroy(message); + + message = NULL; + conn->mutex->lock(conn->mutex); + while (conn->wait == WAIT_IDLE) + { + conn->cond->wait(conn->cond, conn->mutex); + } + switch (conn->wait) + { + case WAIT_SUCCESS: + message = vici_message_create_from_data(conn->queue, TRUE); + conn->queue = chunk_empty; + break; + case WAIT_READ_ERROR: + errno = conn->error; + break; + case WAIT_FAILURE: + default: + errno = ENOENT; + break; + } + conn->wait = WAIT_IDLE; + conn->mutex->unlock(conn->mutex); + + conn->stream->on_read(conn->stream, on_read, conn); + + if (message) + { + INIT(res, + .message = message, + .enumerator = message->create_enumerator(message), + .strings = linked_list_create(), + ); + return res; + } + return NULL; +} + +void vici_free_req(vici_req_t *req) +{ + vici_message_t *message; + + free(req->name); + message = req->b->finalize(req->b); + if (message) + { + message->destroy(message); + } + free(req); +} + +int vici_dump(vici_res_t *res, char *label, bool pretty, FILE *out) +{ + if (res->message->dump(res->message, label, pretty, out)) + { + return 0; + } + errno = EBADMSG; + return 1; +} + +vici_parse_t vici_parse(vici_res_t *res) +{ + if (!res->enumerator->enumerate(res->enumerator, + &res->type, &res->name, &res->value)) + { + return VICI_PARSE_ERROR; + } + switch (res->type) + { + case VICI_END: + return VICI_PARSE_END; + case VICI_SECTION_START: + return VICI_PARSE_BEGIN_SECTION; + case VICI_SECTION_END: + return VICI_PARSE_END_SECTION; + case VICI_LIST_START: + return VICI_PARSE_BEGIN_LIST; + case VICI_LIST_ITEM: + return VICI_PARSE_LIST_ITEM; + case VICI_LIST_END: + return VICI_PARSE_END_LIST; + case VICI_KEY_VALUE: + return VICI_PARSE_KEY_VALUE; + default: + return VICI_PARSE_ERROR; + } +} + +char* vici_parse_name(vici_res_t *res) +{ + char *name; + + switch (res->type) + { + case VICI_SECTION_START: + case VICI_LIST_START: + case VICI_KEY_VALUE: + name = strdup(res->name); + res->strings->insert_last(res->strings, name); + return name; + default: + errno = EINVAL; + return NULL; + } +} + +int vici_parse_name_eq(vici_res_t *res, char *name) +{ + switch (res->type) + { + case VICI_SECTION_START: + case VICI_LIST_START: + case VICI_KEY_VALUE: + return streq(name, res->name) ? 1 : 0; + default: + return 0; + } +} + +void* vici_parse_value(vici_res_t *res, int *len) +{ + switch (res->type) + { + case VICI_LIST_ITEM: + case VICI_KEY_VALUE: + *len = res->value.len; + return res->value.ptr; + default: + *len = 0; + errno = EINVAL; + return NULL; + } +} + +char* vici_parse_value_str(vici_res_t *res) +{ + char *val; + + switch (res->type) + { + case VICI_LIST_ITEM: + case VICI_KEY_VALUE: + if (!chunk_printable(res->value, NULL, 0)) + { + errno = EBADMSG; + return NULL; + } + val = strndup(res->value.ptr, res->value.len); + res->strings->insert_last(res->strings, val); + return val; + default: + errno = EINVAL; + return NULL; + } +} + +int vici_parse_cb(vici_res_t *res, vici_parse_section_cb_t section, + vici_parse_value_cb_t kv, vici_parse_value_cb_t li, + void *user) +{ + char *name, *list = NULL; + void *value; + int base, len, ret; + + base = res->level; + + while (TRUE) + { + switch (vici_parse(res)) + { + case VICI_PARSE_KEY_VALUE: + if (res->level == base) + { + if (kv) + { + name = vici_parse_name(res); + value = vici_parse_value(res, &len); + if (name && value) + { + ret = kv(user, res, name, value, len); + if (ret) + { + return ret; + } + } + } + } + break; + case VICI_PARSE_BEGIN_SECTION: + if (res->level++ == base) + { + if (section) + { + name = vici_parse_name(res); + if (name) + { + ret = section(user, res, name); + if (ret) + { + return ret; + } + } + } + } + break; + case VICI_PARSE_END_SECTION: + if (res->level-- == base) + { + return 0; + } + break; + case VICI_PARSE_END: + res->level = 0; + return 0; + case VICI_PARSE_BEGIN_LIST: + if (res->level == base) + { + list = vici_parse_name(res); + } + break; + case VICI_PARSE_LIST_ITEM: + if (list && li) + { + value = vici_parse_value(res, &len); + if (value) + { + ret = li(user, res, list, value, len); + if (ret) + { + return ret; + } + } + } + break; + case VICI_PARSE_END_LIST: + if (res->level == base) + { + list = NULL; + } + break; + case VICI_PARSE_ERROR: + res->level = 0; + errno = EBADMSG; + return 1; + } + } +} + +void* vici_find(vici_res_t *res, int *len, char *fmt, ...) +{ + va_list args; + chunk_t value; + + va_start(args, fmt); + value = res->message->vget_value(res->message, chunk_empty, fmt, args); + va_end(args); + + *len = value.len; + return value.ptr; +} + +char* vici_find_str(vici_res_t *res, char *def, char *fmt, ...) +{ + va_list args; + char *str; + + va_start(args, fmt); + str = res->message->vget_str(res->message, def, fmt, args); + va_end(args); + + return str; +} + +int vici_find_int(vici_res_t *res, int def, char *fmt, ...) +{ + va_list args; + int val; + + va_start(args, fmt); + val = res->message->vget_int(res->message, def, fmt, args); + va_end(args); + + return val; +} + +void vici_free_res(vici_res_t *res) +{ + res->strings->destroy_function(res->strings, free); + res->message->destroy(res->message); + res->enumerator->destroy(res->enumerator); + free(res); +} + +int vici_register(vici_conn_t *conn, char *name, vici_event_cb_t cb, void *user) +{ + event_t *event; + u_int32_t len; + u_int8_t namelen, op; + int ret = 1; + + op = cb ? VICI_EVENT_REGISTER : VICI_EVENT_UNREGISTER; + namelen = strlen(name); + len = htonl(sizeof(op) + sizeof(namelen) + namelen); + if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || + !conn->stream->write_all(conn->stream, &op, sizeof(op)) || + !conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) || + !conn->stream->write_all(conn->stream, name, namelen)) + { + return 1; + } + + conn->mutex->lock(conn->mutex); + while (conn->wait == WAIT_IDLE) + { + conn->cond->wait(conn->cond, conn->mutex); + } + switch (conn->wait) + { + case WAIT_SUCCESS: + ret = 0; + break; + case WAIT_READ_ERROR: + errno = conn->error; + break; + case WAIT_FAILURE: + default: + errno = ENOENT; + break; + } + conn->wait = WAIT_IDLE; + conn->mutex->unlock(conn->mutex); + + conn->stream->on_read(conn->stream, on_read, conn); + + if (ret == 0) + { + conn->mutex->lock(conn->mutex); + if (cb) + { + INIT(event, + .name = strdup(name), + .cb = cb, + .user = user, + ); + event = conn->events->put(conn->events, event->name, event); + } + else + { + event = conn->events->remove(conn->events, name); + } + conn->mutex->unlock(conn->mutex); + + if (event) + { + free(event->name); + free(event); + } + } + return ret; +} + +void vici_init() +{ + library_init(NULL, "vici"); + if (lib->processor->get_total_threads(lib->processor) < 4) + { + lib->processor->set_threads(lib->processor, 4); + } +} + +void vici_deinit() +{ + library_deinit(); +} |