/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2022 Olof Hagsand and Kristofer Hallin 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 /* net-snmp */ #include #include #include /* cligen */ #include /* clicon */ #include #include "snmp_lib.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 */ clixon_snmp_handle *sh; char oidstr0[MAX_OID_LEN*2] = {0,}; char oidstr1[MAX_OID_LEN*2] = {0,}; char oidstr2[MAX_OID_LEN*2] = {0,}; requestvb = requests->requestvb; if (snprint_objid(oidstr0, sizeof(oidstr0), requestvb->name, requestvb->name_length) < 0){ clicon_err(OE_XML, 0, "snprint_objid buffer too small"); } if ((sh = (clixon_snmp_handle*)handler->myvoid) != NULL){ if (snprint_objid(oidstr1, sizeof(oidstr1), nhreg->rootoid, nhreg->rootoid_len) < 0){ clicon_err(OE_XML, 0, "snprint_objid buffer too small"); goto done; } if (snprint_objid(oidstr2, sizeof(oidstr2), sh->sh_oid, sh->sh_oidlen) < 0){ clicon_err(OE_XML, 0, "snprint_objid buffer too small"); goto done; } if (shp) *shp = sh; if (strcmp(oidstr0, oidstr2) == 0) clicon_debug(1, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__, oidstr2, snmp_msg_int2str(reqinfo->mode), requests->inclusive, tablehandler?"table":""); else clicon_debug(1, "%s \"%s\"/\"%s\" %s inclusive:%d %s", __FUNCTION__, oidstr2, oidstr0, snmp_msg_int2str(reqinfo->mode), requests->inclusive, tablehandler?"table":""); } else{ assert(0); } retval = 0; done: 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 * XXX Check expected return values for these netsnmp handler functions */ 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; clixon_snmp_handle *sh = NULL; cvec *nsc = NULL; cxobj *xt = NULL; cbuf *cb = NULL; int ret; if ((ret = snmp_common_handler(handler, nhreg, reqinfo, requests, &sh, 1)) < 0) goto done; switch(reqinfo->mode){ case MODE_GETNEXT:{ // 160 #ifdef NOTYET char *oidstr = NULL; oid oid1[MAX_OID_LEN] = {0,}; size_t sz1 = MAX_OID_LEN; oidstr = ".1.3.6.1.2.1.2.2.1.1.1"; if (snmp_parse_oid(oidstr, oid1, &sz1) == NULL){ clicon_err(OE_XML, errno, "snmp_parse_oid"); goto done; } if (snmp_set_var_objid(requests->requestvb, oid1, sz1) != 0){ clicon_err(OE_XML, 0, "snmp_set_var_objid"); goto done; } #endif } break; case MODE_GET: // 160 case MODE_SET_RESERVE1: case MODE_SET_RESERVE2: case MODE_SET_ACTION: case MODE_SET_COMMIT: 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; } /*! 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 */ static int snmp_scalar_get(clicon_handle h, yang_stmt *ys, cvec *cvk, netsnmp_variable_list *requestvb, 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 *snmpstr = NULL; u_char *snmpval = NULL; size_t snmplen = 0; int ret; int asn1type; char *reason = NULL; /* Prepare backend call by constructing namespace context */ if (xml_nsctx_yang(ys, &nsc) < 0) goto done; /* Create xpath from yang (XXX works not for lists) */ if (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 here. It 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_xml2snmpstr(xml_body(x), ys, &snmpstr)) < 0) goto done; if (ret == 0){ netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_WRONGVALUE); goto ok; } } else if (defaultval != NULL){ if ((snmpstr = strdup(defaultval)) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); goto done; } } else{ netsnmp_set_request_error(reqinfo, requests, SNMP_NOSUCHINSTANCE); goto ok; } if (type_yang2asn1(ys, &asn1type) < 0) goto done; if ((ret = type_snmpstr2val(snmpstr, asn1type, &snmpval, &snmplen, &reason)) < 0) goto done; if (ret == 0){ clicon_debug(1, "%s %s", __FUNCTION__, reason); netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_WRONGTYPE); goto ok; } /* see snmplib/snmp_client. somewhat indirect */ requestvb->type = asn1type; // ASN_NULL on input if ((ret = snmp_set_var_value(requestvb, snmpval, snmplen)) != SNMPERR_SUCCESS){ clicon_err(OE_SNMP, ret, "snmp_set_var_value"); goto done; } ok: retval = 0; done: if (reason) free(reason); if (snmpstr) free(snmpstr); 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 */ static int snmp_scalar_set(clicon_handle h, yang_stmt *ys, netsnmp_variable_list *requestvb, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests) { int retval = -1; char *api_path = NULL; cxobj *xtop = NULL; cxobj *xbot = NULL; cxobj *xb; yang_stmt *yspec; int ret; char *valstr = NULL; cbuf *cb = NULL; 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) < 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; if ((ret = type_snmp2xml(ys, requestvb, reqinfo, requests, &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 (clicon_xml2cbuf(cb, xtop, 0, 0, -1) < 0) goto done; if (clicon_rpc_edit_config(h, "candidate", OP_MERGE, 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; } /*! 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; yang_stmt *ys; int asn1_type; netsnmp_variable_list *requestvb = requests->requestvb; if (snmp_common_handler(handler, nhreg, reqinfo, requests, &sh, 0) < 0) goto done; ys = sh->sh_ys; #if 0 /* If oid match fails */ netsnmp_set_request_error(reqinfo, requests, SNMP_NOSUCHOBJECT); return SNMP_ERR_NOERROR; #endif /* 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, ys, sh->sh_cvk, requestvb, 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(ys, &asn1_type) < 0) goto done; if (requestvb->type != asn1_type) netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_WRONGTYPE); break; case MODE_SET_RESERVE2: /* 1 */ break; case MODE_SET_ACTION: /* 2 */ if (snmp_scalar_set(sh->sh_h, ys, requestvb, reqinfo, requests) < 0) goto done; break; case MODE_SET_UNDO: /* 5 */ if (clicon_rpc_discard_changes(sh->sh_h) < 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; } retval = SNMP_ERR_NOERROR; done: return retval; }