/* * ***** 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 #include #include #include #include #include #include #include /* net-snmp */ #include #include #include /* cligen */ #include /* clicon */ #include #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 */ static int snmp_scalar_set(clicon_handle h, yang_stmt *ys, cvec *cvk, 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; 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; irequestvb; 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, 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; } /*! 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; irequestvb; 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 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 */ 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; 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; }