clixon/apps/snmp/snmp_handler.c
2022-07-10 12:17:01 +02:00

1143 lines
32 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>
#include <assert.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"
static int
snmp_common_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *nhreg,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests,
clixon_snmp_handle **shp,
int tablehandler)
{
int retval = -1;
netsnmp_variable_list *requestvb; /* sub of requests */
cbuf *cb;
if (requests == NULL || shp == NULL){
clicon_err(OE_XML, EINVAL, "requests or shp is null");
goto done;
}
requestvb = requests->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),
requests->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),
requests->inclusive, tablehandler?"table":"scalar");
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*!
*/
static int
snmp_scalar_return(cxobj *xs,
yang_stmt *ys,
oid *oidc,
size_t oidclen,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
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 = requests->requestvb;
int ret;
/* 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){
if ((ret = type_xml2snmp_pre(xml_body(xs), ys, &xmlstr)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(requests, 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(requests, 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(requests, 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] requestvb SNMP variables
* @param[in] defaultval
* @param[in] reqinfo
* @param[in] requests
* @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 *requests)
{
int retval = -1;
cvec *nsc = NULL;
char *xpath = NULL;
cxobj *xt = NULL;
cxobj *xerr;
cxobj *x;
char *xmlstr = NULL;
u_char *snmpval = NULL;
size_t snmplen = 0;
int ret;
int asn1type;
char *reason = NULL;
netsnmp_variable_list *requestvb = requests->requestvb;
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;
/* Do the backend call */
if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, &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;
}
/*
* 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 = xpath_first(xt, nsc, "%s", xpath)) != NULL){
assert(xml_spec(x) == ys);
if ((ret = type_xml2snmp_pre(xml_body(x), ys, &xmlstr)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(requests, 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(requests, 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(requests, 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;
}
/*! 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] db Clixon datastore, typically "candidate"
* @param[in] reqinfo
* @param[in] requestvb SNMP variables
* @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 *db,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int retval = -1;
char *api_path = NULL;
char *api_path_fmt = NULL;
cxobj *xtop = NULL;
cxobj *xbot = NULL;
cxobj *xb;
yang_stmt *yspec;
int ret;
char *valstr = NULL;
cbuf *cb = NULL;
netsnmp_variable_list *requestvb = requests->requestvb;
cvec *cvk1;
int i;
int asn1_type;
int rowstatus = 0;
enum operation_type op = OP_MERGE;
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 (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;
}
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){
/* Must be done before type_snmp2xml: it translates asn1_type */
rowstatus = 1;
}
if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, requests, &valstr)) < 0)
goto done;
if (ret == 0)
goto ok;
/* Special case translation of rowstatus values:
* createAndGo -> active, createAndWait -> notInService
*/
if (rowstatus){
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, "destroy") == 0){ /* Remove entry */
cxobj *xe;
cxobj *xa;
if ((xe = xml_parent(xbot)) != NULL){
if ((xa = xml_new("operation", xe, CX_ATTR)) == NULL)
goto done;
if (xml_value_set(xa, "delete") < 0)
goto done;
if (xml_namespace_change(xa, NETCONF_BASE_NAMESPACE, NETCONF_BASE_PREFIX) < 0)
goto done;
op = OP_NONE;
}
}
}
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, db, op, cbuf_get(cb)) < 0)
goto done;
ok:
retval = 0;
done:
if (cvk1)
cvec_free(cvk1);
if (api_path_fmt)
free(api_path_fmt);
if (api_path)
free(api_path);
if (cb)
cbuf_free(cb);
if (xtop)
xml_free(xtop);
if (valstr)
free(valstr);
return retval;
}
/*! SNMP Scalar operation handler
* Calls order: READ:160,
* WRITE: 0, 1, 2, 3,
* MODE_SET_RESERVE1, MODE_SET_RESERVE2, MODE_SET_ACTION, MODE_SET_COMMIT
*
*/
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;
clixon_snmp_handle *sh = NULL;
int asn1_type;
netsnmp_variable_list *requestvb = requests->requestvb;
int ret;
clicon_debug(2, "%s", __FUNCTION__);
if (snmp_common_handler(handler, nhreg, reqinfo, requests, &sh, 0) < 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, requests) < 0)
goto done;
break;
case MODE_GETNEXT: /* 161 */
assert(0); // Not seen?
break;
case MODE_SET_RESERVE1: /* 0 */
/* 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(requests, 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, "candidate", reqinfo, requests) < 0)
goto done;
break;
case MODE_SET_COMMIT: /* 3 */
if (clicon_rpc_commit(sh->sh_h) < 0)
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:
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;
cxobj *xerr;
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;
/* Do the backend call */
if (clicon_rpc_get_config(h, NULL, "candidate", xpath, nsc, &xt) < 0)
goto done;
if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
clixon_netconf_error(xerr, "clicon_rpc_get_config", NULL);
goto done;
}
if ((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 (xt)
xml_free(xt);
if (xpath)
free(xpath);
if (nsc)
xml_nsctx_free(nsc);
if (reason)
free(reason);
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] oids OID of ultimate scalar value
* @param[in] oidslen OID length of scalar
* @param[in] reginfo
* @param[in] requests
* @retval -1 Error
* @retval 0 Object not found
* @retval 1 OK
*/
static int
snmp_table_get(clicon_handle h,
yang_stmt *yt,
oid *oids,
size_t oidslen,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
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 *ys;
yang_stmt *yk;
char *xpath = NULL;
cvec *cvk_orig;
cvec *cvk_val;
int i;
cg_var *cv;
char *defaultval = NULL;
int ret;
/* 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 */
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)
goto done;
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,
requests) < 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] reginfo
* @param[in] requests
* @retval -1 Error
* @retval 0 Object not found
* @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 *requests)
{
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;
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)
goto done;
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 */
goto fail;
}
if (type_yang2asn1(ys, &asn1_type, 0) < 0)
goto done;
requestvb = requests->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(requests, 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);
goto fail;
}
if (ys == yrowst){
if (snmp_scalar_set(h, ys,
cvk_val,
"candidate",
reqinfo,
requests) < 0)
goto done;
}
else{
if (yrowst){
/* Check rowstatus */
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;
}
switch (rowstatus){
case 0: // not found, invalid
if ((ret = netsnmp_request_set_error(requests, SNMP_ERR_INCONSISTENTVALUE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto ok;
}
break;
case 1: // active
case 4: // createAndGo
if (snmp_scalar_set(h, ys,
cvk_val,
"candidate",
reqinfo,
requests) < 0)
goto done;
break;
case 2: // notInService
case 5: // createAndWait
if (snmp_scalar_set(h, ys,
cvk_val,
"candidate",
reqinfo,
requests) < 0)
goto done;
break;
case 3: // notReady
case 6: // destroy
if ((ret = netsnmp_request_set_error(requests, SNMP_ERR_INCONSISTENTVALUE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto ok;
}
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" objct 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
* @retval 1 OK
* @retval 0 Failed
* @retval -1 Error
*/
static int
snmp_table_getnext(clicon_handle h,
yang_stmt *ylist,
oid *oids,
size_t oidslen,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
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, &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;
/* 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, requests) < 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
* Callorder: 161,160,.... 0, 1,2,3, 160,161,...
* see https://net-snmp.sourceforge.io/dev/agent/data_set_8c-example.html#_a0
*
* see table_array.[ch] simplify the task of
* writing a table handler for the net-snmp agent when the data being
* accessed is in an oid sorted form and must be accessed externally.
*
* netsnmp_table_build_oid_from_index()
*
* table_container.[ch]
*
* build_new_oid
*/
static int
clixon_snmp_table_handler1(netsnmp_mib_handler *handler,
netsnmp_handler_registration *nhreg,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int retval = -1;
clixon_snmp_handle *sh = NULL;
cvec *nsc = NULL;
cxobj *xt = NULL;
cbuf *cb = NULL;
int ret;
netsnmp_variable_list *requestvb;
clicon_debug(2, "%s", __FUNCTION__);
if ((ret = snmp_common_handler(handler, nhreg, reqinfo, requests, &sh, 1)) < 0)
goto done;
if (sh->sh_ys == NULL){
clicon_debug(1, "%s Error table not registered", __FUNCTION__);
goto ok;
}
requestvb = requests->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,
requestvb->name, requestvb->name_length,
reqinfo, requests)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(requests, 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, requests)) < 0)
goto done;
if (ret == 0){
// if ((ret = netsnmp_request_set_error(requests, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){
if ((ret = netsnmp_request_set_error(requests, 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
// 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, requests)) < 0)
goto done;
if (ret == 0){
if ((ret = netsnmp_request_set_error(requests, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
goto done;
}
clicon_debug(1, "%s Nosuchinstance", __FUNCTION__);
}
break;
case MODE_SET_COMMIT: // 3
if (clicon_rpc_commit(sh->sh_h) < 0)
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 request handler, loop over individual requests
*/
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;
}