clixon/apps/backend/backend_client.c
Corey Minyard 25f7c56c0a backend: Handle RPC errors
Use the new RPC error reporting interface to report RPC errors from
plugins.

Signed-off-by: Corey Minyard <minyard@acm.org>
2025-01-30 15:21:24 +01:00

2079 lines
71 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* note: there is also client code in clixon_backend_handle.c
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <inttypes.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include <clixon/clixon.h>
#include "clixon_backend_client.h"
#include "clixon_backend_plugin.h"
#include "clixon_backend_commit.h"
#include "backend_handle.h"
#include "backend_get.h"
#include "backend_client.h"
/*! Find client by session-id
*
* @param[in] ce_list List of clients
* @param[in] id Session id
*/
static struct client_entry *
ce_find_byid(struct client_entry *ce_list,
uint32_t id)
{
struct client_entry *ce;
for (ce = ce_list; ce; ce = ce->ce_next)
if (ce->ce_id == id)
return ce;
return NULL;
}
/*! Construct a client string description from client_entry information for logging
*
* @param[in] ce Client entry struct
* @param[out] cbp Cligen buffer, deallocate with cbuf_free
* @retval 0 OK
* @retval -1 Error
*/
static int
ce_client_descr(struct client_entry *ce,
cbuf **cbp)
{
int retval = -1;
cbuf *cb = NULL;
char *id = NULL;
if (ce == NULL || cbp == NULL){
clixon_err(OE_UNIX, EINVAL, "ce or cbp is NULL");
goto done;
}
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (ce->ce_transport){
if (nodeid_split(ce->ce_transport, NULL, &id) < 0)
goto done;
cprintf(cb, "%s:", id);
}
cprintf(cb, "%u", ce->ce_id);
*cbp = cb;
retval = 0;
done:
if (id)
free(id);
return retval;
}
/*! Stream callback for netconf stream notification (RFC 5277)
*
* @param[in] h Clixon handle
* @param[in] op 0:event, 1:rm
* @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_ss_add
* @retval 0 OK
* @retval -1 Error
* @see stream_ss_add
*/
int
ce_event_cb(clixon_handle h,
int op,
cxobj *event,
void *arg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
cbuf *cbce = NULL;
clixon_debug(CLIXON_DBG_BACKEND, "op:%d", op);
switch (op){
case 1:
/* Risk of recursion here */
if (ce->ce_s)
backend_client_rm(h, ce);
break;
default:
if (ce_client_descr(ce, &cbce) < 0)
goto done;
if (send_msg_notify_xml(h, ce->ce_s, cbuf_get(cbce), event) < 0){
if (errno == ECONNRESET || errno == EPIPE){
clixon_log(h, LOG_WARNING, "client %d reset", ce->ce_nr);
}
break;
}
/* note there may be other notifications than RFC5277 streams */
ce->ce_out_notifications++;
netconf_monitoring_counter_inc(h, "out-notifications");
}
retval = 0;
done:
if (cbce)
cbuf_free(cbce);
return retval;
}
/*! Unlock all db:s of a client and call user unlock calback
*
* @param[in] h Clixon handle
* @param[in] id Session id
* @see xmldb_unlock_all unlocks, but does not call user callbacks which is a backend thing
*/
static int
release_all_dbs(clixon_handle h,
uint32_t id)
{
int retval = -1;
char **keys = NULL;
size_t klen;
int i;
db_elmnt *de;
if (xmldb_islocked(h, "candidate") == id){
if (clicon_option_bool(h, "CLICON_AUTOLOCK")){
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
xmldb_modified_set(h, "candidate", 0); /* reset dirty bit */
}
}
/* get all db:s */
if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0)
goto done;
/* Identify the ones locked by client id */
for (i = 0; i < klen; i++) {
if ((de = clicon_db_elmnt_get(h, keys[i])) != NULL &&
de->de_id == id){
de->de_id = 0; /* unlock */
clicon_db_elmnt_set(h, keys[i], de);
if (clixon_plugin_lockdb_all(h, keys[i], 0, id) < 0)
goto done;
}
}
retval = 0;
done:
if (keys)
free(keys);
return retval;
}
/*! Get backend-specific client netconf monitoring state
*
* Backend-specific netconf monitoring state is:
* sessions
* @param[in] h Clixon handle
* @param[in] yspec Yang spec
* @param[in] xpath XML Xpath
* @param[in] nsc XML Namespace context for xpath
* @param[in,out] xret Existing XML tree, merge x into this
* @param[out] xerr XML error tree, if retval = 0
* @retval 1 OK
* @retval 0 Statedata callback failed, error in xerr
* @retval -1 Error (fatal)
* @see RFC 6022
*/
int
backend_monitoring_state_get(clixon_handle h,
yang_stmt *yspec,
char *xpath,
cvec *nsc,
cxobj **xret,
cxobj **xerr)
{
int retval = -1;
cbuf *cb = NULL;
struct client_entry *ce;
char timestr[28];
int ret;
if ((cb = cbuf_new()) ==NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<netconf-state xmlns=\"%s\">", NETCONF_MONITORING_NAMESPACE);
cprintf(cb, "<sessions>");
for (ce = backend_client_list(h); ce; ce = ce->ce_next){
cprintf(cb, "<session>");
cprintf(cb, "<session-id>%u</session-id>", ce->ce_id);
if (ce->ce_transport == NULL){
#ifdef NOTYET // XXX: too strict, race conditions in clixon_snmp
clixon_err(OE_XML, 0, "Mandatory element transport missing");
goto done;
#else
cprintf(cb, "<transport xmlns:%s=\"%s\">cl:netconf</transport>",
CLIXON_LIB_PREFIX, CLIXON_LIB_NS);
#endif
}
else
cprintf(cb, "<transport xmlns:%s=\"%s\">%s</transport>",
CLIXON_LIB_PREFIX, CLIXON_LIB_NS,
ce->ce_transport);
cprintf(cb, "<username>%s</username>", ce->ce_username);
if (ce->ce_source_host)
cprintf(cb, "<source-host>%s</source-host>", ce->ce_source_host);
if (ce->ce_time.tv_sec != 0){
if (time2str(&ce->ce_time, timestr, sizeof(timestr)) < 0){
clixon_err(OE_UNIX, errno, "time2str");
goto done;
}
cprintf(cb, "<login-time>%s</login-time>", timestr);
}
cprintf(cb, "<in-rpcs>%u</in-rpcs>", ce->ce_in_rpcs);
cprintf(cb, "<in-bad-rpcs>%u</in-bad-rpcs>", ce->ce_in_bad_rpcs);
cprintf(cb, "<out-rpc-errors>%u</out-rpc-errors>", ce->ce_out_rpc_errors);
cprintf(cb, "<out-notifications>%u</out-notifications>", ce->ce_out_notifications);
cprintf(cb, "</session>");
}
cprintf(cb, "</sessions>");
cprintf(cb, "</netconf-state>");
if ((ret = clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
retval = 1;
done:
clixon_debug(CLIXON_DBG_BACKEND, "retval:%d", retval);
if (cb)
cbuf_free(cb);
return retval;
fail:
retval = 0;
goto done;
}
/*! Remove client entry state
*
* Close down everything wrt clients (eg sockets, subscriptions)
* Finally actually remove client struct in handle
* @param[in] h Clixon handle
* @param[in] ce Client handle
* @retval 0 Ok
* @retval -1 Error (fatal)
* @see backend_client_add for adding
* @see backend_client_delete for actual deallocation of client entry struct
*/
int
backend_client_rm(clixon_handle h,
struct client_entry *ce)
{
struct client_entry *c;
struct client_entry *c0;
struct client_entry **ce_prev;
uint32_t myid = ce->ce_id;
yang_stmt *yspec;
/* If the confirmed-commit feature is enabled, rollback any ephemeral commit originated by this client */
if ((yspec = clicon_dbspec_yang(h)) != NULL) {
if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) {
if (confirmed_commit_state_get(h) == EPHEMERAL) {
/* See if this client is the origin */
clixon_debug(CLIXON_DBG_BACKEND, "session_id: %u, confirmed_commit.session_id: %u", ce->ce_id, confirmed_commit_session_id_get(h));
if (myid == confirmed_commit_session_id_get(h)) {
clixon_debug(CLIXON_DBG_BACKEND, "ok, rolling back");
clixon_log(h, LOG_NOTICE, "a client with an active ephemeral confirmed-commit has disconnected; rolling back");
/* do_rollback errors are logged internally and there is no client to report errors to, so errors are
* ignored here.
*/
cancel_rollback_event(h);
do_rollback(h, NULL);
}
}
}
}
clixon_debug(CLIXON_DBG_BACKEND, "");
/* for all streams: XXX better to do it top-level? */
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
c0 = backend_client_list(h);
ce_prev = &c0; /* this points to stack and is not real backpointer */
for (c = *ce_prev; c; c = c->ce_next){
if (c == ce){
if (ce->ce_s){
clixon_event_unreg_fd(ce->ce_s, from_client);
close(ce->ce_s);
ce->ce_s = 0;
if (release_all_dbs(h, ce->ce_id) < 0)
return -1;
}
break;
}
ce_prev = &c->ce_next;
}
return backend_client_delete(h, ce); /* actually purge it */
}
/*! Get clixon per datastore stats
*
* @param[in] h Clixon handle
* @param[in] dbname Datastore name
* @param[in,out] cb Cligen buf
* @retval 0 OK
* @retval -1 Error
*/
static int
clixon_stats_datastore_get(clixon_handle h,
char *dbname,
cbuf *cb)
{
int retval = -1;
cxobj *xt = NULL; /* should not be freed */
uint64_t nr = 0;
size_t sz = 0;
cxobj *xn = NULL;
int ret;
clixon_debug(CLIXON_DBG_BACKEND | CLIXON_DBG_DETAIL, "%s", dbname);
/* This is the db cache */
if ((xt = xmldb_cache_get(h, dbname)) == NULL){
/* Trigger cache if no exist (trick to ensure cache is present) */
if ((ret = xmldb_get0(h, dbname, YB_MODULE, NULL, "/", 1, 0, &xn, NULL, NULL)) < 0)
//goto done;
goto ok;
if (ret == 0)
goto ok;
xt = xmldb_cache_get(h, dbname);
}
if (xt != NULL){
if (xml_stats(xt, &nr, &sz) < 0)
goto done;
cprintf(cb, "<datastore><name>%s</name><nr>%" PRIu64 "</nr>"
"<size>%zu</size></datastore>",
dbname, nr, sz);
}
ok:
retval = 0;
done:
if (xn)
xml_free(xn);
return retval;
}
/*! Get clixon per yang-spec stats
*
* @param[in] h Clixon handle
* @param[in] dbname Datastore name
* @param[in,out] cb Cligen buf
* @retval 0 OK
* @retval -1 Error
*/
static int
clixon_stats_yang_get(clixon_handle h,
yang_stmt *ys,
cbuf *cb)
{
int retval = -1;
uint64_t nr = 0;
size_t sz = 0;
cxobj *xn = NULL;
if (ys == NULL)
return 0;
if (yang_stats(ys, 0, &nr, &sz) < 0)
goto done;
cprintf(cb, "<nr>%" PRIu64 "</nr><size>%zu</size>", nr, sz);
retval = 0;
done:
if (xn)
xml_free(xn);
return retval;
}
/*! Do lock checks and lock
* @param[in] h Clixon handle
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] db Datastore
* @retval 1 OK
* @retval 0 Failed
* @retval -1 Error
*/
static int
do_lock(clixon_handle h,
cbuf *cbret,
uint32_t id,
char *db)
{
int retval = -1;
uint32_t otherid;
cbuf *cbx = NULL; /* Assist cbuf */
yang_stmt *yspec;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clixon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if ((cbx = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* 2) The target configuration is <candidate>, it has already been modified, and
* these changes have not been committed or rolled back.
*/
if (strcmp(db, "candidate") == 0) {
if (xmldb_exists(h, db) && xmldb_modified_get(h, db)){
if (netconf_lock_denied(cbret, "<session-id>0</session-id>",
"Operation failed, candidate has already been modified and the changes have not been committed or rolled back (RFC 6241 7.5)") < 0)
goto done;
goto failed;
}
}
/* 3) The target configuration is <running>, and another NETCONF
* session has an ongoing confirmed commit
*/
if (strcmp(db, "running") == 0 &&
if_feature(yspec, "ietf-netconf", "confirmed-commit") &&
confirmed_commit_state_get(h) != INACTIVE){
if ((otherid = confirmed_commit_session_id_get(h)) != 0){
cprintf(cbx, "<session-id>%u</session-id>", otherid);
if (netconf_lock_denied(cbret, cbuf_get(cbx),
"Operation failed, another session has an ongoing confirmed commit") < 0)
goto done;
goto failed;
}
}
if (xmldb_lock(h, db, id) < 0)
goto done;
if (strcmp(db, "candidate") == 0) {
/* Add system-only config to candidate cache */
if (clicon_option_bool(h, "CLICON_XMLDB_SYSTEM_ONLY_CONFIG")){
if (system_only_data_add(h, "candidate") < 0)
goto done;
}
}
/* user callback */
if (clixon_plugin_lockdb_all(h, db, 1, id) < 0)
goto done;
retval = 1;
done:
if (cbx)
cbuf_free(cbx);
return retval;
failed:
retval = 0;
goto done;
}
/*! Loads all or part of a specified configuration to target configuration
*
* @param[in] h Clixon handle
* @param[in] xn Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
* @note According to RFC 7950 8.3: constraints MUST be enforced:
* o during parsing of RPC payloads
* o during processing of the <edit-config> operation
* But Clixon is flexible in allowing invalid XML payload here, only some specialized
* validations are made.
* However, what really should be done is to apply the change to the datastore and then
* validate, if error, discard to previous state.
* But this could discard other previous changes to candidate.
*/
static int
from_client_edit_config(clixon_handle h,
cxobj *xn,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
uint32_t myid = ce->ce_id;
uint32_t iddb;
char *target;
cxobj *xc;
cxobj *x;
enum operation_type operation = OP_MERGE;
int non_config = 0;
yang_stmt *yspec;
cbuf *cbx = NULL; /* Assist cbuf */
int ret;
char *username;
cxobj *xret = NULL;
char *attr;
int autocommit = 0;
char *val = NULL;
cvec *nsc = NULL;
char *prefix = NULL;
username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clixon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
if ((target = netconf_db_find(xn, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
if ((cbx = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Check if target locked by other client */
iddb = xmldb_islocked(h, target);
if (iddb && myid != iddb){
cprintf(cbx, "<session-id>%u</session-id>", iddb);
if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0)
goto done;
goto ok;
}
if (clicon_option_bool(h, "CLICON_AUTOLOCK")){
if ((ret = do_lock(h, cbret, myid, target)) < 0)
goto done;
if (ret == 0)
goto ok;
}
if (strcmp(target, "candidate") == 0) {
/* Add system-only config to candidate cache */
if (clicon_option_bool(h, "CLICON_XMLDB_SYSTEM_ONLY_CONFIG")){
if (system_only_data_add(h, "candidate") < 0)
goto done;
}
}
if (xml_nsctx_node(xn, &nsc) < 0)
goto done;
/* Get prefix of netconf base namespace in the incoming message */
if (xml_nsctx_get_prefix(nsc, NETCONF_BASE_NAMESPACE, &prefix) == 0){
cprintf(cbx, "No appropriate prefix exists for: %s", NETCONF_BASE_NAMESPACE);
if (netconf_unknown_namespace(cbret, "protocol", xml_name(xn), cbuf_get(cbx)) < 0)
goto done;
goto ok;
}
/* Get default-operation element */
if ((x = xpath_first(xn, nsc, "%s%sdefault-operation", prefix?prefix:"", prefix?":":"")) != NULL){
if (xml_operation(xml_body(x), &operation) < 0){
if (netconf_invalid_value(cbret, "protocol", "Wrong operation")< 0)
goto done;
goto ok;
}
}
/* Get config element */
if ((xc = xpath_first(xn, nsc, "%s%s%s",
prefix?prefix:"",
prefix?":":"",
NETCONF_INPUT_CONFIG)) == NULL){
cprintf(cbx, "Element not found, or mismatching prefix %s for namespace %s",
prefix?prefix:"null", NETCONF_BASE_NAMESPACE);
if (netconf_missing_element(cbret, "protocol", NETCONF_INPUT_CONFIG, cbuf_get(cbx)) < 0)
goto done;
goto ok;
}
/* <config> yang spec may be set to anyxml by ingress yang check,...*/
if (xml_spec(xc) != NULL)
xml_spec_set(xc, NULL);
/* Populate XML with Yang spec. Binding is done in from_client_msg only frm an RPC perspective,
* where <config> is ANYDATA
*/
if ((ret = xml_bind_yang(h, xc, YB_MODULE, yspec, &xret)) < 0)
goto done;
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
goto ok;
}
/* (Mark all nodes that are not configure data and) set return */
if ((ret = xml_non_config_data(xc, &xret)) < 0)
goto done;
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
goto ok;
}
if (non_config){
if (netconf_invalid_value(cbret, "protocol", "State data not allowed")< 0)
goto done;
goto ok;
}
/* Limited validation of incoming payload
*/
if ((ret = xml_yang_validate_minmax(xc, 1, &xret)) < 0)
goto done;
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
goto ok;
}
/* Must do before duplicate check */
if (xml_sort_recurse(xc) < 0)
goto done;
/* Disable duplicate check in NETCONF messages. */
if (clicon_option_bool(h, "CLICON_NETCONF_DUPLICATE_ALLOW")){
if ((ret = xml_duplicate_detect(xc, 1, NULL)) < 0)
goto done;
}
else {
if ((ret = xml_duplicate_detect(xc, 0, &xret)) < 0)
goto done;
}
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
goto ok;
}
/* xmldb_put (difflist handling) requires list keys */
if ((ret = xml_yang_validate_list_key_only(xc, &xret)) < 0)
goto done;
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
goto ok;
}
if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){
if (netconf_operation_failed(cbret, "protocol", clixon_err_reason())< 0)
goto done;
goto ok;
}
if (ret == 0)
goto ok;
xmldb_modified_set(h, target, 1); /* mark as dirty */
/* Clixon extension: autocommit */
if ((attr = xml_find_value(xn, "autocommit")) != NULL &&
strcmp(attr,"true") == 0)
autocommit = 1;
/* If autocommit option is set or requested by client */
if (clicon_autocommit(h) || autocommit) {
/* if this is from a restconf client ...
* and, if there is an existing ephemeral commit, set is_valid_confirming_commit=1 such that
* candidate_commit will apply the configuration per RFC 8040 1.4:
* If a confirmed commit procedure is
* in progress by any NETCONF client, then any new commit will act as
* the confirming commit.
* and, if there is an existing persistent commit, netconf_operation_failed with "in-use", so
* that the restconf server will return "409 Conflict" per RFC 8040 1.4:
* If the NETCONF server is expecting a
* "persist-id" parameter to complete the confirmed commit procedure,
* then the RESTCONF edit operation MUST fail with a "409 Conflict"
* status-line. The error-tag "in-use" is used in this case.
*/
if (if_feature(yspec, "ietf-netconf", "confirmed-commit")) {
switch (confirmed_commit_state_get(h)){
case INACTIVE:
break;
case PERSISTENT:
if (netconf_in_use(cbret, "application", "Persistent commit is ongoing")< 0)
goto done;
goto ok;
break;
case EPHEMERAL:
case ROLLBACK:
cancel_confirmed_commit(h);
break;
}
}
if ((ret = candidate_commit(h, NULL, "candidate", myid, 0, cbret)) < 0){ /* Assume validation fail, nofatal */
if (clixon_plugin_report_err(h, cbret) < 0)
goto done;
xmldb_copy(h, "running", "candidate");
goto ok;
}
if (ret == 0){ /* discard */
if (xmldb_copy(h, "running", "candidate") < 0){
if (netconf_operation_failed(cbret, "application", clixon_err_reason())< 0)
goto done;
goto ok;
}
goto ok;
}
}
/* Clixon extension: copy */
if ((attr = xml_find_value(xn, "copystartup")) != NULL &&
strcmp(attr, "true") == 0){
if (xmldb_copy(h, "running", "startup") < 0){
if (netconf_operation_failed(cbret, "application", clixon_err_reason())< 0)
goto done;
goto ok;
}
}
if (cbuf_len(cbret) != 0){
clixon_err(OE_NETCONF, EINVAL, "Internal error: cbret is not empty");
goto done;
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok", NETCONF_BASE_NAMESPACE);
// Set in text_modify
if (clicon_data_get(h, "objectexisted", &val) == 0)
cprintf(cbret, " %s:objectexisted=\"%s\" xmlns:%s=\"%s\"",
CLIXON_LIB_PREFIX, val,
CLIXON_LIB_PREFIX, CLIXON_LIB_NS);
cprintf(cbret, "/></rpc-reply>");
ok:
retval = 0;
done:
if (nsc)
cvec_free(nsc);
if (xret)
xml_free(xret);
if (cbx)
cbuf_free(cbx);
clixon_debug(CLIXON_DBG_BACKEND, "done cbret:%s", cbuf_get(cbret));
return retval;
} /* from_client_edit_config */
/*! Create or replace an entire config with another complete config db
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
* NACM: If source running and target startup --> only exec permission
* else:
* - omit data nodes to which the client does not have read access
* - access denied if user lacks create/delete/update
*/
static int
from_client_copy_config(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
char *source;
char *target;
uint32_t iddb;
uint32_t myid = ce->ce_id;
cbuf *cbx = NULL; /* Assist cbuf */
cbuf *cbmsg = NULL;
int ret;
if ((source = netconf_db_find(xe, "source")) == NULL){
if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
goto done;
goto ok;
}
if ((cbx = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if ((target = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
/* Check if target locked by other client */
iddb = xmldb_islocked(h, target);
if (iddb && myid != iddb){
cprintf(cbx, "<session-id>%u</session-id>", iddb);
if (netconf_lock_denied(cbret, cbuf_get(cbx), "Copy failed, lock is already held") < 0)
goto done;
goto ok;
}
if (clicon_option_bool(h, "CLICON_AUTOLOCK")){
if ((ret = do_lock(h, cbret, myid, target)) < 0)
goto done;
if (ret == 0)
goto ok;
}
if (xmldb_copy(h, source, target) < 0){
if ((cbmsg = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cbmsg, "Copy %s datastore to %s: %s", source, target, clixon_err_reason());
if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg))< 0)
goto done;
goto ok;
}
if (strcmp(target, "candidate") == 0){
xmldb_modified_set(h, target, 1); /* mark as dirty */
/* Add system-only config to candidate */
if (clicon_option_bool(h, "CLICON_XMLDB_SYSTEM_ONLY_CONFIG")){
if (system_only_data_add(h, "candidate") < 0)
goto done;
}
}
/* Remove system-only-config data from destination cache */
if (strcmp(source, "candidate") == 0){
if (clicon_option_bool(h, "CLICON_XMLDB_SYSTEM_ONLY_CONFIG")){
xmldb_clear(h, target);
}
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
ok:
retval = 0;
done:
if (cbmsg)
cbuf_free(cbmsg);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Delete a configuration datastore.
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_delete_config(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
char *target;
uint32_t iddb;
uint32_t myid = ce->ce_id;
cbuf *cbx = NULL; /* Assist cbuf */
cbuf *cbmsg = NULL;
/* XXX should use prefix cf edit_config */
if ((target = netconf_db_find(xe, "target")) == NULL ||
strcmp(target, "running")==0){
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
if ((cbx = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Check if target locked by other client */
iddb = xmldb_islocked(h, target);
if (iddb && myid != iddb){
cprintf(cbx, "<session-id>%u</session-id>", iddb);
if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0)
goto done;
goto ok;
}
if (xmldb_delete(h, target) < 0){
if ((cbmsg = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cbmsg, "Delete %s datastore: %s", target, clixon_err_reason());
if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0)
goto done;
goto ok;
}
if (xmldb_create(h, target) < 0){
if ((cbmsg = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cbmsg, "Create %s datastore: %s", target, clixon_err_reason());
if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0)
goto done;
goto ok;
}
xmldb_modified_set(h, target, 1); /* mark as dirty */
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
ok:
retval = 0;
done:
if (cbmsg)
cbuf_free(cbmsg);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Lock the configuration system of a device
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_lock(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
uint32_t id = ce->ce_id;
char *db;
int ret;
cbuf *cbx = NULL; /* Assist cbuf */
uint32_t iddb;
if ((db = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
if ((cbx = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/*
* A lock MUST not be granted if either of the following conditions is true:
* 1) A lock is already held by any NETCONF session or another entity.
*/
if ((iddb = xmldb_islocked(h, db)) != 0){
cprintf(cbx, "<session-id>%u</session-id>", iddb);
if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0)
goto done;
goto ok;
}
if ((ret = do_lock(h, cbret, id, db)) < 0)
goto done;
if (ret == 0)
goto ok;
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
ok:
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Release a configuration lock previously obtained with the 'lock' operation
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*
* RFC 6241, 8.3.5.2: To facilitate easy recovery, any outstanding changes are discarded when the
* lock is released, whether explicitly with the <unlock> operation or implicitly from session
* failure. (XXX This is not implemented)
*/
static int
from_client_unlock(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
uint32_t id = ce->ce_id;
uint32_t iddb; /* DBs lock, if any */
char *db;
cbuf *cbx = NULL; /* Assist cbuf */
if ((db = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
if ((cbx = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
iddb = xmldb_islocked(h, db);
/*
* An unlock operation will not succeed if any of the following
* conditions are true:
* 1) the specified lock is not currently active
*/
if (iddb == 0){
cprintf(cbx, "<session-id>0</session-id>");
if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock is not currently active") < 0)
goto done;
goto ok;
}
/* 2) the session issuing the <unlock> operation is not the same
* session that obtained the lock
*/
else if (iddb != id){
cprintf(cbx, "<session-id>%u</session-id>", iddb);
if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock held by other session") < 0)
goto done;
goto ok;
}
else{
xmldb_unlock(h, db);
/* user callback */
if (clixon_plugin_lockdb_all(h, db, 0, id) < 0)
goto done;
if (cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE) < 0)
goto done;
}
ok:
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Request graceful termination of a NETCONF session.
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_close_session(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
struct client_entry *ce = (struct client_entry *)arg;
uint32_t id = ce->ce_id;
if (release_all_dbs(h, id) < 0)
return -1;
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
return 0;
}
/*! Internal message: Force the termination of a NETCONF session.
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_kill_session(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
uint32_t id; /* session id */
char *str = NULL;
struct client_entry *ce;
cxobj *x;
int ret;
char *reason = NULL;
if ((x = xml_find(xe, "session-id")) == NULL ||
(str = xml_find_value(x, "body")) == NULL){
if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0)
goto done;
goto ok;
}
if ((ret = netconf_parse_uint32("session-id", str, NULL, 0, cbret, &id)) < 0)
goto done;
if (ret == 0)
goto ok;
/* may or may not be in active client list, probably not */
if ((ce = ce_find_byid(backend_client_list(h), id)) != NULL){
backend_client_rm(h, ce); /* Removes client struct */
}
if (release_all_dbs(h, id) < 0)
goto done;
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
ok:
retval = 0;
done:
if (reason)
free(reason);
return retval;
}
/*! Create a notification subscription
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
* @see RFC5277 2.1
* @example:
* <create-subscription>
* <stream>RESULT</stream> # If not present, events in the default NETCONF stream will be sent.
* <filter type="xpath" select="XPATH-EXPR"/>
* <startTime></startTime>
* <stopTime></stopTime>
* </create-subscription>
*/
static int
from_client_create_subscription(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
char *stream = "NETCONF";
cxobj *x; /* Generic xml tree */
cxobj *xfilter; /* Filter xml tree */
char *ftype;
char *starttime = NULL;
char *stoptime = NULL;
char *selector = NULL;
struct timeval start;
struct timeval stop;
cvec *nsc = NULL;
/* XXX should use prefix cf edit_config */
if ((nsc = xml_nsctx_init(NULL, EVENT_RFC5277_NAMESPACE)) == NULL)
goto done;
if ((x = xpath_first(xe, nsc, "stream")) != NULL){
if ((stream = xml_find_value(x, "body")) == NULL){
if (netconf_bad_element(cbret, "application", "stream", "Expected stream name") < 0)
goto done;
goto ok;
}
}
if ((x = xpath_first(xe, nsc, "stopTime")) != NULL){
if ((stoptime = xml_find_value(x, "body")) != NULL &&
str2time(stoptime, &stop) < 0){
if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0)
goto done;
goto ok;
}
}
if ((x = xpath_first(xe, nsc, "startTime")) != NULL){
if ((starttime = xml_find_value(x, "body")) != NULL &&
str2time(starttime, &start) < 0){
if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0)
goto done;
goto ok;
}
}
if ((xfilter = xpath_first(xe, nsc, "filter")) != NULL){
if ((ftype = xml_find_value(xfilter, "type")) != NULL){
/* Only accept xpath as filter type */
if (strcmp(ftype, "xpath") != 0){
if (netconf_operation_failed(cbret, "application", "Only xpath filter type supported")< 0)
goto done;
goto ok;
}
if ((selector = xml_find_value(xfilter, "select")) == NULL)
goto done;
}
}
if ((stream_find(h, stream)) == NULL){
if (netconf_invalid_value(cbret, "application", "No such stream") < 0)
goto done;
goto ok;
}
/* Add subscriber to stream - to make notifications for this client */
if (stream_ss_add(h, stream, selector,
starttime?&start:NULL, stoptime?&stop:NULL,
ce_event_cb, (void*)ce) < 0)
goto done;
/* Replay of this stream to specific subscription according to start and
* stop (if present).
* RFC 5277: If <startTime> is not present, this is not a replay
* subscription.
* Schedule the replay to occur right after this RPC completes, eg "now"
*/
if (starttime){
if (stream_replay_trigger(h, stream, ce_event_cb, (void*)ce) < 0)
goto done;
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
ok:
retval = 0;
done:
if (nsc)
xml_nsctx_free(nsc);
return retval;
}
/*! Retrieve a schema from the NETCONF server.
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
* @see RFC6022, ietf-netconf-monitoring.yang
*/
static int
from_client_get_schema(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
cxobj *x; /* Generic xml tree */
cvec *nsc = NULL;
char *identifier = NULL;
char *version = NULL;
char *format = NULL;
yang_stmt *yspec;
yang_stmt *ymod;
yang_stmt *ymatch;
yang_stmt *yrev;
cbuf *cbyang = NULL;
cbuf *cbmsg = NULL;
const char *filename;
int inext;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clixon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if ((nsc = xml_nsctx_init(NULL, NETCONF_MONITORING_NAMESPACE)) == NULL)
goto done;
if ((x = xpath_first(xe, nsc, "identifier")) == NULL){
if (netconf_missing_element(cbret, "protocol", "identifier", NULL) < 0)
goto done;
goto ok;
}
identifier = xml_body(x);
if ((x = xpath_first(xe, nsc, "version")) != NULL)
version = xml_body(x);
if ((x = xpath_first(xe, nsc, "format")) != NULL)
format = xml_body(x);
ymatch = NULL;
inext = 0;
while ((ymod = yn_iter(yspec, &inext)) != NULL) {
if (yang_keyword_get(ymod) != Y_MODULE &&
yang_keyword_get(ymod) != Y_SUBMODULE)
continue;
if (strcmp(identifier, yang_argument_get(ymod)) != 0)
continue;
if (version){
if ((yrev = yang_find(ymod, Y_REVISION, NULL)) == NULL)
continue;
if (strcmp(version, yang_argument_get(yrev)) != 0)
continue;
ymatch = ymod;
break;
}
else if (ymatch){
/* If more than one schema matches the requested parameters, the
* <error-tag> is 'operation-failed', and <error-app-tag> is
* 'data-not-unique'.
*/
if (netconf_data_not_unique(cbret, NULL, NULL)< 0)
goto done;
goto ok;
}
else
ymatch = ymod;
}
if (ymatch == NULL){
if ((cbmsg = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (version)
cprintf(cbmsg, "No schema matching: %s@%s", identifier, version);
else
cprintf(cbmsg, "No schema matching: %s", identifier);
if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbmsg)) < 0)
goto done;
goto ok;
}
if (format && strcmp(format, "yang") != 0){
if ((cbmsg = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cbmsg, "Format not supported: %s", format);
if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbmsg)) < 0)
goto done;
goto ok;
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><data xmlns=\"%s\">",
NETCONF_BASE_NAMESPACE, NETCONF_MONITORING_NAMESPACE);
if ((cbyang = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((filename = yang_filename_get(ymatch)) != NULL){
if (clicon_file_cbuf(filename, cbyang) < 0)
goto done;
}
xml_chardata_cbuf_append(cbret, 0, cbuf_get(cbyang));
cprintf(cbret, "</data></rpc-reply>");
ok:
retval = 0;
done:
if (cbmsg)
cbuf_free(cbmsg);
if (cbyang)
cbuf_free(cbyang);
if (nsc)
xml_nsctx_free(nsc);
return retval;
}
/*! Set debug level.
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_debug(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
uint32_t level;
char *valstr;
if ((valstr = xml_find_body(xe, "level")) == NULL){
if (netconf_missing_element(cbret, "application", "level", NULL) < 0)
goto done;
goto ok;
}
level = atoi(valstr);
clixon_debug_init(h, level); /* 0: dont debug, 1:debug */
setlogmask(LOG_UPTO(level?LOG_DEBUG:LOG_INFO)); /* for syslog */
clixon_log(h, LOG_NOTICE, "%s debug:%d", __FUNCTION__, clixon_debug_get());
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
ok:
retval = 0;
done:
return retval;
}
/*! Check liveness of backend daemon, just send a reply
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_ping(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
return 0;
}
/*! Check liveness of backend daemon, just send a reply
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_stats(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
uint64_t nr;
char *str;
int modules = 0;
yang_stmt *yspec0;
yang_stmt *ymounts;
yang_stmt *ydomain;
yang_stmt *yspec;
yang_stmt *ymodule;
cxobj *xt = NULL;
char *domain;
int inext;
int inext2;
int inext3;
if ((str = xml_find_body(xe, "modules")) != NULL)
modules = strcmp(str, "true") == 0;
yspec0 = clicon_dbspec_yang(h);
cprintf(cbret, "<rpc-reply xmlns=\"%s\">", NETCONF_BASE_NAMESPACE);
cprintf(cbret, "<global xmlns=\"%s\">", CLIXON_LIB_NS);
nr=0;
xml_stats_global(&nr);
cprintf(cbret, "<xmlnr>%" PRIu64 "</xmlnr>", nr);
nr=0;
yang_stats_global(&nr);
cprintf(cbret, "<yangnr>%" PRIu64 "</yangnr>", nr);
cprintf(cbret, "</global>");
cprintf(cbret, "<datastores xmlns=\"%s\">", CLIXON_LIB_NS);
if (clixon_stats_datastore_get(h, "running", cbret) < 0)
goto done;
if (clixon_stats_datastore_get(h, "candidate", cbret) < 0)
goto done;
if (if_feature(yspec0, "ietf-netconf", "startup"))
if (clixon_stats_datastore_get(h, "startup", cbret) < 0)
goto done;
cprintf(cbret, "</datastores>");
if ((ymounts = clixon_yang_mounts_get(h)) == NULL){
clixon_err(OE_YANG, ENOENT, "Top-level yang mounts not found");
goto done;
}
cprintf(cbret, "<module-sets xmlns=\"%s\">", CLIXON_LIB_NS);
inext = 0;
while ((ydomain = yn_iter(ymounts, &inext)) != NULL) {
domain = yang_argument_get(ydomain);
/* per module-set, first configuration, then main dbspec, then mountpoints */
inext2 = 0;
while ((yspec = yn_iter(ydomain, &inext2)) != NULL) {
cprintf(cbret, "<module-set>");
cprintf(cbret, "<name>%s/%s</name>", domain, yang_argument_get(yspec));
if (clixon_stats_yang_get(h, yspec, cbret) < 0)
goto done;
if (modules){
inext3 = 0;
while ((ymodule = yn_iter(yspec, &inext3)) != NULL) {
cprintf(cbret, "<module><name>%s</name>", yang_argument_get(ymodule));
if (clixon_stats_yang_get(h, ymodule, cbret) < 0)
goto done;
cprintf(cbret, "</module>");
}
}
cprintf(cbret, "</module-set>");
}
}
cprintf(cbret, "</module-sets>");
cprintf(cbret, "</rpc-reply>");
retval = 0;
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Request restart of specific plugins
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_restart_plugin(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
char *name;
cxobj **vec = NULL;
size_t veclen;
int i;
clixon_plugin_t *cp;
int ret;
if (xpath_vec(xe, NULL, "plugin", &vec, &veclen) < 0)
goto done;
for (i=0; i<veclen; i++){
name = xml_body(vec[i]);
if ((cp = clixon_plugin_find(h, name)) == NULL){
if (netconf_bad_element(cbret, "application", "plugin", "No such plugin") < 0)
goto done;
goto ok;
}
if ((ret = from_client_restart_one(h, cp, cbret)) < 0)
goto done;
if (ret == 0)
goto ok; /* cbret set */
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
ok:
retval = 0;
done:
if (vec)
free(vec);
return retval;
}
/*! Control a specific process or daemon: start/stop, etc
*
* @param[in] h Clixon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_process_control(clixon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
cxobj *x;
char *name = NULL;
char *opstr = NULL;
proc_operation op = PROC_OP_NONE;
if ((x = xml_find_type(xe, NULL, "name", CX_ELMNT)) != NULL)
name = xml_body(x);
if ((x = xml_find_type(xe, NULL, "operation", CX_ELMNT)) != NULL){
opstr = xml_body(x);
op = clixon_process_op_str2int(opstr);
}
/* Make the actual process operation (with wrap function enabled) */
if (op == PROC_OP_STATUS){
if (clixon_process_status(h, name, cbret) < 0)
goto done;
}
else{
if (clixon_process_operation(h, name, op, 1) < 0)
goto done;
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok xmlns=\"%s\"/></rpc-reply>",
NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS);
}
retval = 0;
done:
return retval;
}
/*! Clixon hello to check liveness
*
* @param[in] h Clixon handle
* @param[in] x Incoming XML of hello request
* @param[in] ce Client entry (from)
* @param[out] cbret Hello reply
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_hello(clixon_handle h,
cxobj *x,
struct client_entry *ce,
cbuf *cbret)
{
int retval = -1;
char *val;
if ((val = xml_find_type_value(x, "cl", "transport", CX_ATTR)) != NULL){
if ((ce->ce_transport = strdup(val)) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if ((val = xml_find_type_value(x, "cl", "source-host", CX_ATTR)) != NULL){
if ((ce->ce_source_host = strdup(val)) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
cprintf(cbret, "<hello xmlns=\"%s\"><session-id>%u</session-id></hello>",
NETCONF_BASE_NAMESPACE, ce->ce_id);
retval = 0;
done:
return retval;
}
/*! An internal clixon NETCONF message has arrived from a local client. Receive and dispatch.
*
* @param[in] h Clixon handle
* @param[in] ce Client entry (from)
* @param[in] msg Incoming message
* @retval 0 OK
* @retval -1 Error Terminates backend and is never called). Instead errors are
* propagated back to client.
*/
static int
from_client_msg(clixon_handle h,
struct client_entry *ce,
char *msg)
{
int retval = -1;
cxobj *xt = NULL;
cxobj *x;
cxobj *xe;
char *rpc = NULL;
char *module = NULL;
cbuf *cbret = NULL; /* return message */
int ret;
char *username;
yang_stmt *yspec;
yang_stmt *ye;
yang_stmt *ymod;
cxobj *xnacm = NULL;
cxobj *xret = NULL;
enum nacm_credentials_t creds;
char *rpcname;
char *rpcprefix;
char *namespace = NULL;
int nr = 0;
cbuf *cbce = NULL;
clixon_debug(CLIXON_DBG_BACKEND | CLIXON_DBG_DETAIL, "");
yspec = clicon_dbspec_yang(h);
/* Return netconf message. Should be filled in by the dispatch(sub) functions
* as wither rpc-error or by positive response.
*/
if ((cbret = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Decode msg from client -> xml top (ct) and session id
* Bind is a part of the decode function
*/
if ((ret = clixon_xml_parse_string(msg, YB_RPC, yspec, &xt, &xret)) < 0){
if (netconf_malformed_message(cbret, "XML parse error") < 0)
goto done;
goto reply;
}
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
goto reply;
}
/* Check for empty frame (no mesaages), return empty message, not clear from RFC what to do */
if (xml_child_nr_type(xt, CX_ELMNT) == 0){
if (netconf_malformed_message(cbret, "Empty message in netconf rpc frame")< 0)
goto done;
goto reply;
}
/* Check for multi-messages in frame */
if (xml_child_nr_type(xt, CX_ELMNT) != 1){
if (netconf_malformed_message(cbret, "More than one message in netconf rpc frame")< 0)
goto done;
goto reply;
}
if ((x = xml_child_i_type(xt, 0, CX_ELMNT)) == NULL){ /* Shouldnt happen */
clixon_err(OE_XML, EFAULT, "No xml req (shouldnt happen)");
goto done;
}
rpcname = xml_name(x);
rpcprefix = xml_prefix(x);
#ifdef NOTACTIVE /* May need to re-activate */
/* Sanity check:
* op_id from internal message can be out-of-sync from client's sessions-id for the following reasons:
* 1. Its a hello when the client starts with op_id=0 to get its proper id on hello reply
* 2. The backend has restarted and the client uses an old op_id
* 3. Its a create-subscription message that uses a separate socket(=client)
*/
if (op_id != 0 && ce->ce_id != op_id && strcmp(rpcname, "create-subscription")){
client_entry *ce0;
clixon_debug(CLIXON_DBG_BACKEND, "Warning: incoming session-id:%u does not match ce_id:%u on socket: %d", op_id, ce->ce_id, ce->ce_s);
/* Copy transport from orig client-entry */
if (ce->ce_transport == NULL &&
(ce0 = ce_find_byid(backend_client_list(h), op_id)) != NULL &&
ce0->ce_transport){
if ((ce->ce_transport = strdup(ce0->ce_transport)) == NULL){
clixon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
}
#endif
/* Note that this validation is also made in xml_yang_validate_rpc, but not for hello
*/
if (xml2ns(x, rpcprefix, &namespace) < 0)
goto done;
/* Only accept resolved NETCONF base namespace */
if (namespace == NULL){
if (netconf_bad_element(cbret, "protocol", rpcname, "No namespace associated with prefix") < 0)
goto done;
goto reply;
}
else if (strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){
cbuf *cbmsg = NULL;
if ((cbmsg = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cbmsg, "No appropriate namespace found for: %s %s", rpcprefix, rpcname);
if (netconf_unknown_namespace(cbret, "protocol", namespace, cbuf_get(cbmsg)) < 0)
goto done;
cbuf_free(cbmsg);
goto reply;
}
if (strcmp(rpcname, "rpc") == 0){
}
else if (strcmp(rpcname, "hello") == 0){
if ((ret = from_client_hello(h, x, ce, cbret)) <0)
goto done;
goto reply;
}
else{
if (netconf_unknown_element(cbret, "protocol", rpcname, "Unrecognized netconf operation")< 0)
goto done;
ce->ce_in_bad_rpcs++;
ce->ce_out_rpc_errors++; /* Number of <rpc-reply> messages sent that contained an <rpc-error> */
netconf_monitoring_counter_inc(h, "in-bad-rpcs");
netconf_monitoring_counter_inc(h, "out-rpc-errors");
goto reply;
}
/* As a side-effect, this expands xt with default values according to "report-all"
* This may not be correct, the RFC does not mention expanding default values for
* input RPC
*/
if ((ret = xml_yang_validate_rpc(h, x, 1, &xret)) < 0){
ce->ce_in_bad_rpcs++;
netconf_monitoring_counter_inc(h, "in-bad-rpcs");
goto done;
}
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
ce->ce_in_bad_rpcs++;
netconf_monitoring_counter_inc(h, "in-bad-rpcs");
goto reply;
}
ce->ce_in_rpcs++; /* Track all RPCs */
netconf_monitoring_counter_inc(h, "in-rpcs");
xe = NULL;
username = xml_find_value(x, "username");
/* May be used by callbacks, etc */
clicon_username_set(h, username);
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
rpc = xml_name(xe);
if ((ye = xml_spec(xe)) == NULL){
if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0)
goto done;
ce->ce_out_rpc_errors++;
netconf_monitoring_counter_inc(h, "out-rpc-errors");
goto reply;
}
if ((ymod = ys_module(ye)) == NULL){
clixon_err(OE_XML, ENOENT, "rpc yang does not have module");
goto done;
}
module = yang_argument_get(ymod);
clixon_debug(CLIXON_DBG_BACKEND, "module:%s rpc:%s ce_id:%u s:%d", module,
rpc, ce->ce_id, ce->ce_s);
/* Pre-NACM access step */
xnacm = NULL;
/* NACM intial pre- access control enforcements. Retval:
* 0: nacm declaration error
* 1: Use NACM validation and xnacm is set.
* 2: Permit, skip NACM
* Therefore, xnacm=NULL means no NACM checks needed.
*/
if ((ret = nacm_access_pre(h, ce->ce_username, username, &xnacm, cbret)) < 0)
goto done;
if (ret == 2)
goto reply;
/* Cache XML NACM tree here. Use with caution, only valid on from_client_msg stack
*/
if (clicon_nacm_cache_set(h, xnacm) < 0)
goto done;
if (ret == 0){ /* Do NACM RPC validation */
creds = clicon_nacm_credentials(h);
if ((ret = verify_nacm_user(h, creds, ce->ce_username, username, rpc, cbret)) < 0)
goto done;
if (ret == 0){ /* credentials fail */
ce->ce_out_rpc_errors++;
netconf_monitoring_counter_inc(h, "out-rpc-errors");
goto reply;
}
/* NACM rpc operation exec validation */
if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0){ /* Not permitted and cbret set */
ce->ce_out_rpc_errors++;
netconf_monitoring_counter_inc(h, "out-rpc-errors");
goto reply;
}
}
clixon_err_reset();
if ((ret = rpc_callback_call(h, xe, ce, &nr, cbret)) < 0){
if (netconf_operation_failed(cbret, "application", clixon_err_reason())< 0)
goto done;
clixon_log(h, LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe));
ce->ce_out_rpc_errors++;
netconf_monitoring_counter_inc(h, "out-rpc-errors");
goto reply; /* Dont quit here on user callbacks */
}
if (ret == 0){
ce->ce_out_rpc_errors++;
netconf_monitoring_counter_inc(h, "out-rpc-errors");
goto reply;
}
if (nr == 0){ /* not handled by callback */
if (netconf_operation_not_supported(cbret, "application", "RPC operation not supported")< 0)
goto done;
ce->ce_out_rpc_errors++;
netconf_monitoring_counter_inc(h, "out-rpc-errors");
goto reply;
}
if (xnacm){
xml_free(xnacm);
xnacm = NULL;
if (clicon_nacm_cache_set(h, NULL) < 0)
goto done;
}
} /* while */
reply:
if (cbuf_len(cbret) == 0)
if (netconf_operation_failed(cbret, "application",
clixon_err_category()?clixon_err_reason():"unknown")< 0)
goto done;
// XXX clixon_debug(CLIXON_DBG_MSG, "Reply:%s", cbuf_get(cbret));
/* XXX problem here is that cbret has not been parsed so may contain
parse errors */
if (ce_client_descr(ce, &cbce) < 0)
goto done;
if (send_msg_reply(ce->ce_s, cbuf_get(cbce), cbuf_get(cbret), cbuf_len(cbret)+1) < 0){
switch (errno){
case EPIPE:
/* man (2) write:
* EPIPE fd is connected to a pipe or socket whose reading end is
* closed. When this happens the writing process will also receive
* a SIGPIPE signal.
* In Clixon this means a client, eg restconf, netconf or cli closes
* the (UNIX domain) socket.
*/
case ECONNRESET:
clixon_log(h, LOG_WARNING, "client rpc reset");
break;
default:
goto done;
}
}
// ok:
retval = 0;
done:
clixon_debug(CLIXON_DBG_BACKEND | CLIXON_DBG_DETAIL, "retval:%d", retval);
if (xnacm){
xml_free(xnacm);
if (clicon_nacm_cache_set(h, NULL) < 0)
goto done;
}
if (xret)
xml_free(xret);
if (xt)
xml_free(xt);
if (cbce)
cbuf_free(cbce);
if (cbret)
cbuf_free(cbret);
/* Sanity: log if clixon_err() is not called ! */
if (retval < 0 && clixon_err_category() < 0)
clixon_log(h, LOG_NOTICE, "%s: Internal error: No clixon_err call on RPC error (message: %s)",
__FUNCTION__, rpc?rpc:"");
// clixon_debug(CLIXON_DBG_BACKEND, "retval:%d", retval);
return retval;// -1 here terminates backend
}
/*! Internal clixon message has arrived from a client. Receive and dispatch.
*
* Internal clixon is NETCONF 1.1 chunked encoding
* @param[in] s Socket where message arrived. read from this.
* @param[in] arg Client entry (from).
* @retval 0 OK
* @retval -1 Error Terminates backend and is never called). Instead errors are
* propagated back to client.
*/
int
from_client(int s,
void* arg)
{
int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
clixon_handle h = ce->ce_handle;
int eof = 0;
cbuf *cbce = NULL;
cbuf *cb = NULL;
clixon_debug(CLIXON_DBG_BACKEND | CLIXON_DBG_DETAIL, "");
if (s != ce->ce_s){
clixon_err(OE_NETCONF, EINVAL, "Internal error: s != ce->ce_s");
goto done;
}
if (ce_client_descr(ce, &cbce) < 0)
goto done;
if (clixon_msg_rcv11(s, cbuf_get(cbce), 0, &cb, &eof) < 0) /* XXX why not descr here? */
goto done;
if (eof){
backend_client_rm(h, ce);
netconf_monitoring_counter_inc(h, "dropped-sessions");
}
else if (from_client_msg(h, ce, cbuf_get(cb)) < 0)
goto done;
retval = 0;
done:
clixon_debug(CLIXON_DBG_BACKEND | CLIXON_DBG_DETAIL, "retval:%d", retval);
if (cb)
cbuf_free(cb);
if (cbce)
cbuf_free(cbce);
return retval; /* -1 here terminates backend */
}
/*! Init backend rpc: Set up standard netconf rpc callbacks
*
* @param[in] h Clixon handle
* @retval 0 OK
* @retval -1 Error (fatal)
* @see ietf-netconf@2011-06-01.yang
*/
int
backend_rpc_init(clixon_handle h)
{
int retval = -1;
/* In backend_client.? RFC 6241 */
if (rpc_callback_register(h, from_client_get_config, NULL,
NETCONF_BASE_NAMESPACE, "get-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_edit_config, NULL,
NETCONF_BASE_NAMESPACE, "edit-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_copy_config, NULL,
NETCONF_BASE_NAMESPACE, "copy-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_delete_config, NULL,
NETCONF_BASE_NAMESPACE, "delete-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_lock, NULL,
NETCONF_BASE_NAMESPACE, "lock") < 0)
goto done;
if (rpc_callback_register(h, from_client_unlock, NULL,
NETCONF_BASE_NAMESPACE, "unlock") < 0)
goto done;
if (rpc_callback_register(h, from_client_get, NULL,
NETCONF_BASE_NAMESPACE, "get") < 0)
goto done;
if (rpc_callback_register(h, from_client_close_session, NULL,
NETCONF_BASE_NAMESPACE, "close-session") < 0)
goto done;
if (rpc_callback_register(h, from_client_kill_session, NULL,
NETCONF_BASE_NAMESPACE, "kill-session") < 0)
goto done;
if (rpc_callback_register(h, action_callback_call, NULL,
YANG_XML_NAMESPACE, "action") < 0)
goto done;
/* In backend_commit.? */
if (rpc_callback_register(h, from_client_commit, NULL,
NETCONF_BASE_NAMESPACE, "commit") < 0)
goto done;
if (rpc_callback_register(h, from_client_discard_changes, NULL,
NETCONF_BASE_NAMESPACE, "discard-changes") < 0)
goto done;
/* if-feature confirmed-commit */
if (rpc_callback_register(h, from_client_cancel_commit, NULL,
NETCONF_BASE_NAMESPACE, "cancel-commit") < 0)
goto done;
/* if-feature validate */
if (rpc_callback_register(h, from_client_validate, NULL,
NETCONF_BASE_NAMESPACE, "validate") < 0)
goto done;
/* In backend_client.? RPC from RFC 5277 */
if (rpc_callback_register(h, from_client_create_subscription, NULL,
EVENT_RFC5277_NAMESPACE, "create-subscription") < 0)
goto done;
/* RFC 6022 */
if (rpc_callback_register(h, from_client_get_schema, NULL,
NETCONF_MONITORING_NAMESPACE, "get-schema") < 0)
goto done;
/* Clixon RPC */
if (rpc_callback_register(h, from_client_debug, NULL,
CLIXON_LIB_NS, "debug") < 0)
goto done;
if (rpc_callback_register(h, from_client_ping, NULL,
CLIXON_LIB_NS, "ping") < 0)
goto done;
if (rpc_callback_register(h, from_client_stats, NULL,
CLIXON_LIB_NS, "stats") < 0)
goto done;
if (rpc_callback_register(h, from_client_restart_plugin, NULL,
CLIXON_LIB_NS, "restart-plugin") < 0)
goto done;
if (rpc_callback_register(h, from_client_process_control, NULL,
CLIXON_LIB_NS, "process-control") < 0)
goto done;
retval =0;
done:
return retval;
}