blob: c3327f5abd62e602b77efdd2a865f976f7d036dd [file] [log] [blame]
/**
* \file messages_server.c
* \author Michal Vasko <mvasko@cesnet.cz>
* \brief libnetconf2 - server NETCONF messages functions
*
* Copyright (c) 2015 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <libyang/libyang.h>
#include "session_server.h"
#include "libnetconf.h"
extern struct nc_server_opts server_opts;
API struct nc_server_reply *
nc_server_reply_ok(void)
{
struct nc_server_reply *ret;
ret = malloc(sizeof *ret);
if (!ret) {
ERRMEM;
return NULL;
}
ret->type = NC_RPL_OK;
return ret;
}
API struct nc_server_reply *
nc_server_reply_data(struct lyd_node *data, NC_PARAMTYPE paramtype)
{
struct nc_server_reply_data *ret;
if (!data) {
ERRARG("data");
return NULL;
}
ret = malloc(sizeof *ret);
if (!ret) {
ERRMEM;
return NULL;
}
ret->type = NC_RPL_DATA;
if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
ret->data = lyd_dup(data, 1);
} else {
ret->data = data;
}
if (paramtype != NC_PARAMTYPE_CONST) {
ret->free = 1;
} else {
ret->free = 0;
}
return (struct nc_server_reply *)ret;
}
API struct nc_server_reply *
nc_server_reply_err(struct nc_server_error *err)
{
struct nc_server_reply_error *ret;
if (!err) {
ERRARG("err");
return NULL;
}
ret = malloc(sizeof *ret);
if (!ret) {
ERRMEM;
return NULL;
}
ret->type = NC_RPL_ERROR;
ret->err = malloc(sizeof *ret->err);
if (!ret->err) {
ERRMEM;
free(ret);
return NULL;
}
ret->err[0] = err;
ret->count = 1;
return (struct nc_server_reply *)ret;
}
API int
nc_server_reply_add_err(struct nc_server_reply *reply, struct nc_server_error *err)
{
struct nc_server_reply_error *err_rpl;
if (!reply || (reply->type != NC_RPL_ERROR)) {
ERRARG("reply");
return -1;
} else if (!err) {
ERRARG("err");
return -1;
}
err_rpl = (struct nc_server_reply_error *)reply;
++err_rpl->count;
err_rpl->err = nc_realloc(err_rpl->err, err_rpl->count * sizeof *err_rpl->err);
if (!err_rpl->err) {
ERRMEM;
return -1;
}
err_rpl->err[err_rpl->count - 1] = err;
return 0;
}
API struct nc_server_error *
nc_err(NC_ERR tag, ...)
{
va_list ap;
struct nc_server_error *ret;
NC_ERR_TYPE type;
const char *arg1, *arg2;
uint32_t sid;
if (!tag) {
ERRARG("tag");
return NULL;
}
ret = calloc(1, sizeof *ret);
if (!ret) {
ERRMEM;
return NULL;
}
va_start(ap, tag);
switch (tag) {
case NC_ERR_IN_USE:
case NC_ERR_INVALID_VALUE:
case NC_ERR_ACCESS_DENIED:
case NC_ERR_ROLLBACK_FAILED:
case NC_ERR_OP_NOT_SUPPORTED:
type = va_arg(ap, NC_ERR_TYPE);
if ((type != NC_ERR_TYPE_PROT) && (type != NC_ERR_TYPE_APP)) {
ERRARG("type");
goto fail;
}
break;
case NC_ERR_TOO_BIG:
case NC_ERR_RES_DENIED:
type = va_arg(ap, NC_ERR_TYPE);
/* nothing to check */
break;
case NC_ERR_MISSING_ATTR:
case NC_ERR_BAD_ATTR:
case NC_ERR_UNKNOWN_ATTR:
type = va_arg(ap, NC_ERR_TYPE);
arg1 = va_arg(ap, const char *);
arg2 = va_arg(ap, const char *);
if (type == NC_ERR_TYPE_TRAN) {
ERRARG("type");
goto fail;
}
nc_err_add_bad_attr(ret, arg1);
nc_err_add_bad_elem(ret, arg2);
break;
case NC_ERR_MISSING_ELEM:
case NC_ERR_BAD_ELEM:
case NC_ERR_UNKNOWN_ELEM:
type = va_arg(ap, NC_ERR_TYPE);
arg1 = va_arg(ap, const char *);
if ((type != NC_ERR_TYPE_PROT) && (type != NC_ERR_TYPE_APP)) {
ERRARG("type");
goto fail;
}
nc_err_add_bad_elem(ret, arg1);
break;
case NC_ERR_UNKNOWN_NS:
type = va_arg(ap, NC_ERR_TYPE);
arg1 = va_arg(ap, const char *);
arg2 = va_arg(ap, const char *);
if ((type != NC_ERR_TYPE_PROT) && (type != NC_ERR_TYPE_APP)) {
ERRARG("type");
goto fail;
}
nc_err_add_bad_elem(ret, arg1);
nc_err_add_bad_ns(ret, arg2);
break;
case NC_ERR_LOCK_DENIED:
sid = va_arg(ap, uint32_t);
type = NC_ERR_TYPE_PROT;
nc_err_set_sid(ret, sid);
break;
case NC_ERR_DATA_EXISTS:
case NC_ERR_DATA_MISSING:
type = NC_ERR_TYPE_APP;
break;
case NC_ERR_OP_FAILED:
type = va_arg(ap, NC_ERR_TYPE);
if (type == NC_ERR_TYPE_TRAN) {
ERRARG("type");
goto fail;
}
break;
case NC_ERR_MALFORMED_MSG:
type = NC_ERR_TYPE_RPC;
break;
default:
ERRARG("tag");
goto fail;
}
switch (tag) {
case NC_ERR_IN_USE:
nc_err_set_msg(ret, "The request requires a resource that already is in use.", "en");
break;
case NC_ERR_INVALID_VALUE:
nc_err_set_msg(ret, "The request specifies an unacceptable value for one or more parameters.", "en");
break;
case NC_ERR_TOO_BIG:
nc_err_set_msg(ret, "The request or response (that would be generated) is too large for the implementation to handle.", "en");
break;
case NC_ERR_MISSING_ATTR:
nc_err_set_msg(ret, "An expected attribute is missing.", "en");
break;
case NC_ERR_BAD_ATTR:
nc_err_set_msg(ret, "An attribute value is not correct.", "en");
break;
case NC_ERR_UNKNOWN_ATTR:
nc_err_set_msg(ret, "An unexpected attribute is present.", "en");
break;
case NC_ERR_MISSING_ELEM:
nc_err_set_msg(ret, "An expected element is missing.", "en");
break;
case NC_ERR_BAD_ELEM:
nc_err_set_msg(ret, "An element value is not correct.", "en");
break;
case NC_ERR_UNKNOWN_ELEM:
nc_err_set_msg(ret, "An unexpected element is present.", "en");
break;
case NC_ERR_UNKNOWN_NS:
nc_err_set_msg(ret, "An unexpected namespace is present.", "en");
break;
case NC_ERR_ACCESS_DENIED:
nc_err_set_msg(ret, "Access to the requested protocol operation or data model is denied because authorization failed.", "en");
break;
case NC_ERR_LOCK_DENIED:
nc_err_set_msg(ret, "Access to the requested lock is denied because the lock is currently held by another entity.", "en");
break;
case NC_ERR_RES_DENIED:
nc_err_set_msg(ret, "Request could not be completed because of insufficient resources.", "en");
break;
case NC_ERR_ROLLBACK_FAILED:
nc_err_set_msg(ret, "Request to roll back some configuration change was not completed for some reason.", "en");
break;
case NC_ERR_DATA_EXISTS:
nc_err_set_msg(ret, "Request could not be completed because the relevant data model content already exists.", "en");
break;
case NC_ERR_DATA_MISSING:
nc_err_set_msg(ret, "Request could not be completed because the relevant data model content does not exist.", "en");
break;
case NC_ERR_OP_NOT_SUPPORTED:
nc_err_set_msg(ret, "Request could not be completed because the requested operation is not supported by this implementation.", "en");
break;
case NC_ERR_OP_FAILED:
nc_err_set_msg(ret, "Request could not be completed because the requested operation failed for a non-specific reason.", "en");
break;
case NC_ERR_MALFORMED_MSG:
nc_err_set_msg(ret, "A message could not be handled because it failed to be parsed correctly.", "en");
break;
default:
ERRARG("tag");
goto fail;
}
va_end(ap);
ret->type = type;
ret->tag = tag;
return ret;
fail:
va_end(ap);
free(ret);
return NULL;
}
static struct lyxml_elem *
nc_err_libyang_other_elem(const char *name, const char *content, int cont_len)
{
struct lyxml_elem *root = NULL;
struct lyxml_ns *ns;
root = calloc(1, sizeof *root);
if (!root) {
ERRMEM;
goto error;
}
root->prev = root;
root->name = lydict_insert(server_opts.ctx, name, 0);
root->content = lydict_insert(server_opts.ctx, content, cont_len);
ns = calloc(1, sizeof *root->ns);
if (!ns) {
ERRMEM;
goto error;
}
root->attr = (struct lyxml_attr *)ns;
ns->type = LYXML_ATTR_NS;
ns->parent = root;
ns->value = lydict_insert(server_opts.ctx, "urn:ietf:params:xml:ns:yang:1", 0);
root->ns = ns;
return root;
error:
lyxml_free(server_opts.ctx, root);
return NULL;
}
API struct nc_server_error *
nc_err_libyang(void)
{
struct nc_server_error *e;
struct lyxml_elem *elem;
const char *str, *stri, *strj, *strk, *strl, *uniqi, *uniqj;
char *attr, *path;
int len;
if (!ly_errno) {
/* LY_SUCCESS */
return NULL;
} else if (ly_errno == LY_EVALID) {
switch (ly_vecode) {
/* RFC 6020 section 13 errors */
case LYVE_NOUNIQ:
e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
nc_err_set_app_tag(e, "data-not-unique");
nc_err_set_path(e, ly_errpath());
/* parse the message and get all the information we need */
str = ly_errmsg();
uniqi = strchr(str, '"');
uniqi++;
uniqj = strchr(uniqi, '"');
stri = strchr(uniqj + 1, '"');
stri++;
strj = strchr(stri, '"');
strk = strchr(strj + 1, '"');
++strk;
strl = strchr(strk, '"');
/* maximum length is the whole unique string with the longer list instance identifier */
len = (uniqj - uniqi) + (strj - stri > strl - strk ? strj - stri : strl - strk);
path = malloc(len + 1);
if (!path) {
ERRMEM;
return e;
}
/* create non-unique elements, one in 1st list, one in 2nd list, for each unique list */
while (1) {
uniqj = strpbrk(uniqi, " \"");
len = sprintf(path, "%.*s/%.*s", (int)(strj - stri), stri, (int)(uniqj - uniqi), uniqi);
elem = nc_err_libyang_other_elem("non-unique", path, len);
if (!elem) {
free(path);
return e;
}
nc_err_add_info_other(e, elem);
len = sprintf(path, "%.*s/%.*s", (int)(strl - strk), strk, (int)(uniqj - uniqi), uniqi);
elem = nc_err_libyang_other_elem("non-unique", path, len);
if (!elem) {
return e;
}
nc_err_add_info_other(e, elem);
if (uniqj[0] == '"') {
break;
}
uniqi = uniqj + 1;
}
free(path);
break;
case LYVE_NOMAX:
e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
nc_err_set_app_tag(e, "too-many-elements");
nc_err_set_path(e, ly_errpath());
break;
case LYVE_NOMIN:
e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
nc_err_set_app_tag(e, "too-few-elements");
nc_err_set_path(e, ly_errpath());
break;
case LYVE_NOMUST:
e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
if (ly_errapptag()[0]) {
nc_err_set_app_tag(e, ly_errapptag());
} else {
nc_err_set_app_tag(e, "must-violation");
}
break;
case LYVE_NOREQINS:
case LYVE_NOLEAFREF:
e = nc_err(NC_ERR_DATA_MISSING);
nc_err_set_app_tag(e, "instance-required");
nc_err_set_path(e, ly_errpath());
break;
case LYVE_NOMANDCHOICE:
e = nc_err(NC_ERR_DATA_MISSING);
nc_err_set_app_tag(e, "missing-choice");
nc_err_set_path(e, ly_errpath());
str = ly_errmsg();
stri = strchr(str, '"');
stri++;
strj = strchr(stri, '"');
elem = nc_err_libyang_other_elem("missing-choice", stri, strj - stri);
if (elem) {
nc_err_add_info_other(e, elem);
}
break;
case LYVE_INELEM:
str = ly_errpath();
if (!strcmp(str, "/")) {
e = nc_err(NC_ERR_OP_NOT_SUPPORTED, NC_ERR_TYPE_APP);
/* keep default message */
return e;
} else {
e = nc_err(NC_ERR_UNKNOWN_ELEM, NC_ERR_TYPE_PROT, ly_errpath());
}
break;
case LYVE_MISSELEM:
case LYVE_INORDER:
e = nc_err(NC_ERR_MISSING_ELEM, NC_ERR_TYPE_PROT, ly_errpath());
break;
case LYVE_INVAL:
e = nc_err(NC_ERR_BAD_ELEM, NC_ERR_TYPE_PROT, ly_errpath());
break;
case LYVE_INATTR:
case LYVE_MISSATTR:
case LYVE_INVALATTR:
str = ly_errmsg();
stri = strchr(str, '"');
stri++;
strj = strchr(stri, '"');
strj--;
attr = strndup(stri, strj - stri);
if (ly_vecode == LYVE_INATTR) {
e = nc_err(NC_ERR_UNKNOWN_ATTR, NC_ERR_TYPE_PROT, attr, ly_errpath());
} else if (ly_vecode == LYVE_MISSATTR) {
e = nc_err(NC_ERR_MISSING_ATTR, NC_ERR_TYPE_PROT, attr, ly_errpath());
} else { /* LYVE_INVALATTR */
e = nc_err(NC_ERR_BAD_ATTR, NC_ERR_TYPE_PROT, attr, ly_errpath());
}
free(attr);
break;
case LYVE_NOCONSTR:
case LYVE_NOWHEN:
e = nc_err(NC_ERR_INVALID_VALUE, NC_ERR_TYPE_PROT);
/* LYVE_NOCONSTR (length, range, pattern) can have a specific error-app-tag */
if (ly_errapptag()[0]) {
nc_err_set_app_tag(e, ly_errapptag());
}
break;
default:
e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
break;
}
} else {
/* non-validation (internal) error */
e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
}
nc_err_set_msg(e, ly_errmsg(), "en");
return e;
}
API NC_ERR_TYPE
nc_err_get_type(struct nc_server_error *err)
{
if (!err) {
ERRARG("err");
return 0;
}
return err->type;
}
API NC_ERR
nc_err_get_tag(struct nc_server_error *err)
{
if (!err) {
ERRARG("err");
return 0;
}
return err->tag;
}
API int
nc_err_set_app_tag(struct nc_server_error *err, const char *error_app_tag)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!error_app_tag) {
ERRARG("error_app_tag");
return -1;
}
if (err->apptag) {
lydict_remove(server_opts.ctx, err->apptag);
}
err->apptag = lydict_insert(server_opts.ctx, error_app_tag, 0);
return 0;
}
API const char *
nc_err_get_app_tag(struct nc_server_error *err)
{
if (!err) {
ERRARG("err");
return NULL;
}
return err->apptag;
}
API int
nc_err_set_path(struct nc_server_error *err, const char *error_path)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!error_path) {
ERRARG("error_path");
return -1;
}
if (err->path) {
lydict_remove(server_opts.ctx, err->path);
}
err->path = lydict_insert(server_opts.ctx, error_path, 0);
return 0;
}
API const char *
nc_err_get_path(struct nc_server_error *err)
{
if (!err) {
ERRARG("err");
return 0;
}
return err->path;
}
API int
nc_err_set_msg(struct nc_server_error *err, const char *error_message, const char *lang)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!error_message) {
ERRARG("error_message");
return -1;
}
if (err->message) {
lydict_remove(server_opts.ctx, err->message);
}
err->message = lydict_insert(server_opts.ctx, error_message, 0);
if (err->message_lang) {
lydict_remove(server_opts.ctx, err->message_lang);
}
if (lang) {
err->message_lang = lydict_insert(server_opts.ctx, lang, 0);
} else {
lang = NULL;
}
return 0;
}
API const char *
nc_err_get_msg(struct nc_server_error *err)
{
if (!err) {
ERRARG("err");
return 0;
}
return err->message;
}
API int
nc_err_set_sid(struct nc_server_error *err, uint32_t session_id)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!session_id) {
ERRARG("session_id");
return -1;
}
err->sid = session_id;
return 0;
}
API int
nc_err_add_bad_attr(struct nc_server_error *err, const char *attr_name)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!attr_name) {
ERRARG("attr_name");
return -1;
}
++err->attr_count;
err->attr = nc_realloc(err->attr, err->attr_count * sizeof *err->attr);
if (!err->attr) {
ERRMEM;
return -1;
}
err->attr[err->attr_count - 1] = lydict_insert(server_opts.ctx, attr_name, 0);
return 0;
}
API int
nc_err_add_bad_elem(struct nc_server_error *err, const char *elem_name)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!elem_name) {
ERRARG("elem_name");
return -1;
}
++err->elem_count;
err->elem = nc_realloc(err->elem, err->elem_count * sizeof *err->elem);
if (!err->elem) {
ERRMEM;
return -1;
}
err->elem[err->elem_count - 1] = lydict_insert(server_opts.ctx, elem_name, 0);
return 0;
}
API int
nc_err_add_bad_ns(struct nc_server_error *err, const char *ns_name)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!ns_name) {
ERRARG("ns_name");
return -1;
}
++err->ns_count;
err->ns = nc_realloc(err->ns, err->ns_count * sizeof *err->ns);
if (!err->ns) {
ERRMEM;
return -1;
}
err->ns[err->ns_count - 1] = lydict_insert(server_opts.ctx, ns_name, 0);
return 0;
}
API int
nc_err_add_info_other(struct nc_server_error *err, struct lyxml_elem *other)
{
if (!err) {
ERRARG("err");
return -1;
} else if (!other) {
ERRARG("other");
return -1;
}
++err->other_count;
err->other = nc_realloc(err->other, err->other_count * sizeof *err->other);
if (!err->other) {
ERRMEM;
return -1;
}
err->other[err->other_count - 1] = other;
return 0;
}
void
nc_server_rpc_free(struct nc_server_rpc *rpc, struct ly_ctx *ctx)
{
if (!rpc) {
return;
}
lyxml_free(ctx, rpc->root);
lyd_free(rpc->tree);
free(rpc);
}
API void
nc_server_reply_free(struct nc_server_reply *reply)
{
uint32_t i;
struct nc_server_reply_data *data_rpl;
struct nc_server_reply_error *error_rpl;
if (!reply) {
return;
}
switch (reply->type) {
case NC_RPL_DATA:
data_rpl = (struct nc_server_reply_data *)reply;
if (data_rpl->free) {
lyd_free_withsiblings(data_rpl->data);
}
break;
case NC_RPL_OK:
/* nothing to free */
break;
case NC_RPL_ERROR:
error_rpl = (struct nc_server_reply_error *)reply;
for (i = 0; i < error_rpl->count; ++i) {
nc_err_free(error_rpl->err[i]);
}
free(error_rpl->err);
break;
default:
break;
}
free(reply);
}
API void
nc_err_free(struct nc_server_error *err)
{
uint32_t i;
if (!err) {
return;
}
lydict_remove(server_opts.ctx, err->apptag);
lydict_remove(server_opts.ctx, err->path);
lydict_remove(server_opts.ctx, err->message);
lydict_remove(server_opts.ctx, err->message_lang);
for (i = 0; i < err->attr_count; ++i) {
lydict_remove(server_opts.ctx, err->attr[i]);
}
free(err->attr);
for (i = 0; i < err->elem_count; ++i) {
lydict_remove(server_opts.ctx, err->elem[i]);
}
free(err->elem);
for (i = 0; i < err->ns_count; ++i) {
lydict_remove(server_opts.ctx, err->ns[i]);
}
free(err->ns);
for (i = 0; i < err->other_count; ++i) {
lyxml_free(server_opts.ctx, err->other[i]);
}
free(err->other);
free(err);
}