/* Portions of this file are subject to the following copyright(s). See * the Net-SNMP's COPYING file for more details and other copyrights * that may apply: */ /* * Portions of this file are copyrighted by: * Copyright @ 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms specified in the COPYING file * distributed with the Net-SNMP package. * * Portions of this file are copyrighted by: * Copyright (c) 2016 VMware, Inc. All rights reserved. * Use is subject to license terms specified in the COPYING file * distributed with the Net-SNMP package. */ #include #include #include #if HAVE_STRING_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #include #include #include "proxy.h" netsnmp_feature_require(handler_mark_requests_as_delegated) netsnmp_feature_require(request_set_error_idx) static struct simple_proxy *proxies = NULL; /* * this must be standardized somewhere, right? */ #define MAX_ARGS 128 char *context_string; static void proxyOptProc(int argc, char *const *argv, int opt) { switch (opt) { case 'C': while (*optarg) { switch (*optarg++) { case 'n': optind++; if (optind < argc) { context_string = argv[optind - 1]; } else { config_perror("No context name passed to -Cn"); } break; case 'c': netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_IGNORE_NO_COMMUNITY, 1); break; default: config_perror("unknown argument passed to -C"); break; } } break; default: break; /* * shouldn't get here */ } } void proxy_parse_config(const char *token, char *line) { /* * proxy args [base-oid] [remap-to-remote-oid] */ netsnmp_session session, *ss; struct simple_proxy *newp, **listpp; char *argv[MAX_ARGS]; int argn, arg; char *cp; char *buff; netsnmp_handler_registration *reg; context_string = NULL; DEBUGMSGTL(("proxy_config", "entering\n")); /* Put the first string into the array */ argv[0] = strdup("snmpd-proxy"); if (!argv[0]) { config_perror("could not allocate memory for argv[0]"); return; } /* * create the argv[] like array */ /* Allocates memory to store the parameters value */ buff = (char *) malloc (strlen(line)+1); if (!buff) { config_perror("could not allocate memory for buff"); /* Free the memory allocated */ SNMP_FREE(argv[0]); return; } for (argn = 1, cp = line; cp && argn < MAX_ARGS;) { /* Copy a parameter into the buff */ cp = copy_nword(cp, buff, strlen(cp)+1); argv[argn] = strdup(buff); if (!argv[argn]) { config_perror("could not allocate memory for argv[n]"); while(argn--) SNMP_FREE(argv[argn]); SNMP_FREE(buff); return; } argn++; } SNMP_FREE(buff); for (arg = 0; arg < argn; arg++) { DEBUGMSGTL(("proxy_args", "final args: %d = %s\n", arg, argv[arg])); } DEBUGMSGTL(("proxy_config", "parsing args: %d\n", argn)); /* Call special parse_args that allows for no specified community string */ arg = netsnmp_parse_args(argn, argv, &session, "C:", proxyOptProc, NETSNMP_PARSE_ARGS_NOLOGGING | NETSNMP_PARSE_ARGS_NOZERO); /* reset this in case we modified it */ netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_IGNORE_NO_COMMUNITY, 0); if (arg < 0) { config_perror("failed to parse proxy args"); /* Free the memory allocated */ while(argn--) SNMP_FREE(argv[argn]); return; } DEBUGMSGTL(("proxy_config", "done parsing args\n")); if (arg >= argn) { config_perror("missing base oid"); /* Free the memory allocated */ while(argn--) SNMP_FREE(argv[argn]); return; } /* * usm_set_reportErrorOnUnknownID(0); * * hack, stupid v3 ASIs. */ /* * XXX: on a side note, we don't really need to be a reference * platform any more so the proper thing to do would be to fix * snmplib/snmpusm.c to pass in the pdu type to usm_process_incoming * so this isn't needed. */ ss = snmp_open(&session); /* * usm_set_reportErrorOnUnknownID(1); */ if (ss == NULL) { /* * diagnose snmp_open errors with the input netsnmp_session pointer */ snmp_sess_perror("snmpget", &session); /* Free the memory allocated */ while(argn--) SNMP_FREE(argv[argn]); return; } newp = (struct simple_proxy *) calloc(1, sizeof(struct simple_proxy)); newp->sess = ss; DEBUGMSGTL(("proxy_init", "name = %s\n", argv[arg])); newp->name_len = MAX_OID_LEN; if (!snmp_parse_oid(argv[arg++], newp->name, &newp->name_len)) { snmp_perror("proxy"); config_perror("illegal proxy oid specified\n"); /*deallocate the memory previously allocated*/ SNMP_FREE(newp); while(argn--) SNMP_FREE(argv[argn]); return; } if (arg < argn) { DEBUGMSGTL(("proxy_init", "base = %s\n", argv[arg])); newp->base_len = MAX_OID_LEN; if (!snmp_parse_oid(argv[arg++], newp->base, &newp->base_len)) { snmp_perror("proxy"); config_perror("illegal variable name specified (base oid)\n"); SNMP_FREE(newp); /* Free the memory allocated */ while(argn--) SNMP_FREE(argv[argn]); return; } } if ( context_string ) newp->context = strdup(context_string); DEBUGMSGTL(("proxy_init", "registering at: ")); DEBUGMSGOID(("proxy_init", newp->name, newp->name_len)); DEBUGMSG(("proxy_init", "\n")); /* * add to our chain */ /* * must be sorted! */ listpp = &proxies; while (*listpp && snmp_oid_compare(newp->name, newp->name_len, (*listpp)->name, (*listpp)->name_len) > 0) { listpp = &((*listpp)->next); } /* * listpp should be next in line from us. */ if (*listpp) { /* * make our next in the link point to the current link */ newp->next = *listpp; } /* * replace current link with us */ *listpp = newp; reg = netsnmp_create_handler_registration("proxy", proxy_handler, newp->name, newp->name_len, HANDLER_CAN_RWRITE); reg->handler->myvoid = newp; if (context_string) reg->contextName = strdup(context_string); netsnmp_register_handler(reg); /* Free the memory allocated */ while(argn--) SNMP_FREE(argv[argn]); } void proxy_free_config(void) { struct simple_proxy *rm; DEBUGMSGTL(("proxy_free_config", "Free config\n")); while (proxies) { rm = proxies; proxies = rm->next; DEBUGMSGTL(( "proxy_free_config", "freeing ")); DEBUGMSGOID(("proxy_free_config", rm->name, rm->name_len)); DEBUGMSG(( "proxy_free_config", " (%s)\n", rm->context)); unregister_mib_context(rm->name, rm->name_len, DEFAULT_MIB_PRIORITY, 0, 0, rm->context); SNMP_FREE(rm->variables); SNMP_FREE(rm->context); snmp_close(rm->sess); SNMP_FREE(rm); } } /* * Configure special parameters on the session. * Currently takes the parameter configured and changes it if something * was configured. It becomes "-c" if the community string from the pdu * is placed on the session. */ int proxy_fill_in_session(netsnmp_mib_handler *handler, netsnmp_agent_request_info *reqinfo, void **configured) { netsnmp_session *session; struct simple_proxy *sp; sp = (struct simple_proxy *) handler->myvoid; if (!sp) { return 0; } session = sp->sess; if (!session) { return 0; } #if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) if ( #ifndef NETSNMP_DISABLE_SNMPV1 ((session->version == SNMP_VERSION_1) && !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_V1)) || #endif #ifndef NETSNMP_DISABLE_SNMPV2C ((session->version == SNMP_VERSION_2c) && !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_V2c)) || #endif 0 ) { /* 0 to terminate '||' above */ /* * Check if session has community string defined for it. * If not, need to extract community string from the pdu. * Copy to session and set 'configured' to indicate this. */ if (session->community_len == 0) { DEBUGMSGTL(("proxy", "session has no community string\n")); if (reqinfo->asp == NULL || reqinfo->asp->pdu == NULL || reqinfo->asp->pdu->community_len == 0) { return 0; } *configured = strdup("-c"); DEBUGMSGTL(("proxy", "pdu has community string\n")); session->community_len = reqinfo->asp->pdu->community_len; session->community = malloc(session->community_len + 1); sprintf((char *)session->community, "%.*s", (int) session->community_len, (const char *)reqinfo->asp->pdu->community); } } #endif return 1; } /* * Free any specially configured parameters used on the session. */ void proxy_free_filled_in_session_args(netsnmp_session *session, void **configured) { /* Only do comparisions, etc., if something was configured */ if (*configured == NULL) { return; } /* If used community string from pdu, release it from session now */ if (strcmp((const char *)(*configured), "-c") == 0) { free(session->community); session->community = NULL; session->community_len = 0; } free((u_char *)(*configured)); *configured = NULL; } void init_proxy(void) { snmpd_register_config_handler("proxy", proxy_parse_config, proxy_free_config, "[snmpcmd args] host oid [remoteoid]"); } void shutdown_proxy(void) { proxy_free_config(); } int proxy_handler(netsnmp_mib_handler *handler, netsnmp_handler_registration *reginfo, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests) { netsnmp_pdu *pdu; struct simple_proxy *sp; oid *ourname; size_t ourlength; netsnmp_request_info *request = requests; u_char *configured = NULL; DEBUGMSGTL(("proxy", "proxy handler starting, mode = %d\n", reqinfo->mode)); switch (reqinfo->mode) { case MODE_GET: case MODE_GETNEXT: case MODE_GETBULK: /* WWWXXX */ pdu = snmp_pdu_create(reqinfo->mode); break; #ifndef NETSNMP_NO_WRITE_SUPPORT case MODE_SET_ACTION: pdu = snmp_pdu_create(SNMP_MSG_SET); break; case MODE_SET_UNDO: /* * If we set successfully (status == NOERROR), * we can't back out again, so need to report the fact. * If we failed to set successfully, then we're fine. */ for (request = requests; request; request=request->next) { if (request->status == SNMP_ERR_NOERROR) { netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED); return SNMP_ERR_UNDOFAILED; } } return SNMP_ERR_NOERROR; case MODE_SET_RESERVE1: case MODE_SET_RESERVE2: case MODE_SET_FREE: case MODE_SET_COMMIT: /* * Nothing to do in this pass */ return SNMP_ERR_NOERROR; #endif /* !NETSNMP_NO_WRITE_SUPPORT */ default: snmp_log(LOG_WARNING, "unsupported mode for proxy called (%d)\n", reqinfo->mode); return SNMP_ERR_NOERROR; } sp = (struct simple_proxy *) handler->myvoid; if (!pdu || !sp) { netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR); if (pdu) snmp_free_pdu(pdu); return SNMP_ERR_NOERROR; } while (request) { ourname = request->requestvb->name; ourlength = request->requestvb->name_length; if (sp->base_len && reqinfo->mode == MODE_GETNEXT && (snmp_oid_compare(ourname, ourlength, sp->base, sp->base_len) < 0)) { DEBUGMSGTL(( "proxy", "request is out of registered range\n")); /* * Create GETNEXT request with an OID so the * master returns the first OID in the registered range. */ memcpy(ourname, sp->base, sp->base_len * sizeof(oid)); ourlength = sp->base_len; if (ourname[ourlength-1] <= 1) { /* * The registered range ends with x.y.z.1 * -> ask for the next of x.y.z */ ourlength--; } else { /* * The registered range ends with x.y.z.A * -> ask for the next of x.y.z.A-1.MAX_SUBID */ ourname[ourlength-1]--; ourname[ourlength] = MAX_SUBID; ourlength++; } } else if (sp->base_len > 0) { if ((ourlength - sp->name_len + sp->base_len) > MAX_OID_LEN) { /* * too large */ if (pdu) snmp_free_pdu(pdu); snmp_log(LOG_ERR, "proxy oid request length is too long\n"); return SNMP_ERR_NOERROR; } /* * suffix appended? */ DEBUGMSGTL(("proxy", "length=%d, base_len=%d, name_len=%d\n", (int)ourlength, (int)sp->base_len, (int)sp->name_len)); if (ourlength > sp->name_len) memcpy(&(sp->base[sp->base_len]), &(ourname[sp->name_len]), sizeof(oid) * (ourlength - sp->name_len)); ourlength = ourlength - sp->name_len + sp->base_len; ourname = sp->base; } snmp_pdu_add_variable(pdu, ourname, ourlength, request->requestvb->type, request->requestvb->val.string, request->requestvb->val_len); request->delegated = 1; request = request->next; } /* * Customize session parameters based on request information */ if (!proxy_fill_in_session(handler, reqinfo, (void **)&configured)) { netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR); if (pdu) snmp_free_pdu(pdu); return SNMP_ERR_NOERROR; } /* * send the request out */ DEBUGMSGTL(("proxy", "sending pdu\n")); snmp_async_send(sp->sess, pdu, proxy_got_response, netsnmp_create_delegated_cache(handler, reginfo, reqinfo, requests, (void *) sp)); /* Free any special parameters generated on the session */ proxy_free_filled_in_session_args(sp->sess, (void **)&configured); return SNMP_ERR_NOERROR; } int proxy_got_response(int operation, netsnmp_session * sess, int reqid, netsnmp_pdu *pdu, void *cb_data) { netsnmp_delegated_cache *cache = (netsnmp_delegated_cache *) cb_data; netsnmp_request_info *requests, *request = NULL; netsnmp_variable_list *vars, *var = NULL; struct simple_proxy *sp; oid myname[MAX_OID_LEN]; size_t myname_len = MAX_OID_LEN; cache = netsnmp_handler_check_cache(cache); if (!cache) { DEBUGMSGTL(("proxy", "a proxy request was no longer valid.\n")); return SNMP_ERR_NOERROR; } requests = cache->requests; sp = (struct simple_proxy *) cache->localinfo; if (!sp) { DEBUGMSGTL(("proxy", "a proxy request was no longer valid.\n")); return SNMP_ERR_NOERROR; } switch (operation) { case NETSNMP_CALLBACK_OP_TIMED_OUT: /* * WWWXXX: don't leave requests delayed if operation is * something like TIMEOUT */ DEBUGMSGTL(("proxy", "got timed out... requests = %8p\n", requests)); netsnmp_handler_mark_requests_as_delegated(requests, REQUEST_IS_NOT_DELEGATED); if(cache->reqinfo->mode != MODE_GETNEXT) { DEBUGMSGTL(("proxy", " ignoring timeout\n")); netsnmp_set_request_error(cache->reqinfo, requests, /* XXXWWW: should be index = 0 */ SNMP_ERR_GENERR); } netsnmp_free_delegated_cache(cache); return 0; case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: vars = pdu->variables; if (pdu->errstat != SNMP_ERR_NOERROR) { /* * If we receive an error from the proxy agent, pass it on up. * The higher-level processing seems to Do The Right Thing. * * 2005/06 rks: actually, it doesn't do the right thing for * a get-next request that returns NOSUCHNAME. If we do nothing, * it passes that error back to the comman initiator. What it should * do is ignore the error and move on to the next tree. To * accomplish that, all we need to do is clear the delegated flag. * Not sure if any other error codes need the same treatment. Left * as an exercise to the reader... */ DEBUGMSGTL(("proxy", "got error response (%ld)\n", pdu->errstat)); if((cache->reqinfo->mode == MODE_GETNEXT) && (SNMP_ERR_NOSUCHNAME == pdu->errstat)) { DEBUGMSGTL(("proxy", " ignoring error response\n")); netsnmp_handler_mark_requests_as_delegated(requests, REQUEST_IS_NOT_DELEGATED); } #ifndef NETSNMP_NO_WRITE_SUPPORT else if (cache->reqinfo->mode == MODE_SET_ACTION) { /* * In order for netsnmp_wrap_up_request to consider the * SET request complete, * there must be no delegated requests pending. * https://sourceforge.net/tracker/ * ?func=detail&atid=112694&aid=1554261&group_id=12694 */ DEBUGMSGTL(("proxy", "got SET error %s, index %ld\n", snmp_errstring(pdu->errstat), pdu->errindex)); netsnmp_handler_mark_requests_as_delegated( requests, REQUEST_IS_NOT_DELEGATED); netsnmp_request_set_error_idx(requests, pdu->errstat, pdu->errindex); } #endif /* !NETSNMP_NO_WRITE_SUPPORT */ else { netsnmp_handler_mark_requests_as_delegated( requests, REQUEST_IS_NOT_DELEGATED); netsnmp_request_set_error_idx(requests, pdu->errstat, pdu->errindex); } /* * update the original request varbinds with the results */ } else for (var = vars, request = requests; request && var; request = request->next, var = var->next_variable) { /* * XXX - should this be done here? * Or wait until we know it's OK? */ snmp_set_var_typed_value(request->requestvb, var->type, var->val.string, var->val_len); DEBUGMSGTL(("proxy", "got response... ")); DEBUGMSGOID(("proxy", var->name, var->name_length)); DEBUGMSG(("proxy", "\n")); request->delegated = 0; /* * Check the response oid is legitimate, * and discard the value if not. * * XXX - what's the difference between these cases? */ if (sp->base_len && (var->name_length < sp->base_len || snmp_oid_compare(var->name, sp->base_len, sp->base, sp->base_len) != 0)) { DEBUGMSGTL(( "proxy", "out of registered range... ")); DEBUGMSGOID(("proxy", var->name, sp->base_len)); DEBUGMSG(( "proxy", " (%d) != ", (int)sp->base_len)); DEBUGMSGOID(("proxy", sp->base, sp->base_len)); DEBUGMSG(( "proxy", "\n")); snmp_set_var_typed_value(request->requestvb, ASN_NULL, NULL, 0); continue; } else if (!sp->base_len && (var->name_length < sp->name_len || snmp_oid_compare(var->name, sp->name_len, sp->name, sp->name_len) != 0)) { DEBUGMSGTL(( "proxy", "out of registered base range... ")); DEBUGMSGOID(("proxy", var->name, sp->name_len)); DEBUGMSG(( "proxy", " (%d) != ", (int)sp->name_len)); DEBUGMSGOID(("proxy", sp->name, sp->name_len)); DEBUGMSG(( "proxy", "\n")); snmp_set_var_typed_value(request->requestvb, ASN_NULL, NULL, 0); continue; } else { /* * If the returned OID is legitimate, then update * the original request varbind accordingly. */ if (sp->base_len) { /* * XXX: oid size maxed? */ memcpy(myname, sp->name, sizeof(oid) * sp->name_len); myname_len = sp->name_len + var->name_length - sp->base_len; if (myname_len > MAX_OID_LEN) { snmp_log(LOG_WARNING, "proxy OID return length too long.\n"); netsnmp_set_request_error(cache->reqinfo, requests, SNMP_ERR_GENERR); if (pdu) snmp_free_pdu(pdu); netsnmp_free_delegated_cache(cache); return 1; } if (var->name_length > sp->base_len) memcpy(&myname[sp->name_len], &var->name[sp->base_len], sizeof(oid) * (var->name_length - sp->base_len)); snmp_set_var_objid(request->requestvb, myname, myname_len); } else { snmp_set_var_objid(request->requestvb, var->name, var->name_length); } } } if (request || var) { /* * ack, this is bad. The # of varbinds don't match and * there is no way to fix the problem */ if (pdu) snmp_free_pdu(pdu); snmp_log(LOG_ERR, "response to proxy request illegal. We're screwed.\n"); netsnmp_set_request_error(cache->reqinfo, requests, SNMP_ERR_GENERR); } /* fix bulk_to_next operations */ if (cache->reqinfo->mode == MODE_GETBULK) netsnmp_bulk_to_next_fix_requests(requests); /* * free the response */ if (pdu && 0) snmp_free_pdu(pdu); break; default: DEBUGMSGTL(("proxy", "no response received: op = %d\n", operation)); break; } netsnmp_free_delegated_cache(cache); return 1; }