/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren Copyright (C) 2017-2019 Olof Hagsand Copyright (C) 2020-2021 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 ***** */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clicon */ #include #include "clixon_backend_handle.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; } /*! Stream callback for netconf stream notification (RFC 5277) * @param[in] h Clicon handle * @param[in] op 0:event, 1:rm * @param[in] event Event as XML * @param[in] arg Extra argument provided in stream_ss_add * @see stream_ss_add */ int ce_event_cb(clicon_handle h, int op, cxobj *event, void *arg) { struct client_entry *ce = (struct client_entry *)arg; clicon_debug(1, "%s op:%d", __FUNCTION__, op); switch (op){ case 1: /* Risk of recursion here */ if (ce->ce_s) backend_client_rm(h, ce); break; default: if (send_msg_notify_xml(h, ce->ce_s, event) < 0){ if (errno == ECONNRESET || errno == EPIPE){ clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr); } break; } } return 0; } /*! Unlock all db:s of a client and call user unlock calback * @see xmldb_unlock_all unlocks, but does not call user callbacks which is a backend thing */ static int release_all_dbs(clicon_handle h, uint32_t id) { int retval = -1; char **keys = NULL; size_t klen; int i; db_elmnt *de; /* 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; } /*! Remove client entry state * Close down everything wrt clients (eg sockets, subscriptions) * Finally actually remove client struct in handle * @param[in] h Clicon handle * @param[in] ce Client handle * @see backend_client_delete for actual deallocation of client entry struct */ int backend_client_rm(clicon_handle h, struct client_entry *ce) { struct client_entry *c; struct client_entry *c0; struct client_entry **ce_prev; clicon_debug(1, "%s", __FUNCTION__); /* 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 Clicon handle * @param[in] dbname Datastore name * @param[in,out] cb Cligen buf * @retval 0 OK * @retval -1 Error */ static int clixon_stats_get_db(clicon_handle h, char *dbname, cbuf *cb) { int retval = -1; cxobj *xt = NULL; uint64_t nr = 0; size_t sz = 0; /* This is the db cache */ if ((xt = xmldb_cache_get(h, dbname)) == NULL){ cprintf(cb, "%s00", dbname); } else{ if (xml_stats(xt, &nr, &sz) < 0) goto done; cprintf(cb, "%s%" PRIu64 "" "%zu", dbname, nr, sz); } retval = 0; done: return retval; } /*! Loads all or part of a specified configuration to target configuration * * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., 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){ clicon_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){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (xmldb_validate_db(target) < 0){ cprintf(cbx, "No such database: %s", target); if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) goto done; goto ok; } /* Check if target locked by other client */ iddb = xmldb_islocked(h, target); if (iddb && myid != iddb){ cprintf(cbx, "%u", iddb); if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) goto done; goto ok; } 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; } /* 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 (why not do this in parser?) */ if ((ret = xml_bind_yang(xc, YB_MODULE, yspec, &xret)) < 0) goto done; if (ret == 0){ if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 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 (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; goto ok; } if (non_config){ if (netconf_invalid_value(cbret, "protocol", "State data not allowed")< 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 (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; goto ok; } /* Cant do this earlier since we dont have a yang spec to * the upper part of the tree, until we get the "config" tree. */ if (xml_sort_recurse(xc) < 0) goto done; if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){ if (netconf_operation_failed(cbret, "protocol", clicon_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 ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 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", clicon_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", clicon_err_reason)< 0) goto done; goto ok; } } assert(cbuf_len(cbret) == 0); cprintf(cbret, ""); ok: retval = 0; done: if (nsc) cvec_free(nsc); if (xret) xml_free(xret); if (cbx) cbuf_free(cbx); clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); return retval; } /* from_client_edit_config */ /*! Create or replace an entire config with another complete config db * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., 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(clicon_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; 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){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (xmldb_validate_db(source) < 0){ cprintf(cbx, "No such database: %s", source); if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) goto done; goto ok; } if ((target = netconf_db_find(xe, "target")) == NULL){ if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } if (xmldb_validate_db(target) < 0){ cprintf(cbx, "No such database: %s", target); if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) goto done; goto ok; } /* Check if target locked by other client */ iddb = xmldb_islocked(h, target); if (iddb && myid != iddb){ cprintf(cbx, "%u", iddb); if (netconf_lock_denied(cbret, cbuf_get(cbx), "Copy failed, lock is already held") < 0) goto done; goto ok; } if (xmldb_copy(h, source, target) < 0){ if ((cbmsg = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } cprintf(cbmsg, "Copy %s datastore to %s: %s", source, target, clicon_err_reason); if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg))< 0) goto done; goto ok; } xmldb_modified_set(h, target, 1); /* mark as dirty */ cprintf(cbret, "", 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 Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., 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){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (xmldb_validate_db(target) < 0){ cprintf(cbx, "No such database: %s", target); if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) goto done; goto ok; } /* Check if target locked by other client */ iddb = xmldb_islocked(h, target); if (iddb && myid != iddb){ cprintf(cbx, "%u", 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){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } cprintf(cbmsg, "Delete %s datastore: %s", target, clicon_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){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } cprintf(cbmsg, "Create %s datastore: %s", target, clicon_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, "", 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 Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ce_id; uint32_t iddb; 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){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (xmldb_validate_db(db) < 0){ cprintf(cbx, "No such database: %s", db); if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) goto done; goto ok; } /* * 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, "%u", iddb); if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) goto done; goto ok; } /* 2) The target configuration is , it has already been modified, and * these changes have not been committed or rolled back. */ if (strcmp(db, "candidate") == 0 && xmldb_modified_get(h, db)){ if (netconf_lock_denied(cbret, "0", "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 ok; } if (xmldb_lock(h, db, id) < 0) goto done; /* user callback */ if (clixon_plugin_lockdb_all(h, db, 1, id) < 0) goto done; cprintf(cbret, "", 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 Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., 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){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (xmldb_validate_db(db) < 0){ cprintf(cbx, "No such database: %s", db); if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) goto done; goto ok; } 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, "0"); 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 operation is not the same * session that obtained the lock */ else if (iddb != id){ cprintf(cbx, "%u", 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, "", 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 Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ce_id; if (release_all_dbs(h, id) < 0) return -1; stream_ss_delete_all(h, ce_event_cb, (void*)ce); cprintf(cbret, "", NETCONF_BASE_NAMESPACE); return 0; } /*! Internal message: Force the termination of a NETCONF session. * * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: if (reason) free(reason); return retval; } /*! Create a notification subscription * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., * RESULT # If not present, events in the default NETCONF stream will be sent. * * * * */ static int from_client_create_subscription(clicon_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) stream = xml_find_value(x, "body"); 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 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, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: if (nsc) xml_nsctx_free(nsc); return retval; } /*! Set debug level. * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ", NETCONF_BASE_NAMESPACE); ok: retval = 0; done: return retval; } /*! Check liveness of backend daemon, just send a reply * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ", NETCONF_BASE_NAMESPACE); return 0; } /*! Check liveness of backend daemon, just send a reply * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ", NETCONF_BASE_NAMESPACE); nr=0; xml_stats_global(&nr); cprintf(cbret, "%" PRIu64 "", nr); if (clixon_stats_get_db(h, "running", cbret) < 0) goto done; if (clixon_stats_get_db(h, "candidate", cbret) < 0) goto done; if (clixon_stats_get_db(h, "startup", cbret) < 0) goto done; cprintf(cbret, ""); retval = 0; done: return retval; } /*! Request restart of specific plugins * @param[in] h Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ", 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 Clicon handle * @param[in] xe Request: * @param[out] cbret Return xml tree, eg ..., ", NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS); } retval = 0; done: return retval; } /*! Clixon hello to check liveness * @retval 0 OK * @retval -1 Error */ static int from_client_hello(clicon_handle h, cxobj *x, struct client_entry *ce, cbuf *cbret) { int retval = -1; uint32_t id; char *msgid; if (clicon_session_id_get(h, &id) < 0){ clicon_err(OE_NETCONF, ENOENT, "session_id not set"); goto done; } id++; clicon_session_id_set(h, id); if ((msgid = xml_find_value(x, "message-id")) != NULL) cprintf(cbret, "%u", NETCONF_BASE_NAMESPACE, msgid, id); else cprintf(cbret, "%u", NETCONF_BASE_NAMESPACE, id); retval = 0; done: return retval; } /*! An internal clicon message has arrived from a client. Receive and dispatch. * @param[in] h Clicon handle * @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. */ static int from_client_msg(clicon_handle h, struct client_entry *ce, struct clicon_msg *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; uint32_t id; enum nacm_credentials_t creds; char *rpcname; char *rpcprefix; char *namespace = NULL; clicon_debug(1, "%s", __FUNCTION__); 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){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } /* Decode msg from client -> xml top (ct) and session id */ if ((ret = clicon_msg_decode(msg, yspec, &id, &xt, &xret)) < 0){ if (netconf_malformed_message(cbret, "XML parse error") < 0) goto done; goto reply; } if (ret == 0){ if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 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 */ clicon_err(OE_XML, EFAULT, "No xml req (shouldnt happen)"); goto done; } rpcname = xml_name(x); rpcprefix = xml_prefix(x); /* 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){ clicon_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){ ; /* continue below */ } 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; goto reply; } ce->ce_id = id; if ((ret = xml_yang_validate_rpc(h, x, &xret)) < 0) goto done; if (ret == 0){ if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; goto reply; } 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; goto reply; } if ((ymod = ys_module(ye)) == NULL){ clicon_err(OE_XML, ENOENT, "rpc yang does not have module"); goto done; } module = yang_argument_get(ymod); clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc); /* Pre-NACM access step */ xnacm = NULL; /* NACM intial pre- access control enforcements. Retval: * 0: Use NACM validation and xnacm is set. * 1: Permit, skip NACM * Therefore, xnacm=NULL means no NACM checks needed. */ if ((ret = nacm_access_pre(h, ce->ce_username, username, &xnacm)) < 0) goto done; /* 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, cbret)) < 0) goto done; if (ret == 0) /* credentials fail */ 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 */ goto reply; } clicon_err_reset(); if ((ret = rpc_callback_call(h, xe, cbret, ce)) < 0){ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; clicon_log(LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe)); goto reply; /* Dont quit here on user callbacks */ } if (ret == 0){ /* not handled by callback */ if (netconf_operation_not_supported(cbret, "application", "RPC operation not supported")< 0) goto done; 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", clicon_errno?clicon_err_reason:"unknown")< 0) goto done; clicon_debug(1, "%s cbret:%s", __FUNCTION__, cbuf_get(cbret)); /* XXX problem here is that cbret has not been parsed so may contain parse errors */ if (send_msg_reply(ce->ce_s, 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: clicon_log(LOG_WARNING, "client rpc reset"); break; default: goto done; } } // ok: retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, 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 (cbret) cbuf_free(cbret); /* Sanity: log if clicon_err() is not called ! */ if (retval < 0 && clicon_errno < 0) clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on RPC error (message: %s)", __FUNCTION__, rpc?rpc:""); // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval;// -1 here terminates backend } /*! An internal clicon message has arrived from a client. Receive and dispatch. * @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 clicon_msg *msg = NULL; struct client_entry *ce = (struct client_entry *)arg; clicon_handle h = ce->ce_handle; int eof = 0; clicon_debug(1, "%s", __FUNCTION__); // assert(s == ce->ce_s); if (clicon_msg_rcv(ce->ce_s, &msg, &eof) < 0) goto done; if (eof) backend_client_rm(h, ce); else if (from_client_msg(h, ce, msg) < 0) goto done; retval = 0; done: clicon_debug(1, "%s retval=%d", __FUNCTION__, retval); if (msg) free(msg); return retval; /* -1 here terminates backend */ } /*! Init backend rpc: Set up standard netconf rpc callbacks * @param[in] h Clicon handle * @retval -1 Error (fatal) * @retval 0 OK * @see ietf-netconf@2011-06-01.yang */ int backend_rpc_init(clicon_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; /* 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; /* 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; }