clixon/apps/snmp/snmp_handler.c

1428 lines
47 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2022 Olof Hagsand and Kristofer Hallin
Sponsored by Siklu Communications LTD
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 <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
/* net-snmp */
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "snmp_lib.h"
#include "snmp_register.h"
#include "snmp_handler.h"
/*! Common code for handling incoming SNMP request
*
* Get clixon handle from snmp request, print debug data
* @param[in] handler Registered MIB handler structure
* @param[in] nhreg Root registration info.
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @param[in] tablehandler 0:scalar, 1:table for debug
* @param[out] shp Clixon snmp handle
* @retval 0 OK
* @retval -1 Error
*/
static int
snmp_common_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *nhreg,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request,
int tablehandler,
clixon_snmp_handle **shp)
{
int retval = -1;
netsnmp_variable_list *requestvb; /* sub of request */
cbuf *cb = NULL;
if (request == NULL || shp == NULL){
clicon_err(OE_XML, EINVAL, "request or shp is null");
goto done;
}
requestvb = request->requestvb;
if ((*shp = (clixon_snmp_handle*)handler->myvoid) == NULL){
clicon_err(OE_XML, 0, "No myvoid handler");
goto done;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
oid_cbuf(cb, (*shp)->sh_oid, (*shp)->sh_oidlen);
if (oid_eq(requestvb->name, requestvb->name_length,
(*shp)->sh_oid, (*shp)->sh_oidlen) == 0){ /* equal */
clicon_debug(1, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__,
cbuf_get(cb),
snmp_msg_int2str(reqinfo->mode),
request->inclusive, tablehandler?"table":"scalar");
}
else{ /* not equal */
cprintf(cb, " (");
oid_cbuf(cb, requestvb->name, requestvb->name_length);
cprintf(cb, ")");
// nhreg->rootoid same as shp
clicon_debug(1, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__,
cbuf_get(cb),
snmp_msg_int2str(reqinfo->mode),
request->inclusive, tablehandler?"table":"scalar");
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*!
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @retval 0 OK
* @retval -1 Error
*/
static int
snmp_scalar_return(cxobj *xs,
yang_stmt *ys,
oid *oidc,
size_t oidclen,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
int asn1type;
char *xmlstr = NULL;
char *defaultval = NULL;
u_char *snmpval = NULL;
size_t snmplen = 0;
char *reason = NULL;
netsnmp_variable_list *requestvb = request->requestvb;
int ret;
char *body = NULL;
/* SMI default value, How is this different from yang defaults?
*/
if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &defaultval) < 0)
goto done;
if (xs != NULL && (body = xml_body(xs)) != NULL){
if ((ret = type_xml2snmp_pre(body, ys, &xmlstr)) < 0) // XXX <---
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
goto ok;
}
}
else if (defaultval != NULL){
if ((xmlstr = strdup(defaultval)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else{
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
goto ok;
}
if (type_yang2asn1(ys, &asn1type, 1) < 0)
goto done;
if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0)
goto done;
if (ret == 0){
clicon_debug(1, "%s %s", __FUNCTION__, reason);
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
goto ok;
}
/* see snmplib/snmp_client. somewhat indirect
*/
if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "snmp_set_var_typed_value");
goto done;
}
if ((ret = snmp_set_var_objid(requestvb, oidc, oidclen)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "snmp_set_var_objid");
goto done;
}
ok:
retval = 0;
done:
if (xmlstr)
free(xmlstr);
if (snmpval)
free(snmpval);
if (reason)
free(reason);
return retval;
}
/*! Scalar handler, set a value to clixon
* get xpath: see yang2api_path_fmt / api_path2xpath
* @param[in] h Clixon handle
* @param[in] ys Yang node
* @param[in] cvk Vector of index/Key variables, if any
* @param[in] defaultval Default value
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @retval 0 OK
* @retval -1 Error
*/
static int
snmp_scalar_get(clicon_handle h,
yang_stmt *ys,
cvec *cvk,
char *defaultval,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
cvec *nsc = NULL;
char *xpath = NULL;
cxobj *xt = NULL;
cxobj *xerr;
cxobj *x = NULL;
char *xmlstr = NULL;
u_char *snmpval = NULL;
size_t snmplen = 0;
int ret;
int asn1type;
char *reason = NULL;
netsnmp_variable_list *requestvb = request->requestvb;
cxobj *xcache = NULL;
char *body = NULL;
clicon_debug(1, "%s", __FUNCTION__);
/* Prepare backend call by constructing namespace context */
if (xml_nsctx_yang(ys, &nsc) < 0)
goto done;
/* Create xpath from yang */
if (snmp_yang2xpath(ys, cvk, &xpath) < 0)
goto done;
if (type_yang2asn1(ys, &asn1type, 1) < 0)
goto done;
/* First try cache */
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
if (xcache==NULL || (x = xpath_first(xcache, nsc, "%s", xpath)) == NULL){
/* If not found do the backend call */
if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, NULL, &xt) < 0)
goto done;
/* Detect error XXX Error handling could improve */
if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
clixon_netconf_error(xerr, "clicon_rpc_get", NULL);
goto done;
}
x = xpath_first(xt, nsc, "%s", xpath);
}
/*
* The xml to snmp value conversion is done in two steps:
* 1. From XML to SNMP string, there is a special case for enumeration, and for default value
* 2. From SNMP string to SNMP binary value which invloves parsing
*/
if (x != NULL && (body = xml_body(x)) != NULL){
if ((ret = type_xml2snmp_pre(body, ys, &xmlstr)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
goto ok;
}
}
else if (defaultval != NULL){
if ((xmlstr = strdup(defaultval)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else{
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
goto ok;
}
if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0)
goto done;
if (ret == 0){
clicon_debug(1, "%s %s", __FUNCTION__, reason);
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
goto ok;
}
/* see snmplib/snmp_client. somewhat indirect
*/
if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "snmp_set_var_typed_value");
goto done;
}
ok:
retval = 0;
done:
if (reason)
free(reason);
if (xmlstr)
free(xmlstr);
if (snmpval)
free(snmpval);
if (xt)
xml_free(xt);
if (xpath)
free(xpath);
if (nsc)
xml_nsctx_free(nsc);
return retval;
}
/*! Yang 2 xml via api-path lib functions
*/
int
snmp_yang2xml(cxobj *xtop,
yang_stmt *ys,
cvec *cvk,
cxobj **xbot)
{
int retval = -1;
cvec *cvk1 = NULL;
char *api_path = NULL;
char *api_path_fmt = NULL;
int i;
int ret;
yang_stmt *yspec;
yspec = ys_spec(ys);
if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0)
goto done;
/* Need to prepend an element to fit api_path_fmt2api_path cvv parameter */
if ((cvk1 = cvec_new(1)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
for (i=0; i<cvec_len(cvk); i++)
cvec_append_var(cvk1, cvec_i(cvk,i));
if (api_path_fmt2api_path(api_path_fmt, cvk1, &api_path, NULL) < 0)
goto done;
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, xbot, NULL, NULL)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, 0, "api_path2xml %s invalid", api_path);
goto done;
}
retval = 0;
done:
if (api_path_fmt)
free(api_path_fmt);
if (api_path)
free(api_path);
if (cvk1)
cvec_free(cvk1);
return retval;
}
/*! Scalar handler, get a value from clixon
* @param[in] h Clixon handle
* @param[in] ys Yang node
* @param[in] cvk Vector of index/Key variables, if any
* @param[in] valstr0 Pre-set value string, ignore requestvb
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @retval 0 OK
* @retval -1 Error
* @note contains special logic for rowstatus handling
*/
static int
snmp_scalar_set(clicon_handle h,
yang_stmt *ys,
cvec *cvk,
char *valstr0,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
cxobj *xtop = NULL;
cxobj *xbot = NULL;
cxobj *xb;
int ret;
char *valstr = NULL;
cbuf *cb = NULL;
netsnmp_variable_list *requestvb = request->requestvb;
int asn1_type;
enum operation_type op = OP_MERGE;
clicon_debug(1, "%s", __FUNCTION__);
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0)
goto done;
if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
goto done;
/* Extended */
if (type_yang2asn1(ys, &asn1_type, 1) < 0)
goto done;
if (valstr0){
if (xml_value_set(xb, valstr0) < 0)
goto done;
}
else {
if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0)
goto done;
if (ret == 0)
goto ok;
if (xml_value_set(xb, valstr) < 0)
goto done;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0)
goto done;
if (clicon_rpc_edit_config(h, "candidate", op, cbuf_get(cb)) < 0)
goto done;
ok:
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xtop)
xml_free(xtop);
if (valstr)
free(valstr);
return retval;
}
/* Make cache row operation: move to backend, remove altogether
*
* Remove row from cache, then make merge or delete operation on backend.
* @param[in] h Clixon handle
* @param[in] yp Yang statement of list / parent of leaf
* @param[in] cvk Vector of index/Key variables
* @param[in] opstr Operation on row: merge or delete
* @param[in] rpc If 0: do not make backend RPC, 1: do make backend RPC
* @retval 0 OK
* @retval -1 Error
*/
static int
snmp_cache_row_op(clicon_handle h,
yang_stmt *yp,
cvec *cvk,
char *opstr,
int rpc)
{
int retval = -1;
char *xpath;
cxobj *xrow = NULL;
cvec *nsc = NULL;
cbuf *cb = NULL;
cxobj *xtop = NULL;
cxobj *xbot = NULL;
cxobj *xa;
cxobj *xcache;
if (xml_nsctx_yang(yp, &nsc) < 0)
goto done;
/* Create xpath from yang */
if (snmp_yang2xpath(yp, cvk, &xpath) < 0)
goto done;
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
if (xcache && (xrow = xpath_first(xcache, nsc, "%s", xpath)) != NULL){
if (xml_rm(xrow) < 0)
goto done;
if (rpc){
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
if (snmp_yang2xml(xtop, yang_parent_get(yp), cvk, &xbot) < 0)
goto done;
if ((xa = xml_new("operation", xrow, CX_ATTR)) == NULL)
goto done;
if (xml_value_set(xa, opstr) < 0)
goto done;
if (xml_namespace_change(xa, NETCONF_BASE_NAMESPACE, NETCONF_BASE_PREFIX) < 0)
goto done;
if (xml_addsub(xbot, xrow) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0)
goto done;
if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
goto done;
}
else
xml_free(xrow);
}
retval = 0;
done:
if (nsc)
xml_nsctx_free(nsc);
if (xtop)
xml_free(xtop);
if (cb)
cbuf_free(cb);
if (xpath)
free(xpath);
return retval;
}
/*! Set internal cache instead of RPC to backend
*
* Always applies to rowstatus settings, as well as rows with notInService(2) status
* @param[in] h Clixon handle
* @param[in] yp Yang statement of list / parent of leaf
* @param[in] cvk Vector of index/Key variables
* @param[in] rowstatus Existing value of rowstatus
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @retval 0 OK
* @retval -1 Error
*/
static int
snmp_cache_set(clicon_handle h,
yang_stmt *ys,
cvec *cvk,
int rowstatus,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
cxobj *xtop = NULL;
cxobj *xbot = NULL;
cxobj *xb;
int ret;
char *valstr = NULL;
netsnmp_variable_list *requestvb = request->requestvb;
int asn1_type;
yang_stmt *yspec;
int isrowstatus = 0;
cxobj *xcache = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0)
goto done;
if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
goto done;
/* Extended */
if (type_yang2asn1(ys, &asn1_type, 1) < 0)
goto done;
if (asn1_type == CLIXON_ASN_ROWSTATUS)
isrowstatus++;
if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0)
goto done;
if (ret == 0)
goto ok;
if (isrowstatus){
/* Special case translation of rowstatus values:
* createAndGo -> active, createAndWait -> notInService
*/
if (strcmp(valstr, "createAndGo") == 0){
free(valstr);
if ((valstr = strdup("active")) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else if (strcmp(valstr, "createAndWait") == 0){
free(valstr);
if ((valstr = strdup("notInService")) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else if (strcmp(valstr, "active") == 0){
if (rowstatus == 5) /* createAndWait */
if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "merge", 1) < 0)
goto done;
}
else if (strcmp(valstr, "destroy") == 0){
clicon_debug(1, "%s %d", __FUNCTION__, rowstatus);
/* Dont send delete to backend if notInService(2) */
if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "delete", rowstatus!=2) < 0)
goto done;
}
}
if (xml_value_set(xb, valstr) < 0)
goto done;
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
if (xcache == NULL){
if ((xcache = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
clicon_ptr_set(h, "snmp-rowstatus-tree", xcache);
}
if (!isrowstatus || strcmp(valstr, "destroy") != 0)
if (xml_merge(xcache, xtop, yspec, NULL) < 0)
goto done;
/* Special case: push active to clixon */
if (isrowstatus && strcmp(valstr, "active") == 0){
if (snmp_scalar_set(h, ys,
cvk,
valstr,
reqinfo,
request) < 0)
goto done;
}
ok:
retval = 0;
done:
if (xtop)
xml_free(xtop);
if (valstr)
free(valstr);
return retval;
}
/*! Specialized get-config to get value of row-status, if any
*
* @param[in] h Clixon handle
* @param[in] ys Yang node
* @param[in] cvk Vector of index/Key variables, if any
* @param[out] rowstatus Enmu rowstatus: 0 invalid, 1 active, etc
* @retval 0 OK
* @retval -1 Error
*/
static int
snmp_table_rowstatus_get(clicon_handle h,
yang_stmt *ys,
yang_stmt *yrestype,
cvec *cvk,
int32_t *rowstatus)
{
int retval = -1;
cvec *nsc = NULL;
cxobj *xt = NULL;
char *xpath = NULL;
cxobj *xr;
int ret;
char *body;
char *intstr;
char *reason = NULL;
clicon_debug(1, "%s", __FUNCTION__);
/* Prepare backend call by constructing namespace context */
if (xml_nsctx_yang(ys, &nsc) < 0)
goto done;
/* Create xpath from yang */
if (snmp_yang2xpath(ys, cvk, &xpath) < 0)
goto done;
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xt);
if (xt && (xr = xpath_first(xt, nsc, "%s", xpath)) != NULL &&
(body = xml_body(xr)) != NULL) {
if ((ret = yang_enum2valstr(yrestype, body, &intstr)) < 0)
goto done;
if (ret == 0){
clicon_debug(1, "%s %s invalid or not found", __FUNCTION__, body);
*rowstatus = 0;
}
else {
if ((ret = parse_int32(intstr, rowstatus, &reason)) < 0)
goto done;
if (ret == 0){
clicon_debug(1, "%s parse_int32: %s", __FUNCTION__, reason);
*rowstatus = 0;
}
}
}
else
*rowstatus = 0;
retval = 0;
done:
if (xpath)
free(xpath);
if (nsc)
xml_nsctx_free(nsc);
if (reason)
free(reason);
return retval;
}
/*! Single SNMP Scalar operation handler
*
* @param[in] handler Registered MIB handler structure
* @param[in] nhreg Root registration info.
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @retval 0 OK
* @retval -1 Error
*/
static int
clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler,
netsnmp_handler_registration *nhreg,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
clixon_snmp_handle *sh = NULL;
int asn1_type;
netsnmp_variable_list *requestvb = request->requestvb;
int ret;
clicon_debug(2, "%s", __FUNCTION__);
if (snmp_common_handler(handler, nhreg, reqinfo, request, 0, &sh) < 0)
goto done;
/* see net-snmp/agent/snmp_agent.h / net-snmp/library/snmp.h */
switch (reqinfo->mode) {
case MODE_GET: /* 160 */
if (snmp_scalar_get(sh->sh_h, sh->sh_ys, sh->sh_cvk_orig,
sh->sh_default, reqinfo, request) < 0)
goto done;
break;
case MODE_GETNEXT: /* 161 */
break;
case MODE_SET_RESERVE1: /* 0 */
if (!yang_config_ancestor(sh->sh_ys) ||
nhreg->modes == HANDLER_CAN_RONLY){
netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE);
goto done;
}
/* Translate from YANG ys leaf type to SNMP asn1.1 type ids (not value), also cvtype */
if (type_yang2asn1(sh->sh_ys, &asn1_type, 0) < 0)
goto done;
if (requestvb->type != asn1_type){
clicon_debug(1, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type);
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto ok;
}
}
break;
case MODE_SET_RESERVE2: /* 1 */
break;
case MODE_SET_ACTION: /* 2 */
if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, NULL, reqinfo, request) < 0)
goto done;
/*
* There does not seem to be a separate validation action and commit does not
* return an error.
* Therefore validation is done here directly as well as discard if it fails.
*/
if ((ret = clicon_rpc_validate(sh->sh_h, "candidate")) < 0)
goto done;
if (ret == 0){
clicon_rpc_discard_changes(sh->sh_h);
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
goto done;
}
break;
case MODE_SET_COMMIT: /* 3 */
if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0)
goto done;
if (ret == 0){
/* Note that error given in commit is not propagated to the snmp client,
* therefore validation is in the ACTION instead
*/
clicon_rpc_discard_changes(sh->sh_h);
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
goto done;
}
break;
case MODE_SET_FREE: /* 4 */
break;
case MODE_SET_UNDO: /* 5 */
if (clicon_rpc_discard_changes(sh->sh_h) < 0)
goto done;
break;
}
ok:
retval = SNMP_ERR_NOERROR;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Top level scalar request handler, loop over individual request
*
* @param[in] handler Registered MIB handler structure
* @param[in] nhreg Root registration info.
* @param[in] reqinfo Agent transaction request structure
* @param[in] requests The netsnmp request info structure.
* @see clixon_snmp_table_handler
*/
int
clixon_snmp_scalar_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *nhreg,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int retval = -1;
netsnmp_request_info *req;
int ret;
clicon_debug(1, "%s", __FUNCTION__);
for (req = requests; req; req = req->next){
ret = clixon_snmp_scalar_handler1(handler, nhreg, reqinfo, req);
if (ret != SNMP_ERR_NOERROR){
retval = ret;
goto done;
break;
}
}
retval = SNMP_ERR_NOERROR;
done:
return retval;
}
/*! Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name
*
* Get yang of leaf from first part of OID
* Create xpath with right keys from later part of OID
* Query clixon if object exists, if so return value
* @param[in] h Clixon handle
* @param[in] yt Yang of table (of list type)
* @param[in] oidt OID of registered top container object (above list), may be different from yt
* @param[in] oidtlen OID length of list object OID
* @param[in] oids OID of ultimate scalar value
* @param[in] oidslen OID length of scalar
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @retval -1 Error
* @retval 0 Object not found
* @retval 1 OK
*/
static int
snmp_table_get(clicon_handle h,
yang_stmt *yt,
oid *oidt,
size_t oidtlen,
oid *oids,
size_t oidslen,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
oid oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */
size_t oidleaflen = MAX_OID_LEN;
oid *oidi;
size_t oidilen;
yang_stmt *ys;
yang_stmt *yk;
char *xpath = NULL;
cvec *cvk_orig;
cvec *cvk_val = NULL;
int i;
cg_var *cv;
char *defaultval = NULL;
int ret;
/* Get yang of leaf from first part of OID */
ys = NULL;
while ((ys = yn_each(yt, ys)) != NULL) {
if (yang_keyword_get(ys) != Y_LEAF)
continue;
/* reset oid */
oidleaflen = MAX_OID_LEN;
if ((ret = yangext_oid_get(ys, oidleaf, &oidleaflen, NULL)) < 0)
goto done;
if (ret == 0)
continue;
if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */
continue;
if (oids[oidleaflen-1] == oidleaf[oidleaflen-1])
break;
}
if (ys == NULL){
/* No leaf with matching OID */
goto fail;
}
/* SMI default value, How is this different from yang defaults?
*/
if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &defaultval) < 0)
goto done;
/* Create xpath with right keys from later part of OID
* Inverse of snmp_str2oid
*/
if ((cvk_orig = yang_cvec_get(yt)) == NULL){
clicon_err(OE_YANG, 0, "No keys");
goto done;
}
if ((cvk_val = cvec_dup(cvk_orig)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
/* read through keys and create cvk */
oidilen = oidslen-(oidtlen+1);
oidi = oids+oidtlen+1;
/* Add keys */
for (i=0; i<cvec_len(cvk_val); i++){
cv = cvec_i(cvk_val, i);
if ((yk = yang_find(yt, Y_LEAF, cv_string_get(cv))) == NULL){
clicon_err(OE_YANG, 0, "List key %s not found", cv_string_get(cv));
goto done;
}
if (snmp_oid2str(&oidi, &oidilen, yk, cv) < 0)
goto done;
}
if (oidilen != 0){
clicon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen);
goto fail;
}
/* Get scalar value */
if (snmp_scalar_get(h, ys, cvk_val,
defaultval,
reqinfo,
request) < 0)
goto done;
// ok:
retval = 1;
done:
if (cvk_val)
cvec_free(cvk_val);
if (xpath)
free(xpath);
return retval;
fail:
retval = 0;
goto done;
}
/*! Set value in table
* Get yang of leaf from first part of OID
* Create xpath with right keys from later part of OID
* Query clixon if object exists, if so return value
* @param[in] h Clixon handle
* @param[in] yt Yang of table (of list type)
* @param[in] oids OID of ultimate scalar value
* @param[in] oidslen OID length of scalar
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @param[out] err Error code if failed (retval = 0)
* @retval -1 Error
* @retval 0 Failed, err set
* @retval 1 OK
*/
static int
snmp_table_set(clicon_handle h,
yang_stmt *yt,
oid *oids,
size_t oidslen,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request,
int *err)
{
int retval = -1;
oid oidt[MAX_OID_LEN] = {0,}; /* Table / list oid */
size_t oidtlen = MAX_OID_LEN;
oid oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */
size_t oidleaflen = MAX_OID_LEN;
oid *oidi;
size_t oidilen;
yang_stmt *yi;
yang_stmt *ys;
yang_stmt *yrowst;
yang_stmt *yk;
yang_stmt *yrestype = NULL;
char *xpath = NULL;
cvec *cvk_orig;
cvec *cvk_val = NULL;
int i;
cg_var *cv;
int ret;
int asn1_type;
netsnmp_variable_list *requestvb;
int rowstatus = 0;
char *origtype;
/* Get OID from table /list */
if ((ret = yangext_oid_get(yt, oidt, &oidtlen, NULL)) < 0)
goto done;
if (ret == 0)
goto done;
/* Get yang of leaf from first part of OID
* and also leaf with rowstatus type
*/
ys = NULL;
yrowst = NULL;
yi = NULL;
while ((yi = yn_each(yt, yi)) != NULL) {
if (yang_keyword_get(yi) != Y_LEAF)
continue;
/* reset oid */
oidleaflen = MAX_OID_LEN;
if ((ret = yangext_oid_get(yi, oidleaf, &oidleaflen, NULL)) < 0)
goto done;
if (ret == 0)
continue;
if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */
continue;
if (oids[oidleaflen-1] == oidleaf[oidleaflen-1]){
ys = yi;
}
origtype = NULL;
if (snmp_yang_type_get(yi, NULL, &origtype, &yrestype, NULL) < 0)
goto done;
if (strcmp(origtype, "RowStatus") == 0){
yrowst = yi;
}
if (origtype){
free(origtype);
origtype = NULL;
}
}
if (ys == NULL){
/* No leaf with matching OID */
*err = SNMP_NOSUCHOBJECT;
goto fail;
}
if (!yang_config_ancestor(ys)){
*err = SNMP_ERR_NOTWRITABLE;
goto fail;
}
{
char *modes_str = NULL;
int modes;
if (yang_extension_value(ys, "max-access", IETF_YANG_SMIV2_NS, NULL, &modes_str) < 0)
goto done;
if (modes_str){
modes = snmp_access_str2int(modes_str);
if (modes == HANDLER_CAN_RONLY){
*err = SNMP_ERR_NOTWRITABLE;
goto fail;
}
}
}
if (type_yang2asn1(ys, &asn1_type, 0) < 0)
goto done;
requestvb = request->requestvb;
if (requestvb->type != asn1_type){
clicon_debug(1, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type);
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto ok;
}
}
/* Create xpath with right keys from later part of OID
* Inverse of snmp_str2oid
*/
if ((cvk_orig = yang_cvec_get(yt)) == NULL){
clicon_err(OE_YANG, 0, "No keys");
goto done;
}
if ((cvk_val = cvec_dup(cvk_orig)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
/* read through keys and create cvk */
oidilen = oidslen-(oidtlen+1);
oidi = oids+oidtlen+1;
/* Add keys */
for (i=0; i<cvec_len(cvk_val); i++){
cv = cvec_i(cvk_val, i);
if ((yk = yang_find(yt, Y_LEAF, cv_string_get(cv))) == NULL){
clicon_err(OE_YANG, 0, "List key %s not found", cv_string_get(cv));
goto done;
}
if (snmp_oid2str(&oidi, &oidilen, yk, cv) < 0)
goto done;
}
if (oidilen != 0){
clicon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen);
*err = SNMP_NOSUCHOBJECT;
goto fail;
}
if (yrowst){
/* Get rowstatus from cache */
if ((ret = snmp_table_rowstatus_get(h,
yrowst,
yrestype,
cvk_val,
&rowstatus)) < 0)
goto done;
}
else{
/* If no rowstatus object, default to active */
rowstatus = 1;
}
if (ys == yrowst){
/* Set a rowstatus value to cache */
if (snmp_cache_set(h, ys,
cvk_val,
rowstatus,
reqinfo,
request) < 0)
goto done;
}
else{
switch (rowstatus){
case 0: /* not found, invalid */
case 3: /* notReady */
case 6: /* destroy */
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_INCONSISTENTVALUE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto ok;
}
break;
case 1: /* active */
case 4: /* createAndGo */
/* Set backend */
if (snmp_scalar_set(h, ys,
cvk_val,
NULL,
reqinfo,
request) < 0)
goto done;
break;
case 2: /* notInService */
case 5: /* createAndWait */
/* Set cache */
if (snmp_cache_set(h, ys,
cvk_val,
rowstatus,
reqinfo,
request) < 0)
goto done;
break;
}
}
ok:
retval = 1;
done:
if (cvk_val)
cvec_free(cvk_val);
if (xpath)
free(xpath);
if (origtype)
free(origtype);
return retval;
fail:
retval = 0;
goto done;
}
/*! Find "next" object from oids minus key and return that.
* @param[in] h Clixon handle
* @param[in] ylist Yang of table (of list type)
* @param[in] oids OID of ultimate scalar value
* @param[in] oidslen OID length of scalar
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
* @retval 1 OK
* @retval 0 Failed
* @retval -1 Error
* XXX: merge with cache
*/
static int
snmp_table_getnext(clicon_handle h,
yang_stmt *ylist,
oid *oids,
size_t oidslen,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
cvec *nsc = NULL;
char *xpath = NULL;
cxobj *xt = NULL;
cxobj *xerr;
cxobj *xtable;
cxobj *xrow;
cxobj *xcol;
yang_stmt *ycol;
yang_stmt *ys;
int ret;
cvec *cvk_name;
oid oidc[MAX_OID_LEN] = {0,}; /* Table / list oid */
size_t oidclen = MAX_OID_LEN;
oid oidk[MAX_OID_LEN] = {0,}; /* Key oid */
size_t oidklen = MAX_OID_LEN;
oid oidnext[MAX_OID_LEN] = {0x7fffffff,}; /* Next oid: start with high value */
size_t oidnextlen = MAX_OID_LEN;
int found = 0;
cxobj *xnext = NULL;
yang_stmt *ynext = NULL;
cbuf *cb = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((ys = yang_parent_get(ylist)) == NULL ||
yang_keyword_get(ys) != Y_CONTAINER){
clicon_err(OE_YANG, EINVAL, "ylist parent is not list");
goto done;
}
if (xml_nsctx_yang(ys, &nsc) < 0)
goto done;
if (snmp_yang2xpath(ys, NULL, &xpath) < 0)
goto done;
if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, NULL, &xt) < 0)
goto done;
if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
clixon_netconf_error(xerr, "clicon_rpc_get", NULL);
goto done;
}
if ((xtable = xpath_first(xt, nsc, "%s", xpath)) != NULL) {
/* Make a clone of key-list, but replace names with values */
if ((cvk_name = yang_cvec_get(ylist)) == NULL){
clicon_err(OE_YANG, 0, "No keys");
goto done;
}
xrow = NULL;
while ((xrow = xml_child_each(xtable, xrow, CX_ELMNT)) != NULL) {
/* Get key part of OID from XML list entry */
if ((ret = snmp_xmlkey2val_oid(xrow, cvk_name, NULL, /*&cvk_oid,*/ oidk, &oidklen)) < 0)
goto done;
if (ret == 0)
continue; /* skip row, not all indexes */
xcol = NULL;
while ((xcol = xml_child_each(xrow, xcol, CX_ELMNT)) != NULL) {
if ((ycol = xml_spec(xcol)) == NULL)
continue;
if (yang_keyword_get(ycol) != Y_LEAF)
continue;
if ((ret = yangext_oid_get(ycol, oidc, &oidclen, NULL)) < 0)
goto done;
if (ret == 0)
continue;
/* Append key oid */
if (oid_append(oidc, &oidclen, oidk, oidklen) < 0)
goto done;
/* Get smallest larger */
if (oid_eq(oidc, oidclen, oids, oidslen) > 0 &&
oid_eq(oidc, oidclen, oidnext, oidnextlen) < 0){
memcpy(oidnext, oidc, oidclen*sizeof(*oidnext));
oidnextlen = oidclen;
xnext = xcol;
ynext = ycol;
found++;
}
} /* while xcol */
} /* while xrow */
}
if (found){
if (snmp_scalar_return(xnext, ynext, oidnext, oidnextlen, reqinfo, request) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
oid_cbuf(cb, oidnext, oidnextlen);
clicon_debug(1, "%s next: %s", __FUNCTION__, cbuf_get(cb));
}
retval = found;
done:
if (cb)
cbuf_free(cb);
if (xpath)
free(xpath);
if (xt)
xml_free(xt);
if (nsc)
xml_nsctx_free(nsc);
return retval;
}
/*! SNMP table operation handler
*
* @param[in] handler Registered MIB handler structure
* @param[in] nhreg Root registration info.
* @param[in] reqinfo Agent transaction request structure
* @param[in] request The netsnmp request info structure.
*/
static int
clixon_snmp_table_handler1(netsnmp_mib_handler *handler,
netsnmp_handler_registration *nhreg,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *request)
{
int retval = -1;
clixon_snmp_handle *sh = NULL;
cvec *nsc = NULL;
cxobj *xt = NULL;
cbuf *cb = NULL;
int ret;
netsnmp_variable_list *requestvb;
int err = 0;
clicon_debug(2, "%s", __FUNCTION__);
if ((ret = snmp_common_handler(handler, nhreg, reqinfo, request, 1, &sh)) < 0)
goto done;
if (sh->sh_ys == NULL){
clicon_debug(1, "%s Error table not registered", __FUNCTION__);
goto ok;
}
requestvb = request->requestvb;
switch(reqinfo->mode){
case MODE_GET: // 160
/* Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name
*/
if ((ret = snmp_table_get(sh->sh_h, sh->sh_ys,
sh->sh_oid2, sh->sh_oid2len,
requestvb->name, requestvb->name_length,
reqinfo, request)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
clicon_debug(1, "%s Nosuchinstance", __FUNCTION__);
}
break;
case MODE_GETNEXT: // 161
/* Register table sub-oid:s of existing entries in clixon */
if ((ret = snmp_table_getnext(sh->sh_h, sh->sh_ys,
requestvb->name, requestvb->name_length,
reqinfo, request)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
clicon_debug(1, "%s No such object", __FUNCTION__);
}
break;
case MODE_SET_RESERVE1: // 0
if (!yang_config_ancestor(sh->sh_ys)){
netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE);
goto done;;
}
// Check types: compare type in requestvb to yang type (or do later)
break;
case MODE_SET_RESERVE2: // 1
break;
case MODE_SET_ACTION: // 2
if ((ret = snmp_table_set(sh->sh_h, sh->sh_ys,
requestvb->name, requestvb->name_length,
reqinfo, request, &err)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(request, err)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
clicon_debug(1, "%s Nosuchinstance", __FUNCTION__);
}
/*
* There does not seem to be a separate validation action and commit does not
* return an error.
* Therefore validation is done here directly as well as discard if it fails.
*/
if ((ret = clicon_rpc_validate(sh->sh_h, "candidate")) < 0)
goto done;
if (ret == 0){
clicon_rpc_discard_changes(sh->sh_h);
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
goto done;
}
break;
case MODE_SET_COMMIT: // 3
if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0)
goto done;
if (ret == 0){
clicon_rpc_discard_changes(sh->sh_h);
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
goto done;
}
break;
case MODE_SET_FREE: // 4
break;
case MODE_SET_UNDO : // 5
if (clicon_rpc_discard_changes(sh->sh_h) < 0)
goto done;
break;
}
ok:
retval = SNMP_ERR_NOERROR;
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
if (nsc)
xml_nsctx_free(nsc);
return retval;
}
/*! Top level table request handler, loop over individual requests
*
* @param[in] handler Registered MIB handler structure
* @param[in] nhreg Root registration info.
* @param[in] reqinfo Agent transaction request structure
* @param[in] requests The netsnmp request info structure.
* @see clixon_snmp_scalar_handler
*/
int
clixon_snmp_table_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *nhreg,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int retval = -1;
netsnmp_request_info *req;
int ret;
clicon_debug(1, "%s", __FUNCTION__);
for (req = requests; req; req = req->next){
ret = clixon_snmp_table_handler1(handler, nhreg, reqinfo, req);
if (ret != SNMP_ERR_NOERROR){
retval = ret;
goto done;
break;
}
}
retval = SNMP_ERR_NOERROR;
done:
return retval;
}