diff options
Diffstat (limited to 'agent/snmp_agent.c')
-rw-r--r-- | agent/snmp_agent.c | 3838 |
1 files changed, 3838 insertions, 0 deletions
diff --git a/agent/snmp_agent.c b/agent/snmp_agent.c new file mode 100644 index 0000000..0b44a81 --- /dev/null +++ b/agent/snmp_agent.c @@ -0,0 +1,3838 @@ +/* + * snmp_agent.c + * + * Simple Network Management Protocol (RFC 1067). + */ +/* 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 subject to the following copyrights. See + * the Net-SNMP's COPYING file for more details and other copyrights + * that may apply: + */ +/*********************************************************** + Copyright 1988, 1989 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of CMU not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. +******************************************************************/ +/* + * Portions of this file are copyrighted by: + * Copyright © 2003 Sun Microsystems, Inc. All rights + * reserved. Use is subject to license terms specified in the + * COPYING file distributed with the Net-SNMP package. + */ +/** @defgroup snmp_agent net-snmp agent related processing + * @ingroup agent + * + * @{ + */ +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-features.h> + +#include <sys/types.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_STRING_H +#include <string.h> +#endif +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#if HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#include <errno.h> + +#define SNMP_NEED_REQUEST_LIST +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> +#include <net-snmp/library/snmp_assert.h> + +#if HAVE_SYSLOG_H +#include <syslog.h> +#endif + +#ifdef NETSNMP_USE_LIBWRAP +#include <tcpd.h> +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#include "snmpd.h" +#include <net-snmp/agent/mib_module_config.h> +#include <net-snmp/agent/mib_modules.h> + +#ifdef USING_AGENTX_PROTOCOL_MODULE +#include "agentx/protocol.h" +#endif + +#ifdef USING_AGENTX_MASTER_MODULE +#include "agentx/master.h" +#endif + +#ifdef USING_SMUX_MODULE +#include "smux/smux.h" +#endif + +netsnmp_feature_child_of(snmp_agent, libnetsnmpagent) +netsnmp_feature_child_of(agent_debugging_utilities, libnetsnmpagent) + +netsnmp_feature_child_of(allocate_globalcacheid, snmp_agent) +netsnmp_feature_child_of(free_agent_snmp_session_by_session, snmp_agent) +netsnmp_feature_child_of(check_all_requests_error, snmp_agent) +netsnmp_feature_child_of(check_requests_error, snmp_agent) +netsnmp_feature_child_of(request_set_error_idx, snmp_agent) +netsnmp_feature_child_of(set_agent_uptime, snmp_agent) +netsnmp_feature_child_of(agent_check_and_process, snmp_agent) + +netsnmp_feature_child_of(dump_sess_list, agent_debugging_utilities) + +netsnmp_feature_child_of(agent_remove_list_data, netsnmp_unused) +netsnmp_feature_child_of(set_all_requests_error, netsnmp_unused) +netsnmp_feature_child_of(addrcache_age, netsnmp_unused) +netsnmp_feature_child_of(delete_subtree_cache, netsnmp_unused) + + +NETSNMP_INLINE void +netsnmp_agent_add_list_data(netsnmp_agent_request_info *ari, + netsnmp_data_list *node) +{ + if (ari) { + if (ari->agent_data) { + netsnmp_add_list_data(&ari->agent_data, node); + } else { + ari->agent_data = node; + } + } +} + +#ifndef NETSNMP_FEATURE_REMOVE_AGENT_REMOVE_LIST_DATA +NETSNMP_INLINE int +netsnmp_agent_remove_list_data(netsnmp_agent_request_info *ari, + const char * name) +{ + if ((NULL == ari) || (NULL == ari->agent_data)) + return 1; + + return netsnmp_remove_list_node(&ari->agent_data, name); +} +#endif /* NETSNMP_FEATURE_REMOVE_AGENT_REMOVE_LIST_DATA */ + +NETSNMP_INLINE void * +netsnmp_agent_get_list_data(netsnmp_agent_request_info *ari, + const char *name) +{ + if (ari) { + return netsnmp_get_list_data(ari->agent_data, name); + } + return NULL; +} + +NETSNMP_INLINE void +netsnmp_free_agent_data_set(netsnmp_agent_request_info *ari) +{ + if (ari) { + netsnmp_free_list_data(ari->agent_data); + } +} + +NETSNMP_INLINE void +netsnmp_free_agent_data_sets(netsnmp_agent_request_info *ari) +{ + if (ari) { + netsnmp_free_all_list_data(ari->agent_data); + } +} + +NETSNMP_INLINE void +netsnmp_free_agent_request_info(netsnmp_agent_request_info *ari) +{ + if (ari) { + if (ari->agent_data) { + netsnmp_free_all_list_data(ari->agent_data); + } + SNMP_FREE(ari); + } +} + +oid version_sysoid[] = { NETSNMP_SYSTEM_MIB }; +int version_sysoid_len = OID_LENGTH(version_sysoid); + +#define SNMP_ADDRCACHE_SIZE 10 +#define SNMP_ADDRCACHE_MAXAGE 300 /* in seconds */ + +enum { + SNMP_ADDRCACHE_UNUSED = 0, + SNMP_ADDRCACHE_USED = 1 +}; + +struct addrCache { + char *addr; + int status; + struct timeval lastHitM; +}; + +static struct addrCache addrCache[SNMP_ADDRCACHE_SIZE]; +int log_addresses = 0; + + + +typedef struct _agent_nsap { + int handle; + netsnmp_transport *t; + void *s; /* Opaque internal session pointer. */ + struct _agent_nsap *next; +} agent_nsap; + +static agent_nsap *agent_nsap_list = NULL; +static netsnmp_agent_session *agent_session_list = NULL; +netsnmp_agent_session *netsnmp_processing_set = NULL; +netsnmp_agent_session *agent_delegated_list = NULL; +netsnmp_agent_session *netsnmp_agent_queued_list = NULL; + + +int netsnmp_agent_check_packet(netsnmp_session *, + struct netsnmp_transport_s *, + void *, int); +int netsnmp_agent_check_parse(netsnmp_session *, netsnmp_pdu *, + int); +void delete_subnetsnmp_tree_cache(netsnmp_agent_session *asp); +int handle_pdu(netsnmp_agent_session *asp); +int netsnmp_handle_request(netsnmp_agent_session *asp, + int status); +int netsnmp_wrap_up_request(netsnmp_agent_session *asp, + int status); +int check_delayed_request(netsnmp_agent_session *asp); +int handle_getnext_loop(netsnmp_agent_session *asp); +int handle_set_loop(netsnmp_agent_session *asp); + +int netsnmp_check_queued_chain_for(netsnmp_agent_session *asp); +int netsnmp_add_queued(netsnmp_agent_session *asp); +int netsnmp_remove_from_delegated(netsnmp_agent_session *asp); + + +static int current_globalid = 0; + +int netsnmp_running = 1; + +#ifndef NETSNMP_FEATURE_REMOVE_ALLOCATE_GLOBALCACHEID +int +netsnmp_allocate_globalcacheid(void) +{ + return ++current_globalid; +} +#endif /* NETSNMP_FEATURE_REMOVE_ALLOCATE_GLOBALCACHEID */ + +int +netsnmp_get_local_cachid(netsnmp_cachemap *cache_store, int globalid) +{ + while (cache_store != NULL) { + if (cache_store->globalid == globalid) + return cache_store->cacheid; + cache_store = cache_store->next; + } + return -1; +} + +netsnmp_cachemap * +netsnmp_get_or_add_local_cachid(netsnmp_cachemap **cache_store, + int globalid, int localid) +{ + netsnmp_cachemap *tmpp; + + tmpp = SNMP_MALLOC_TYPEDEF(netsnmp_cachemap); + if (tmpp != NULL) { + if (*cache_store) { + tmpp->next = *cache_store; + *cache_store = tmpp; + } else { + *cache_store = tmpp; + } + + tmpp->globalid = globalid; + tmpp->cacheid = localid; + } + return tmpp; +} + +void +netsnmp_free_cachemap(netsnmp_cachemap *cache_store) +{ + netsnmp_cachemap *tmpp; + while (cache_store) { + tmpp = cache_store; + cache_store = cache_store->next; + SNMP_FREE(tmpp); + } +} + + +typedef struct agent_set_cache_s { + /* + * match on these 2 + */ + int transID; + netsnmp_session *sess; + + /* + * store this info + */ + netsnmp_tree_cache *treecache; + int treecache_len; + int treecache_num; + + int vbcount; + netsnmp_request_info *requests; + netsnmp_variable_list *saved_vars; + netsnmp_data_list *agent_data; + + /* + * list + */ + struct agent_set_cache_s *next; +} agent_set_cache; + +static agent_set_cache *Sets = NULL; + +agent_set_cache * +save_set_cache(netsnmp_agent_session *asp) +{ + agent_set_cache *ptr; + + if (!asp || !asp->reqinfo || !asp->pdu) + return NULL; + + ptr = SNMP_MALLOC_TYPEDEF(agent_set_cache); + if (ptr == NULL) + return NULL; + + /* + * Save the important information + */ + DEBUGMSGTL(("verbose:asp", "asp %p reqinfo %p saved in cache (mode %d)\n", + asp, asp->reqinfo, asp->pdu->command)); + ptr->transID = asp->pdu->transid; + ptr->sess = asp->session; + ptr->treecache = asp->treecache; + ptr->treecache_len = asp->treecache_len; + ptr->treecache_num = asp->treecache_num; + ptr->agent_data = asp->reqinfo->agent_data; + ptr->requests = asp->requests; + ptr->saved_vars = asp->pdu->variables; /* requests contains pointers to variables */ + ptr->vbcount = asp->vbcount; + + /* + * make the agent forget about what we've saved + */ + asp->treecache = NULL; + asp->reqinfo->agent_data = NULL; + asp->pdu->variables = NULL; + asp->requests = NULL; + + ptr->next = Sets; + Sets = ptr; + + return ptr; +} + +int +get_set_cache(netsnmp_agent_session *asp) +{ + agent_set_cache *ptr, *prev = NULL; + + for (ptr = Sets; ptr != NULL; ptr = ptr->next) { + if (ptr->sess == asp->session && ptr->transID == asp->pdu->transid) { + /* + * remove this item from list + */ + if (prev) + prev->next = ptr->next; + else + Sets = ptr->next; + + /* + * found it. Get the needed data + */ + asp->treecache = ptr->treecache; + asp->treecache_len = ptr->treecache_len; + asp->treecache_num = ptr->treecache_num; + + /* + * Free previously allocated requests before overwriting by + * cached ones, otherwise memory leaks! + */ + if (asp->requests) { + /* + * I don't think this case should ever happen. Please email + * the net-snmp-coders@lists.sourceforge.net if you have + * a test case that hits this condition. -- rstory + */ + int i; + netsnmp_assert(NULL == asp->requests); /* see note above */ + for (i = 0; i < asp->vbcount; i++) { + netsnmp_free_request_data_sets(&asp->requests[i]); + } + free(asp->requests); + } + /* + * If we replace asp->requests with the info from the set cache, + * we should replace asp->pdu->variables also with the cached + * info, as asp->requests contains pointers to them. And we + * should also free the current asp->pdu->variables list... + */ + if (ptr->saved_vars) { + if (asp->pdu->variables) + snmp_free_varbind(asp->pdu->variables); + asp->pdu->variables = ptr->saved_vars; + asp->vbcount = ptr->vbcount; + } else { + /* + * when would we not have saved variables? someone + * let me know if they hit this condition. -- rstory + */ + netsnmp_assert(NULL != ptr->saved_vars); + } + asp->requests = ptr->requests; + + netsnmp_assert(NULL != asp->reqinfo); + asp->reqinfo->asp = asp; + asp->reqinfo->agent_data = ptr->agent_data; + + /* + * update request reqinfo, if it's out of date. + * yyy-rks: investigate when/why sometimes they match, + * sometimes they don't. + */ + if(asp->requests->agent_req_info != asp->reqinfo) { + /* + * - one don't match case: agentx subagents. prev asp & reqinfo + * freed, request reqinfo ptrs not cleared. + */ + netsnmp_request_info *tmp = asp->requests; + DEBUGMSGTL(("verbose:asp", + " reqinfo %p doesn't match cached reqinfo %p\n", + asp->reqinfo, asp->requests->agent_req_info)); + for(; tmp; tmp = tmp->next) + tmp->agent_req_info = asp->reqinfo; + } else { + /* + * - match case: ? + */ + DEBUGMSGTL(("verbose:asp", + " reqinfo %p matches cached reqinfo %p\n", + asp->reqinfo, asp->requests->agent_req_info)); + } + + SNMP_FREE(ptr); + return SNMP_ERR_NOERROR; + } + prev = ptr; + } + return SNMP_ERR_GENERR; +} + +/* Bulkcache holds the values for the *repeating* varbinds (only), + * but ordered "by column" - i.e. the repetitions for each + * repeating varbind follow on immediately from one another, + * rather than being interleaved, as required by the protocol. + * + * So we need to rearrange the varbind list so it's ordered "by row". + * + * In the following code chunk: + * n = # non-repeating varbinds + * r = # repeating varbinds + * asp->vbcount = # varbinds in the incoming PDU + * (So asp->vbcount = n+r) + * + * repeats = Desired # of repetitions (of 'r' varbinds) + */ +NETSNMP_STATIC_INLINE void +_reorder_getbulk(netsnmp_agent_session *asp) +{ + int i, n = 0, r = 0; + int repeats = asp->pdu->errindex; + int j, k; + int all_eoMib; + netsnmp_variable_list *prev = NULL, *curr; + + if (asp->vbcount == 0) /* Nothing to do! */ + return; + + if (asp->pdu->errstat < asp->vbcount) { + n = asp->pdu->errstat; + } else { + n = asp->vbcount; + } + if ((r = asp->vbcount - n) < 0) { + r = 0; + } + + /* we do nothing if there is nothing repeated */ + if (r == 0) + return; + + /* Fix endOfMibView entries. */ + for (i = 0; i < r; i++) { + prev = NULL; + for (j = 0; j < repeats; j++) { + curr = asp->bulkcache[i * repeats + j]; + /* + * If we don't have a valid name for a given repetition + * (and probably for all the ones that follow as well), + * extend the previous result to indicate 'endOfMibView'. + * Or if the repetition already has type endOfMibView make + * sure it has the correct objid (i.e. that of the previous + * entry or that of the original request). + */ + if (curr->name_length == 0 || curr->type == SNMP_ENDOFMIBVIEW) { + if (prev == NULL) { + /* Use objid from original pdu. */ + prev = asp->orig_pdu->variables; + for (k = i; prev && k > 0; k--) + prev = prev->next_variable; + } + if (prev) { + snmp_set_var_objid(curr, prev->name, prev->name_length); + snmp_set_var_typed_value(curr, SNMP_ENDOFMIBVIEW, NULL, 0); + } + } + prev = curr; + } + } + + /* + * For each of the original repeating varbinds (except the last), + * go through the block of results for that varbind, + * and link each instance to the corresponding instance + * in the next block. + */ + for (i = 0; i < r - 1; i++) { + for (j = 0; j < repeats; j++) { + asp->bulkcache[i * repeats + j]->next_variable = + asp->bulkcache[(i + 1) * repeats + j]; + } + } + + /* + * For the last of the original repeating varbinds, + * go through that block of results, and link each + * instance to the *next* instance in the *first* block. + * + * The very last instance of this block is left untouched + * since it (correctly) points to the end of the list. + */ + for (j = 0; j < repeats - 1; j++) { + asp->bulkcache[(r - 1) * repeats + j]->next_variable = + asp->bulkcache[j + 1]; + } + + /* + * If we've got a full row of endOfMibViews, then we + * can truncate the result varbind list after that. + * + * Look for endOfMibView exception values in the list of + * repetitions for the first varbind, and check the + * corresponding instances for the other varbinds + * (following the next_variable links). + * + * If they're all endOfMibView too, then we can terminate + * the linked list there, and free any redundant varbinds. + */ + all_eoMib = 0; + for (i = 0; i < repeats; i++) { + if (asp->bulkcache[i]->type == SNMP_ENDOFMIBVIEW) { + all_eoMib = 1; + for (j = 1, prev=asp->bulkcache[i]; + j < r; + j++, prev=prev->next_variable) { + if (prev->type != SNMP_ENDOFMIBVIEW) { + all_eoMib = 0; + break; /* Found a real value */ + } + } + if (all_eoMib) { + /* + * This is indeed a full endOfMibView row. + * Terminate the list here & free the rest. + */ + snmp_free_varbind( prev->next_variable ); + prev->next_variable = NULL; + break; + } + } + } +} + + +/* EndOfMibView replies to a GETNEXT request should according to RFC3416 + * have the object ID set to that of the request. Our tree search + * algorithm will sometimes break that requirement. This function will + * fix that. + */ +NETSNMP_STATIC_INLINE void +_fix_endofmibview(netsnmp_agent_session *asp) +{ + netsnmp_variable_list *vb, *ovb; + + if (asp->vbcount == 0) /* Nothing to do! */ + return; + + for (vb = asp->pdu->variables, ovb = asp->orig_pdu->variables; + vb && ovb; vb = vb->next_variable, ovb = ovb->next_variable) { + if (vb->type == SNMP_ENDOFMIBVIEW) + snmp_set_var_objid(vb, ovb->name, ovb->name_length); + } +} + +#ifndef NETSNMP_FEATURE_REMOVE_AGENT_CHECK_AND_PROCESS +/** + * This function checks for packets arriving on the SNMP port and + * processes them(snmp_read) if some are found, using the select(). If block + * is non zero, the function call blocks until a packet arrives + * + * @param block used to control blocking in the select() function, 1 = block + * forever, and 0 = don't block + * + * @return Returns a positive integer if packets were processed, and -1 if an + * error was found. + * + */ +int +agent_check_and_process(int block) +{ + int numfds; + fd_set fdset; + struct timeval timeout = { LONG_MAX, 0 }, *tvp = &timeout; + int count; + int fakeblock = 0; + + numfds = 0; + FD_ZERO(&fdset); + snmp_select_info(&numfds, &fdset, tvp, &fakeblock); + if (block != 0 && fakeblock != 0) { + /* + * There are no alarms registered, and the caller asked for blocking, so + * let select() block forever. + */ + + tvp = NULL; + } else if (block != 0 && fakeblock == 0) { + /* + * The caller asked for blocking, but there is an alarm due sooner than + * LONG_MAX seconds from now, so use the modified timeout returned by + * snmp_select_info as the timeout for select(). + */ + + } else if (block == 0) { + /* + * The caller does not want us to block at all. + */ + + timerclear(tvp); + } + + count = select(numfds, &fdset, NULL, NULL, tvp); + + if (count > 0) { + /* + * packets found, process them + */ + snmp_read(&fdset); + } else + switch (count) { + case 0: + snmp_timeout(); + break; + case -1: + if (errno != EINTR) { + snmp_log_perror("select"); + } + return -1; + default: + snmp_log(LOG_ERR, "select returned %d\n", count); + return -1; + } /* endif -- count>0 */ + + /* + * see if persistent store needs to be saved + */ + snmp_store_if_needed(); + + /* + * Run requested alarms. + */ + run_alarms(); + + netsnmp_check_outstanding_agent_requests(); + + return count; +} +#endif /* NETSNMP_FEATURE_REMOVE_AGENT_CHECK_AND_PROCESS */ + +/* + * Set up the address cache. + */ +void +netsnmp_addrcache_initialise(void) +{ + int i = 0; + + for (i = 0; i < SNMP_ADDRCACHE_SIZE; i++) { + addrCache[i].addr = NULL; + addrCache[i].status = SNMP_ADDRCACHE_UNUSED; + } +} + +void netsnmp_addrcache_destroy(void) +{ + int i = 0; + + for (i = 0; i < SNMP_ADDRCACHE_SIZE; i++) { + if (addrCache[i].status == SNMP_ADDRCACHE_USED) { + free(addrCache[i].addr); + addrCache[i].status = SNMP_ADDRCACHE_UNUSED; + } + } +} + +/* + * Adds a new entry to the cache of addresses that + * have recently made connections to the agent. + * Returns 0 if the entry already exists (but updates + * the entry with a new timestamp) and 1 if the + * entry did not previously exist. + * + * Implements a simple LRU cache replacement + * policy. Uses a linear search, which should be + * okay, as long as SNMP_ADDRCACHE_SIZE remains + * relatively small. + * + * @retval 0 : updated existing entry + * @retval 1 : added new entry + */ +int +netsnmp_addrcache_add(const char *addr) +{ + int oldest = -1; /* Index of the oldest cache entry */ + int unused = -1; /* Index of the first free cache entry */ + int i; /* Looping variable */ + int rc = -1; + struct timeval now; /* What time is it now? */ + struct timeval aged; /* Oldest allowable cache entry */ + + /* + * First get the current and oldest allowable timestamps + */ + netsnmp_get_monotonic_clock(&now); + aged.tv_sec = now.tv_sec - SNMP_ADDRCACHE_MAXAGE; + aged.tv_usec = now.tv_usec; + + /* + * Now look for a place to put this thing + */ + for(i = 0; i < SNMP_ADDRCACHE_SIZE; i++) { + if (addrCache[i].status == SNMP_ADDRCACHE_UNUSED) { /* If unused */ + /* + * remember this location, in case addr isn't in the cache + */ + if (unused < 0) + unused = i; + } + else { /* If used */ + if ((NULL != addr) && (strcmp(addrCache[i].addr, addr) == 0)) { + /* + * found a match + */ + addrCache[i].lastHitM = now; + if (timercmp(&addrCache[i].lastHitM, &aged, <)) + rc = 1; /* should have expired, so is new */ + else + rc = 0; /* not expired, so is existing entry */ + break; + } + else { + /* + * Used, but not this address. check if it's stale. + */ + if (timercmp(&addrCache[i].lastHitM, &aged, <)) { + /* + * Stale, reuse + */ + SNMP_FREE(addrCache[i].addr); + addrCache[i].status = SNMP_ADDRCACHE_UNUSED; + /* + * remember this location, in case addr isn't in the cache + */ + if (unused < 0) + unused = i; + } + else { + /* + * Still fresh, but a candidate for LRU replacement + */ + if (oldest < 0) + oldest = i; + else if (timercmp(&addrCache[i].lastHitM, + &addrCache[oldest].lastHitM, <)) + oldest = i; + } /* fresh */ + } /* used, no match */ + } /* used */ + } /* for loop */ + + if ((-1 == rc) && (NULL != addr)) { + /* + * We didn't find the entry in the cache + */ + if (unused >= 0) { + /* + * If we have a slot free anyway, use it + */ + addrCache[unused].addr = strdup(addr); + addrCache[unused].status = SNMP_ADDRCACHE_USED; + addrCache[unused].lastHitM = now; + } + else { /* Otherwise, replace oldest entry */ + if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_VERBOSE)) + snmp_log(LOG_INFO, "Purging address from address cache: %s", + addrCache[oldest].addr); + + free(addrCache[oldest].addr); + addrCache[oldest].addr = strdup(addr); + addrCache[oldest].lastHitM = now; + } + rc = 1; + } + if ((log_addresses && (1 == rc)) || + netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_VERBOSE)) { + snmp_log(LOG_INFO, "Received SNMP packet(s) from %s\n", addr); + } + + return rc; +} + +/* + * Age the entries in the address cache. + * + * backwards compatability; not used anywhere + */ +#ifndef NETSNMP_FEATURE_REMOVE_ADDRCACHE_AGE +void +netsnmp_addrcache_age(void) +{ + (void)netsnmp_addrcache_add(NULL); +} +#endif /* NETSNMP_FEATURE_REMOVE_ADDRCACHE_AGE */ + +/*******************************************************************-o-****** + * netsnmp_agent_check_packet + * + * Parameters: + * session, transport, transport_data, transport_data_length + * + * Returns: + * 1 On success. + * 0 On error. + * + * Handler for all incoming messages (a.k.a. packets) for the agent. If using + * the libwrap utility, log the connection and deny/allow the access. Print + * output when appropriate, and increment the incoming counter. + * + */ + +int +netsnmp_agent_check_packet(netsnmp_session * session, + netsnmp_transport *transport, + void *transport_data, int transport_data_length) +{ + char *addr_string = NULL; +#ifdef NETSNMP_USE_LIBWRAP + char *tcpudpaddr = NULL, *name; + short not_log_connection; + + name = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_APPTYPE); + + /* not_log_connection will be 1 if we should skip the messages */ + not_log_connection = netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_DONT_LOG_TCPWRAPPERS_CONNECTS); + + /* + * handle the error case + * default to logging the messages + */ + if (not_log_connection == SNMPERR_GENERR) not_log_connection = 0; +#endif + + /* + * Log the message and/or dump the message. + * Optionally cache the network address of the sender. + */ + + if (transport != NULL && transport->f_fmtaddr != NULL) { + /* + * Okay I do know how to format this address for logging. + */ + addr_string = transport->f_fmtaddr(transport, transport_data, + transport_data_length); + /* + * Don't forget to free() it. + */ + } +#ifdef NETSNMP_USE_LIBWRAP + /* Catch udp,udp6,tcp,tcp6 transports using "[" */ + if (addr_string) + tcpudpaddr = strstr(addr_string, "["); + if ( tcpudpaddr != 0 ) { + char sbuf[64]; + char *xp; + + strlcpy(sbuf, tcpudpaddr + 1, sizeof(sbuf)); + xp = strstr(sbuf, "]"); + if (xp) + *xp = '\0'; + + if (hosts_ctl(name, STRING_UNKNOWN, sbuf, STRING_UNKNOWN)) { + if (!not_log_connection) { + snmp_log(allow_severity, "Connection from %s\n", addr_string); + } + } else { + snmp_log(deny_severity, "Connection from %s REFUSED\n", + addr_string); + SNMP_FREE(addr_string); + return 0; + } + } else { + /* + * don't log callback connections. + * What about 'Local IPC', 'IPX' and 'AAL5 PVC'? + */ + if (0 == strncmp(addr_string, "callback", 8)) + ; + else if (hosts_ctl(name, STRING_UNKNOWN, STRING_UNKNOWN, STRING_UNKNOWN)){ + if (!not_log_connection) { + snmp_log(allow_severity, "Connection from <UNKNOWN> (%s)\n", addr_string); + }; + SNMP_FREE(addr_string); + addr_string = strdup("<UNKNOWN>"); + } else { + snmp_log(deny_severity, "Connection from <UNKNOWN> (%s) REFUSED\n", addr_string); + SNMP_FREE(addr_string); + return 0; + } + } +#endif /*NETSNMP_USE_LIBWRAP */ + + snmp_increment_statistic(STAT_SNMPINPKTS); + + if (addr_string != NULL) { + netsnmp_addrcache_add(addr_string); + SNMP_FREE(addr_string); + } + return 1; +} + + +int +netsnmp_agent_check_parse(netsnmp_session * session, netsnmp_pdu *pdu, + int result) +{ + if (result == 0) { + if (snmp_get_do_logging() && + netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_VERBOSE)) { + netsnmp_variable_list *var_ptr; + + switch (pdu->command) { + case SNMP_MSG_GET: + snmp_log(LOG_DEBUG, " GET message\n"); + break; + case SNMP_MSG_GETNEXT: + snmp_log(LOG_DEBUG, " GETNEXT message\n"); + break; + case SNMP_MSG_RESPONSE: + snmp_log(LOG_DEBUG, " RESPONSE message\n"); + break; +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: + snmp_log(LOG_DEBUG, " SET message\n"); + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + case SNMP_MSG_TRAP: + snmp_log(LOG_DEBUG, " TRAP message\n"); + break; + case SNMP_MSG_GETBULK: + snmp_log(LOG_DEBUG, " GETBULK message, non-rep=%ld, max_rep=%ld\n", + pdu->errstat, pdu->errindex); + break; + case SNMP_MSG_INFORM: + snmp_log(LOG_DEBUG, " INFORM message\n"); + break; + case SNMP_MSG_TRAP2: + snmp_log(LOG_DEBUG, " TRAP2 message\n"); + break; + case SNMP_MSG_REPORT: + snmp_log(LOG_DEBUG, " REPORT message\n"); + break; + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_INTERNAL_SET_RESERVE1: + snmp_log(LOG_DEBUG, " INTERNAL RESERVE1 message\n"); + break; + + case SNMP_MSG_INTERNAL_SET_RESERVE2: + snmp_log(LOG_DEBUG, " INTERNAL RESERVE2 message\n"); + break; + + case SNMP_MSG_INTERNAL_SET_ACTION: + snmp_log(LOG_DEBUG, " INTERNAL ACTION message\n"); + break; + + case SNMP_MSG_INTERNAL_SET_COMMIT: + snmp_log(LOG_DEBUG, " INTERNAL COMMIT message\n"); + break; + + case SNMP_MSG_INTERNAL_SET_FREE: + snmp_log(LOG_DEBUG, " INTERNAL FREE message\n"); + break; + + case SNMP_MSG_INTERNAL_SET_UNDO: + snmp_log(LOG_DEBUG, " INTERNAL UNDO message\n"); + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + default: + snmp_log(LOG_DEBUG, " UNKNOWN message, type=%02X\n", + pdu->command); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + return 0; + } + + for (var_ptr = pdu->variables; var_ptr != NULL; + var_ptr = var_ptr->next_variable) { + size_t c_oidlen = 256, c_outlen = 0; + u_char *c_oid = (u_char *) malloc(c_oidlen); + + if (c_oid) { + if (!sprint_realloc_objid + (&c_oid, &c_oidlen, &c_outlen, 1, var_ptr->name, + var_ptr->name_length)) { + snmp_log(LOG_DEBUG, " -- %s [TRUNCATED]\n", + c_oid); + } else { + snmp_log(LOG_DEBUG, " -- %s\n", c_oid); + } + SNMP_FREE(c_oid); + } + } + } + return 1; + } + return 0; /* XXX: does it matter what the return value + * is? Yes: if we return 0, then the PDU is + * dumped. */ +} + + +/* + * Global access to the primary session structure for this agent. + * for Index Allocation use initially. + */ + +/* + * I don't understand what this is for at the moment. AFAICS as long as it + * gets set and points at a session, that's fine. ??? + */ + +netsnmp_session *main_session = NULL; + + + +/* + * Set up an agent session on the given transport. Return a handle + * which may later be used to de-register this transport. A return + * value of -1 indicates an error. + */ + +int +netsnmp_register_agent_nsap(netsnmp_transport *t) +{ + netsnmp_session *s, *sp = NULL; + agent_nsap *a = NULL, *n = NULL, **prevNext = &agent_nsap_list; + int handle = 0; + void *isp = NULL; + + if (t == NULL) { + return -1; + } + + DEBUGMSGTL(("netsnmp_register_agent_nsap", "fd %d\n", t->sock)); + + n = (agent_nsap *) malloc(sizeof(agent_nsap)); + if (n == NULL) { + return -1; + } + s = (netsnmp_session *) malloc(sizeof(netsnmp_session)); + if (s == NULL) { + SNMP_FREE(n); + return -1; + } + memset(s, 0, sizeof(netsnmp_session)); + snmp_sess_init(s); + + /* + * Set up the session appropriately for an agent. + */ + + s->version = SNMP_DEFAULT_VERSION; + s->callback = handle_snmp_packet; + s->authenticator = NULL; + s->flags = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_FLAGS); + s->isAuthoritative = SNMP_SESS_AUTHORITATIVE; + + /* Optional supplimental transport configuration information and + final call to actually open the transport */ + if (netsnmp_sess_config_transport(s->transport_configuration, t) + != SNMPERR_SUCCESS) { + SNMP_FREE(s); + SNMP_FREE(n); + return -1; + } + + + if (t->f_open) + t = t->f_open(t); + + if (NULL == t) { + SNMP_FREE(s); + SNMP_FREE(n); + return -1; + } + + t->flags |= NETSNMP_TRANSPORT_FLAG_OPENED; + + sp = snmp_add(s, t, netsnmp_agent_check_packet, + netsnmp_agent_check_parse); + if (sp == NULL) { + SNMP_FREE(s); + SNMP_FREE(n); + return -1; + } + + isp = snmp_sess_pointer(sp); + if (isp == NULL) { /* over-cautious */ + SNMP_FREE(s); + SNMP_FREE(n); + return -1; + } + + n->s = isp; + n->t = t; + + if (main_session == NULL) { + main_session = snmp_sess_session(isp); + } + + for (a = agent_nsap_list; a != NULL && handle + 1 >= a->handle; + a = a->next) { + handle = a->handle; + prevNext = &(a->next); + } + + if (handle < INT_MAX) { + n->handle = handle + 1; + n->next = a; + *prevNext = n; + SNMP_FREE(s); + return n->handle; + } else { + SNMP_FREE(s); + SNMP_FREE(n); + return -1; + } +} + +void +netsnmp_deregister_agent_nsap(int handle) +{ + agent_nsap *a = NULL, **prevNext = &agent_nsap_list; + int main_session_deregistered = 0; + + DEBUGMSGTL(("netsnmp_deregister_agent_nsap", "handle %d\n", handle)); + + for (a = agent_nsap_list; a != NULL && a->handle < handle; a = a->next) { + prevNext = &(a->next); + } + + if (a != NULL && a->handle == handle) { + *prevNext = a->next; + if (snmp_sess_session_lookup(a->s)) { + if (main_session == snmp_sess_session(a->s)) { + main_session_deregistered = 1; + } + snmp_close(snmp_sess_session(a->s)); + /* + * The above free()s the transport and session pointers. + */ + } + SNMP_FREE(a); + } + + /* + * If we've deregistered the session that main_session used to point to, + * then make it point to another one, or in the last resort, make it equal + * to NULL. Basically this shouldn't ever happen in normal operation + * because main_session starts off pointing at the first session added by + * init_master_agent(), which then discards the handle. + */ + + if (main_session_deregistered) { + if (agent_nsap_list != NULL) { + DEBUGMSGTL(("snmp_agent", + "WARNING: main_session ptr changed from %p to %p\n", + main_session, snmp_sess_session(agent_nsap_list->s))); + main_session = snmp_sess_session(agent_nsap_list->s); + } else { + DEBUGMSGTL(("snmp_agent", + "WARNING: main_session ptr changed from %p to NULL\n", + main_session)); + main_session = NULL; + } + } +} + + + +/* + * + * This function has been modified to use the experimental + * netsnmp_register_agent_nsap interface. The major responsibility of this + * function now is to interpret a string specified to the agent (via -p on the + * command line, or from a configuration file) as a list of agent NSAPs on + * which to listen for SNMP packets. Typically, when you add a new transport + * domain "foo", you add code here such that if the "foo" code is compiled + * into the agent (SNMP_TRANSPORT_FOO_DOMAIN is defined), then a token of the + * form "foo:bletch-3a0054ef%wob&wob" gets turned into the appropriate + * transport descriptor. netsnmp_register_agent_nsap is then called with that + * transport descriptor and sets up a listening agent session on it. + * + * Everything then works much as normal: the agent runs in an infinite loop + * (in the snmpd.c/receive()routine), which calls snmp_read() when a request + * is readable on any of the given transports. This routine then traverses + * the library 'Sessions' list to identify the relevant session and eventually + * invokes '_sess_read'. This then processes the incoming packet, calling the + * pre_parse, parse, post_parse and callback routines in turn. + * + * JBPN 20001117 + */ + +int +init_master_agent(void) +{ + netsnmp_transport *transport; + char *cptr; + char *buf = NULL; + char *st; + + /* default to a default cache size */ + netsnmp_set_lookup_cache_size(-1); + + if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_ROLE) != MASTER_AGENT) { + DEBUGMSGTL(("snmp_agent", + "init_master_agent; not master agent\n")); + + netsnmp_assert("agent role !master && !sub_agent"); + + return 0; /* No error if ! MASTER_AGENT */ + } + +#ifndef NETSNMP_NO_LISTEN_SUPPORT + /* + * Have specific agent ports been specified? + */ + cptr = netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_PORTS); + + if (cptr) { + buf = strdup(cptr); + if (!buf) { + snmp_log(LOG_ERR, + "Error processing transport \"%s\"\n", cptr); + return 1; + } + } else { + /* + * No, so just specify the default port. + */ + buf = strdup(""); + } + + DEBUGMSGTL(("snmp_agent", "final port spec: \"%s\"\n", buf)); + st = buf; + do { + /* + * Specification format: + * + * NONE: (a pseudo-transport) + * UDP:[address:]port (also default if no transport is specified) + * TCP:[address:]port (if supported) + * Unix:pathname (if supported) + * AAL5PVC:itf.vpi.vci (if supported) + * IPX:[network]:node[/port] (if supported) + * + */ + + cptr = st; + st = strchr(st, ','); + if (st) + *st++ = '\0'; + + DEBUGMSGTL(("snmp_agent", "installing master agent on port %s\n", + cptr)); + + if (strncasecmp(cptr, "none", 4) == 0) { + DEBUGMSGTL(("snmp_agent", + "init_master_agent; pseudo-transport \"none\" " + "requested\n")); + break; + } + transport = netsnmp_transport_open_server("snmp", cptr); + + if (transport == NULL) { + snmp_log(LOG_ERR, "Error opening specified endpoint \"%s\"\n", + cptr); + return 1; + } + + if (netsnmp_register_agent_nsap(transport) == 0) { + snmp_log(LOG_ERR, + "Error registering specified transport \"%s\" as an " + "agent NSAP\n", cptr); + return 1; + } else { + DEBUGMSGTL(("snmp_agent", + "init_master_agent; \"%s\" registered as an agent " + "NSAP\n", cptr)); + } + } while(st && *st != '\0'); + SNMP_FREE(buf); +#endif /* NETSNMP_NO_LISTEN_SUPPORT */ + +#ifdef USING_AGENTX_MASTER_MODULE + if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_AGENTX_MASTER) == 1) + real_init_master(); +#endif +#ifdef USING_SMUX_MODULE + if(should_init("smux")) + real_init_smux(); +#endif + + return 0; +} + +void +clear_nsap_list(void) +{ + DEBUGMSGTL(("clear_nsap_list", "clear the nsap list\n")); + + while (agent_nsap_list != NULL) + netsnmp_deregister_agent_nsap(agent_nsap_list->handle); +} + +void +shutdown_master_agent(void) +{ + clear_nsap_list(); +} + + +netsnmp_agent_session * +init_agent_snmp_session(netsnmp_session * session, netsnmp_pdu *pdu) +{ + netsnmp_agent_session *asp = (netsnmp_agent_session *) + calloc(1, sizeof(netsnmp_agent_session)); + + if (asp == NULL) { + return NULL; + } + + DEBUGMSGTL(("snmp_agent","agent_sesion %8p created\n", asp)); + asp->session = session; + asp->pdu = snmp_clone_pdu(pdu); + asp->orig_pdu = snmp_clone_pdu(pdu); + asp->rw = READ; + asp->exact = TRUE; + asp->next = NULL; + asp->mode = RESERVE1; + asp->status = SNMP_ERR_NOERROR; + asp->index = 0; + asp->oldmode = 0; + asp->treecache_num = -1; + asp->treecache_len = 0; + asp->reqinfo = SNMP_MALLOC_TYPEDEF(netsnmp_agent_request_info); + DEBUGMSGTL(("verbose:asp", "asp %p reqinfo %p created\n", + asp, asp->reqinfo)); + + return asp; +} + +void +free_agent_snmp_session(netsnmp_agent_session *asp) +{ + if (!asp) + return; + + DEBUGMSGTL(("snmp_agent","agent_session %8p released\n", asp)); + + netsnmp_remove_from_delegated(asp); + + DEBUGMSGTL(("verbose:asp", "asp %p reqinfo %p freed\n", + asp, asp->reqinfo)); + if (asp->orig_pdu) + snmp_free_pdu(asp->orig_pdu); + if (asp->pdu) + snmp_free_pdu(asp->pdu); + if (asp->reqinfo) + netsnmp_free_agent_request_info(asp->reqinfo); + SNMP_FREE(asp->treecache); + SNMP_FREE(asp->bulkcache); + if (asp->requests) { + int i; + for (i = 0; i < asp->vbcount; i++) { + netsnmp_free_request_data_sets(&asp->requests[i]); + } + SNMP_FREE(asp->requests); + } + if (asp->cache_store) { + netsnmp_free_cachemap(asp->cache_store); + asp->cache_store = NULL; + } + SNMP_FREE(asp); +} + +int +netsnmp_check_for_delegated(netsnmp_agent_session *asp) +{ + int i; + netsnmp_request_info *request; + + if (NULL == asp->treecache) + return 0; + + for (i = 0; i <= asp->treecache_num; i++) { + for (request = asp->treecache[i].requests_begin; request; + request = request->next) { + if (request->delegated) + return 1; + } + } + return 0; +} + +int +netsnmp_check_delegated_chain_for(netsnmp_agent_session *asp) +{ + netsnmp_agent_session *asptmp; + for (asptmp = agent_delegated_list; asptmp; asptmp = asptmp->next) { + if (asptmp == asp) + return 1; + } + return 0; +} + +int +netsnmp_check_for_delegated_and_add(netsnmp_agent_session *asp) +{ + if (netsnmp_check_for_delegated(asp)) { + if (!netsnmp_check_delegated_chain_for(asp)) { + /* + * add to delegated request chain + */ + asp->next = agent_delegated_list; + agent_delegated_list = asp; + DEBUGMSGTL(("snmp_agent", "delegate session == %8p\n", asp)); + } + return 1; + } + return 0; +} + +int +netsnmp_remove_from_delegated(netsnmp_agent_session *asp) +{ + netsnmp_agent_session *curr, *prev = NULL; + + for (curr = agent_delegated_list; curr; prev = curr, curr = curr->next) { + /* + * is this us? + */ + if (curr != asp) + continue; + + /* + * remove from queue + */ + if (prev != NULL) + prev->next = asp->next; + else + agent_delegated_list = asp->next; + + DEBUGMSGTL(("snmp_agent", "remove delegated session == %8p\n", asp)); + + return 1; + } + + return 0; +} + +/* + * netsnmp_remove_delegated_requests_for_session + * + * called when a session is being closed. Check all delegated requests to + * see if the are waiting on this session, and if set, set the status for + * that request to GENERR. + */ +int +netsnmp_remove_delegated_requests_for_session(netsnmp_session *sess) +{ + netsnmp_agent_session *asp; + int count = 0; + + for (asp = agent_delegated_list; asp; asp = asp->next) { + /* + * check each request + */ + netsnmp_request_info *request; + for(request = asp->requests; request; request = request->next) { + /* + * check session + */ + netsnmp_assert(NULL!=request->subtree); + if(request->subtree->session != sess) + continue; + + /* + * matched! mark request as done + */ + netsnmp_request_set_error(request, SNMP_ERR_GENERR); + ++count; + } + } + + /* + * if we found any, that request may be finished now + */ + if(count) { + DEBUGMSGTL(("snmp_agent", "removed %d delegated request(s) for session " + "%8p\n", count, sess)); + netsnmp_check_outstanding_agent_requests(); + } + + return count; +} + +int +netsnmp_check_queued_chain_for(netsnmp_agent_session *asp) +{ + netsnmp_agent_session *asptmp; + for (asptmp = netsnmp_agent_queued_list; asptmp; asptmp = asptmp->next) { + if (asptmp == asp) + return 1; + } + return 0; +} + +int +netsnmp_add_queued(netsnmp_agent_session *asp) +{ + netsnmp_agent_session *asp_tmp; + + /* + * first item? + */ + if (NULL == netsnmp_agent_queued_list) { + netsnmp_agent_queued_list = asp; + return 1; + } + + + /* + * add to end of queued request chain + */ + asp_tmp = netsnmp_agent_queued_list; + for (; asp_tmp; asp_tmp = asp_tmp->next) { + /* + * already in queue? + */ + if (asp_tmp == asp) + break; + + /* + * end of queue? + */ + if (NULL == asp_tmp->next) + asp_tmp->next = asp; + } + return 1; +} + + +int +netsnmp_wrap_up_request(netsnmp_agent_session *asp, int status) +{ + /* + * if this request was a set, clear the global now that we are + * done. + */ + if (asp == netsnmp_processing_set) { + DEBUGMSGTL(("snmp_agent", "SET request complete, asp = %8p\n", + asp)); + netsnmp_processing_set = NULL; + } + + if (asp->pdu) { + /* + * If we've got an error status, then this needs to be + * passed back up to the higher levels.... + */ + if ( status != 0 && asp->status == 0 ) + asp->status = status; + + switch (asp->pdu->command) { +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_INTERNAL_SET_BEGIN: + case SNMP_MSG_INTERNAL_SET_RESERVE1: + case SNMP_MSG_INTERNAL_SET_RESERVE2: + case SNMP_MSG_INTERNAL_SET_ACTION: + /* + * some stuff needs to be saved in special subagent cases + */ + save_set_cache(asp); + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + case SNMP_MSG_GETNEXT: + _fix_endofmibview(asp); + break; + + case SNMP_MSG_GETBULK: + /* + * for a GETBULK response we need to rearrange the varbinds + */ + _reorder_getbulk(asp); + break; + } + + /* + * May need to "dumb down" a SET error status for a + * v1 query. See RFC2576 - section 4.3 + */ +#ifndef NETSNMP_DISABLE_SNMPV1 +#ifndef NETSNMP_NO_WRITE_SUPPORT + if ((asp->pdu->command == SNMP_MSG_SET) && + (asp->pdu->version == SNMP_VERSION_1)) { + switch (asp->status) { + case SNMP_ERR_WRONGVALUE: + case SNMP_ERR_WRONGENCODING: + case SNMP_ERR_WRONGTYPE: + case SNMP_ERR_WRONGLENGTH: + case SNMP_ERR_INCONSISTENTVALUE: + status = SNMP_ERR_BADVALUE; + asp->status = SNMP_ERR_BADVALUE; + break; + case SNMP_ERR_NOACCESS: + case SNMP_ERR_NOTWRITABLE: + case SNMP_ERR_NOCREATION: + case SNMP_ERR_INCONSISTENTNAME: + case SNMP_ERR_AUTHORIZATIONERROR: + status = SNMP_ERR_NOSUCHNAME; + asp->status = SNMP_ERR_NOSUCHNAME; + break; + case SNMP_ERR_RESOURCEUNAVAILABLE: + case SNMP_ERR_COMMITFAILED: + case SNMP_ERR_UNDOFAILED: + status = SNMP_ERR_GENERR; + asp->status = SNMP_ERR_GENERR; + break; + } + } + /* + * Similarly we may need to "dumb down" v2 exception + * types to throw an error for a v1 query. + * See RFC2576 - section 4.1.2.3 + */ + if ((asp->pdu->command != SNMP_MSG_SET) && + (asp->pdu->version == SNMP_VERSION_1)) { + netsnmp_variable_list *var_ptr = asp->pdu->variables; + int i = 1; + + while (var_ptr != NULL) { + switch (var_ptr->type) { + case SNMP_NOSUCHOBJECT: + case SNMP_NOSUCHINSTANCE: + case SNMP_ENDOFMIBVIEW: + case ASN_COUNTER64: + status = SNMP_ERR_NOSUCHNAME; + asp->status = SNMP_ERR_NOSUCHNAME; + asp->index = i; + break; + } + var_ptr = var_ptr->next_variable; + ++i; + } + } +#endif /* NETSNMP_NO_WRITE_SUPPORT */ +#endif /* snmpv1 support */ + } /** if asp->pdu */ + + /* + * Update the snmp error-count statistics + * XXX - should we include the V2 errors in this or not? + */ +#define INCLUDE_V2ERRORS_IN_V1STATS + + switch (status) { +#ifdef INCLUDE_V2ERRORS_IN_V1STATS + case SNMP_ERR_WRONGVALUE: + case SNMP_ERR_WRONGENCODING: + case SNMP_ERR_WRONGTYPE: + case SNMP_ERR_WRONGLENGTH: + case SNMP_ERR_INCONSISTENTVALUE: +#endif + case SNMP_ERR_BADVALUE: + snmp_increment_statistic(STAT_SNMPOUTBADVALUES); + break; +#ifdef INCLUDE_V2ERRORS_IN_V1STATS + case SNMP_ERR_NOACCESS: + case SNMP_ERR_NOTWRITABLE: + case SNMP_ERR_NOCREATION: + case SNMP_ERR_INCONSISTENTNAME: + case SNMP_ERR_AUTHORIZATIONERROR: +#endif + case SNMP_ERR_NOSUCHNAME: + snmp_increment_statistic(STAT_SNMPOUTNOSUCHNAMES); + break; +#ifdef INCLUDE_V2ERRORS_IN_V1STATS + case SNMP_ERR_RESOURCEUNAVAILABLE: + case SNMP_ERR_COMMITFAILED: + case SNMP_ERR_UNDOFAILED: +#endif + case SNMP_ERR_GENERR: + snmp_increment_statistic(STAT_SNMPOUTGENERRS); + break; + + case SNMP_ERR_TOOBIG: + snmp_increment_statistic(STAT_SNMPOUTTOOBIGS); + break; + } + + if ((status == SNMP_ERR_NOERROR) && (asp->pdu)) { +#ifndef NETSNMP_NO_WRITE_SUPPORT + snmp_increment_statistic_by((asp->pdu->command == SNMP_MSG_SET ? + STAT_SNMPINTOTALSETVARS : + STAT_SNMPINTOTALREQVARS), + count_varbinds(asp->pdu->variables)); +#else /* NETSNMP_NO_WRITE_SUPPORT */ + snmp_increment_statistic_by(STAT_SNMPINTOTALREQVARS, + count_varbinds(asp->pdu->variables)); +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + } else { + /* + * Use a copy of the original request + * to report failures. + */ + snmp_free_pdu(asp->pdu); + asp->pdu = asp->orig_pdu; + asp->orig_pdu = NULL; + } + if (asp->pdu) { + asp->pdu->command = SNMP_MSG_RESPONSE; + asp->pdu->errstat = asp->status; + asp->pdu->errindex = asp->index; + if (!snmp_send(asp->session, asp->pdu) && + asp->session->s_snmp_errno != SNMPERR_SUCCESS) { + netsnmp_variable_list *var_ptr; + snmp_perror("send response"); + for (var_ptr = asp->pdu->variables; var_ptr != NULL; + var_ptr = var_ptr->next_variable) { + size_t c_oidlen = 256, c_outlen = 0; + u_char *c_oid = (u_char *) malloc(c_oidlen); + + if (c_oid) { + if (!sprint_realloc_objid (&c_oid, &c_oidlen, &c_outlen, 1, + var_ptr->name, + var_ptr->name_length)) { + snmp_log(LOG_ERR, " -- %s [TRUNCATED]\n", c_oid); + } else { + snmp_log(LOG_ERR, " -- %s\n", c_oid); + } + SNMP_FREE(c_oid); + } + } + snmp_free_pdu(asp->pdu); + asp->pdu = NULL; + } + snmp_increment_statistic(STAT_SNMPOUTPKTS); + snmp_increment_statistic(STAT_SNMPOUTGETRESPONSES); + asp->pdu = NULL; /* yyy-rks: redundant, no? */ + netsnmp_remove_and_free_agent_snmp_session(asp); + } + return 1; +} + +#ifndef NETSNMP_FEATURE_REMOVE_DUMP_SESS_LIST +void +dump_sess_list(void) +{ + netsnmp_agent_session *a; + + DEBUGMSGTL(("snmp_agent", "DUMP agent_sess_list -> ")); + for (a = agent_session_list; a != NULL; a = a->next) { + DEBUGMSG(("snmp_agent", "%8p[session %8p] -> ", a, a->session)); + } + DEBUGMSG(("snmp_agent", "[NIL]\n")); +} +#endif /* NETSNMP_FEATURE_REMOVE_DUMP_SESS_LIST */ + +void +netsnmp_remove_and_free_agent_snmp_session(netsnmp_agent_session *asp) +{ + netsnmp_agent_session *a, **prevNext = &agent_session_list; + + DEBUGMSGTL(("snmp_agent", "REMOVE session == %8p\n", asp)); + + for (a = agent_session_list; a != NULL; a = *prevNext) { + if (a == asp) { + *prevNext = a->next; + a->next = NULL; + free_agent_snmp_session(a); + asp = NULL; + break; + } else { + prevNext = &(a->next); + } + } + + if (a == NULL && asp != NULL) { + /* + * We coulnd't find it on the list, so free it anyway. + */ + free_agent_snmp_session(asp); + } +} + +#ifndef NETSNMP_FEATURE_REMOVE_FREE_AGENT_SNMP_SESSION_BY_SESSION +void +netsnmp_free_agent_snmp_session_by_session(netsnmp_session * sess, + void (*free_request) + (netsnmp_request_list *)) +{ + netsnmp_agent_session *a, *next, **prevNext = &agent_session_list; + + DEBUGMSGTL(("snmp_agent", "REMOVE session == %8p\n", sess)); + + for (a = agent_session_list; a != NULL; a = next) { + if (a->session == sess) { + *prevNext = a->next; + next = a->next; + free_agent_snmp_session(a); + } else { + prevNext = &(a->next); + next = a->next; + } + } +} +#endif /* NETSNMP_FEATURE_REMOVE_FREE_AGENT_SNMP_SESSION_BY_SESSION */ + +/** handles an incoming SNMP packet into the agent */ +int +handle_snmp_packet(int op, netsnmp_session * session, int reqid, + netsnmp_pdu *pdu, void *magic) +{ + netsnmp_agent_session *asp; + int status, access_ret, rc; + + /* + * We only support receiving here. + */ + if (op != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) { + return 1; + } + + /* + * RESPONSE messages won't get this far, but TRAP-like messages + * might. + */ + if (pdu->command == SNMP_MSG_TRAP || pdu->command == SNMP_MSG_INFORM || + pdu->command == SNMP_MSG_TRAP2) { + DEBUGMSGTL(("snmp_agent", "received trap-like PDU (%02x)\n", + pdu->command)); + pdu->command = SNMP_MSG_TRAP2; + snmp_increment_statistic(STAT_SNMPUNKNOWNPDUHANDLERS); + return 1; + } + + /* + * send snmpv3 authfail trap. + */ + if (pdu->version == SNMP_VERSION_3 && + session->s_snmp_errno == SNMPERR_USM_AUTHENTICATIONFAILURE) { + send_easy_trap(SNMP_TRAP_AUTHFAIL, 0); + return 1; + } + + if (magic == NULL) { + asp = init_agent_snmp_session(session, pdu); + status = SNMP_ERR_NOERROR; + } else { + asp = (netsnmp_agent_session *) magic; + status = asp->status; + } + + if ((access_ret = check_access(asp->pdu)) != 0) { + if (access_ret == VACM_NOSUCHCONTEXT) { + /* + * rfc3413 section 3.2, step 5 says that we increment the + * counter but don't return a response of any kind + */ + + /* + * we currently don't support unavailable contexts, as + * there is no reason to that I currently know of + */ + snmp_increment_statistic(STAT_SNMPUNKNOWNCONTEXTS); + + /* + * drop the request + */ + netsnmp_remove_and_free_agent_snmp_session(asp); + return 0; + } else { + /* + * access control setup is incorrect + */ + send_easy_trap(SNMP_TRAP_AUTHFAIL, 0); +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +#if defined(NETSNMP_DISABLE_SNMPV1) + if (asp->pdu->version != SNMP_VERSION_2c) { +#else +#if defined(NETSNMP_DISABLE_SNMPV2C) + if (asp->pdu->version != SNMP_VERSION_1) { +#else + if (asp->pdu->version != SNMP_VERSION_1 + && asp->pdu->version != SNMP_VERSION_2c) { +#endif +#endif + asp->pdu->errstat = SNMP_ERR_AUTHORIZATIONERROR; + asp->pdu->command = SNMP_MSG_RESPONSE; + snmp_increment_statistic(STAT_SNMPOUTPKTS); + if (!snmp_send(asp->session, asp->pdu)) + snmp_free_pdu(asp->pdu); + asp->pdu = NULL; + netsnmp_remove_and_free_agent_snmp_session(asp); + return 1; + } else { +#endif /* support for community based SNMP */ + /* + * drop the request + */ + netsnmp_remove_and_free_agent_snmp_session(asp); + return 0; +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) + } +#endif /* support for community based SNMP */ + } + } + + rc = netsnmp_handle_request(asp, status); + + /* + * done + */ + DEBUGMSGTL(("snmp_agent", "end of handle_snmp_packet, asp = %8p\n", + asp)); + return rc; +} + +netsnmp_request_info * +netsnmp_add_varbind_to_cache(netsnmp_agent_session *asp, int vbcount, + netsnmp_variable_list * varbind_ptr, + netsnmp_subtree *tp) +{ + netsnmp_request_info *request = NULL; + + DEBUGMSGTL(("snmp_agent", "add_vb_to_cache(%8p, %d, ", asp, vbcount)); + DEBUGMSGOID(("snmp_agent", varbind_ptr->name, + varbind_ptr->name_length)); + DEBUGMSG(("snmp_agent", ", %8p)\n", tp)); + + if (tp && + (asp->pdu->command == SNMP_MSG_GETNEXT || + asp->pdu->command == SNMP_MSG_GETBULK)) { + int result; + int prefix_len; + + prefix_len = netsnmp_oid_find_prefix(tp->start_a, + tp->start_len, + tp->end_a, tp->end_len); + if (prefix_len < 1) { + result = VACM_NOTINVIEW; /* ack... bad bad thing happened */ + } else { + result = + netsnmp_acm_check_subtree(asp->pdu, tp->start_a, prefix_len); + } + + while (result == VACM_NOTINVIEW) { + /* the entire subtree is not in view. Skip it. */ + /** @todo make this be more intelligent about ranges. + Right now we merely take the highest level + commonality of a registration range and use that. + At times we might be able to be smarter about + checking the range itself as opposed to the node + above where the range exists, but I doubt this will + come up all that frequently. */ + tp = tp->next; + if (tp) { + prefix_len = netsnmp_oid_find_prefix(tp->start_a, + tp->start_len, + tp->end_a, + tp->end_len); + if (prefix_len < 1) { + /* ack... bad bad thing happened */ + result = VACM_NOTINVIEW; + } else { + result = + netsnmp_acm_check_subtree(asp->pdu, + tp->start_a, prefix_len); + } + } + else + break; + } + } + if (tp == NULL) { + /* + * no appropriate registration found + */ + /* + * make up the response ourselves + */ + switch (asp->pdu->command) { + case SNMP_MSG_GETNEXT: + case SNMP_MSG_GETBULK: + varbind_ptr->type = SNMP_ENDOFMIBVIEW; + break; + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + case SNMP_MSG_GET: + varbind_ptr->type = SNMP_NOSUCHOBJECT; + break; + + default: + return NULL; /* shouldn't get here */ + } + } else { + int cacheid; + + DEBUGMSGTL(("snmp_agent", "tp->start ")); + DEBUGMSGOID(("snmp_agent", tp->start_a, tp->start_len)); + DEBUGMSG(("snmp_agent", ", tp->end ")); + DEBUGMSGOID(("snmp_agent", tp->end_a, tp->end_len)); + DEBUGMSG(("snmp_agent", ", \n")); + + /* + * malloc the request structure + */ + request = &(asp->requests[vbcount - 1]); + request->index = vbcount; + request->delegated = 0; + request->processed = 0; + request->status = 0; + request->subtree = tp; + request->agent_req_info = asp->reqinfo; + if (request->parent_data) { + netsnmp_free_request_data_sets(request); + } + DEBUGMSGTL(("verbose:asp", "asp %p reqinfo %p assigned to request\n", + asp, asp->reqinfo)); + + /* + * for non-SET modes, set the type to NULL + */ +#ifndef NETSNMP_NO_WRITE_SUPPORT + if (!MODE_IS_SET(asp->pdu->command)) { +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + DEBUGMSGTL(("verbose:asp", "asp %p reqinfo %p assigned to request\n", + asp, asp->reqinfo)); + if (varbind_ptr->type == ASN_PRIV_INCL_RANGE) { + DEBUGMSGTL(("snmp_agent", "varbind %d is inclusive\n", + request->index)); + request->inclusive = 1; + } + varbind_ptr->type = ASN_NULL; +#ifndef NETSNMP_NO_WRITE_SUPPORT + } +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + /* + * place them in a cache + */ + if (tp->global_cacheid) { + /* + * we need to merge all marked subtrees together + */ + if (asp->cache_store && -1 != + (cacheid = netsnmp_get_local_cachid(asp->cache_store, + tp->global_cacheid))) { + } else { + cacheid = ++(asp->treecache_num); + netsnmp_get_or_add_local_cachid(&asp->cache_store, + tp->global_cacheid, + cacheid); + goto mallocslot; /* XXX: ick */ + } + } else if (tp->cacheid > -1 && tp->cacheid <= asp->treecache_num && + asp->treecache[tp->cacheid].subtree == tp) { + /* + * we have already added a request to this tree + * pointer before + */ + cacheid = tp->cacheid; + } else { + cacheid = ++(asp->treecache_num); + mallocslot: + /* + * new slot needed + */ + if (asp->treecache_num >= asp->treecache_len) { + /* + * exapand cache array + */ + /* + * WWW: non-linear expansion needed (with cap) + */ +#define CACHE_GROW_SIZE 16 + asp->treecache_len = + (asp->treecache_len + CACHE_GROW_SIZE); + asp->treecache = + (netsnmp_tree_cache *)realloc(asp->treecache, + sizeof(netsnmp_tree_cache) * + asp->treecache_len); + if (asp->treecache == NULL) + return NULL; + memset(&(asp->treecache[cacheid]), 0x00, + sizeof(netsnmp_tree_cache) * (CACHE_GROW_SIZE)); + } + asp->treecache[cacheid].subtree = tp; + asp->treecache[cacheid].requests_begin = request; + tp->cacheid = cacheid; + } + + /* + * if this is a search type, get the ending range oid as well + */ + if (asp->pdu->command == SNMP_MSG_GETNEXT || + asp->pdu->command == SNMP_MSG_GETBULK) { + request->range_end = tp->end_a; + request->range_end_len = tp->end_len; + } else { + request->range_end = NULL; + request->range_end_len = 0; + } + + /* + * link into chain + */ + if (asp->treecache[cacheid].requests_end) + asp->treecache[cacheid].requests_end->next = request; + request->next = NULL; + request->prev = asp->treecache[cacheid].requests_end; + asp->treecache[cacheid].requests_end = request; + + /* + * add the given request to the list of requests they need + * to handle results for + */ + request->requestvb = request->requestvb_start = varbind_ptr; + } + return request; +} + +/* + * check the ACM(s) for the results on each of the varbinds. + * If ACM disallows it, replace the value with type + * + * Returns number of varbinds with ACM errors + */ +int +check_acm(netsnmp_agent_session *asp, u_char type) +{ + int view; + int i, j, k; + netsnmp_request_info *request; + int ret = 0; + netsnmp_variable_list *vb, *vb2, *vbc; + int earliest = 0; + + for (i = 0; i <= asp->treecache_num; i++) { + for (request = asp->treecache[i].requests_begin; + request; request = request->next) { + /* + * for each request, run it through in_a_view() + */ + earliest = 0; + for(j = request->repeat, vb = request->requestvb_start; + vb && j > -1; + j--, vb = vb->next_variable) { + if (vb->type != ASN_NULL && + vb->type != ASN_PRIV_RETRY) { /* not yet processed */ + view = + in_a_view(vb->name, &vb->name_length, + asp->pdu, vb->type); + + /* + * if a ACM error occurs, mark it as type passed in + */ + if (view != VACM_SUCCESS) { + ret++; + if (request->repeat < request->orig_repeat) { + /* basically this means a GETBULK */ + request->repeat++; + if (!earliest) { + request->requestvb = vb; + earliest = 1; + } + + /* ugh. if a whole now exists, we need to + move the contents up the chain and fill + in at the end else we won't end up + lexographically sorted properly */ + if (j > -1 && vb->next_variable && + vb->next_variable->type != ASN_NULL && + vb->next_variable->type != ASN_PRIV_RETRY) { + for(k = j, vbc = vb, vb2 = vb->next_variable; + k > -2 && vbc && vb2; + k--, vbc = vb2, vb2 = vb2->next_variable) { + /* clone next into the current */ + snmp_clone_var(vb2, vbc); + vbc->next_variable = vb2; + } + } + } + snmp_set_var_typed_value(vb, type, NULL, 0); + } + } + } + } + } + return ret; +} + + +int +netsnmp_create_subtree_cache(netsnmp_agent_session *asp) +{ + netsnmp_subtree *tp; + netsnmp_variable_list *varbind_ptr, *vbsave, *vbptr, **prevNext; + int view; + int vbcount = 0; + int bulkcount = 0, bulkrep = 0; + int i = 0, n = 0, r = 0; + netsnmp_request_info *request; + + if (asp->treecache == NULL && asp->treecache_len == 0) { + asp->treecache_len = SNMP_MAX(1 + asp->vbcount / 4, 16); + asp->treecache = + (netsnmp_tree_cache *)calloc(asp->treecache_len, sizeof(netsnmp_tree_cache)); + if (asp->treecache == NULL) + return SNMP_ERR_GENERR; + } + asp->treecache_num = -1; + + if (asp->pdu->command == SNMP_MSG_GETBULK) { + /* + * getbulk prep + */ + int count = count_varbinds(asp->pdu->variables); + if (asp->pdu->errstat < 0) { + asp->pdu->errstat = 0; + } + if (asp->pdu->errindex < 0) { + asp->pdu->errindex = 0; + } + + if (asp->pdu->errstat < count) { + n = asp->pdu->errstat; + } else { + n = count; + } + if ((r = count - n) <= 0) { + r = 0; + asp->bulkcache = NULL; + } else { + int maxbulk = + netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_MAX_GETBULKREPEATS); + int maxresponses = + netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_MAX_GETBULKRESPONSES); + + if (maxresponses == 0) + maxresponses = 100; /* more than reasonable default */ + + /* ensure that the total number of responses fits in a mallocable + * result vector + */ + if (maxresponses < 0 || + maxresponses > (int)(INT_MAX / sizeof(struct varbind_list *))) + maxresponses = (int)(INT_MAX / sizeof(struct varbind_list *)); + + /* ensure that the maximum number of repetitions will fit in the + * result vector + */ + if (maxbulk <= 0 || maxbulk > maxresponses / r) + maxbulk = maxresponses / r; + + /* limit getbulk number of repeats to a configured size */ + if (asp->pdu->errindex > maxbulk) { + asp->pdu->errindex = maxbulk; + DEBUGMSGTL(("snmp_agent", + "truncating number of getbulk repeats to %ld\n", + asp->pdu->errindex)); + } + + asp->bulkcache = + (netsnmp_variable_list **) malloc( + (n + asp->pdu->errindex * r) * sizeof(struct varbind_list *)); + + if (!asp->bulkcache) { + DEBUGMSGTL(("snmp_agent", "Bulkcache malloc failed\n")); + return SNMP_ERR_GENERR; + } + } + DEBUGMSGTL(("snmp_agent", "GETBULK N = %d, M = %ld, R = %d\n", + n, asp->pdu->errindex, r)); + } + + /* + * collect varbinds into their registered trees + */ + prevNext = &(asp->pdu->variables); + for (varbind_ptr = asp->pdu->variables; varbind_ptr; + varbind_ptr = vbsave) { + + /* + * getbulk mess with this pointer, so save it + */ + vbsave = varbind_ptr->next_variable; + + if (asp->pdu->command == SNMP_MSG_GETBULK) { + if (n > 0) { + n--; + } else { + /* + * repeate request varbinds on GETBULK. These will + * have to be properly rearranged later though as + * responses are supposed to actually be interlaced + * with each other. This is done with the asp->bulkcache. + */ + bulkrep = asp->pdu->errindex - 1; + if (asp->pdu->errindex > 0) { + vbptr = varbind_ptr; + asp->bulkcache[bulkcount++] = vbptr; + + for (i = 1; i < asp->pdu->errindex; i++) { + vbptr->next_variable = + SNMP_MALLOC_STRUCT(variable_list); + /* + * don't clone the oid as it's got to be + * overwritten anyway + */ + if (!vbptr->next_variable) { + /* + * XXXWWW: ack!!! + */ + DEBUGMSGTL(("snmp_agent", "NextVar malloc failed\n")); + } else { + vbptr = vbptr->next_variable; + vbptr->name_length = 0; + vbptr->type = ASN_NULL; + asp->bulkcache[bulkcount++] = vbptr; + } + } + vbptr->next_variable = vbsave; + } else { + /* + * 0 repeats requested for this varbind, so take it off + * the list. + */ + vbptr = varbind_ptr; + *prevNext = vbptr->next_variable; + vbptr->next_variable = NULL; + snmp_free_varbind(vbptr); + asp->vbcount--; + continue; + } + } + } + + /* + * count the varbinds + */ + ++vbcount; + + /* + * find the owning tree + */ + tp = netsnmp_subtree_find(varbind_ptr->name, varbind_ptr->name_length, + NULL, asp->pdu->contextName); + + /* + * check access control + */ + switch (asp->pdu->command) { + case SNMP_MSG_GET: + view = in_a_view(varbind_ptr->name, &varbind_ptr->name_length, + asp->pdu, varbind_ptr->type); + if (view != VACM_SUCCESS) + snmp_set_var_typed_value(varbind_ptr, SNMP_NOSUCHOBJECT, + NULL, 0); + break; + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: + view = in_a_view(varbind_ptr->name, &varbind_ptr->name_length, + asp->pdu, varbind_ptr->type); + if (view != VACM_SUCCESS) { + asp->index = vbcount; + return SNMP_ERR_NOACCESS; + } + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + case SNMP_MSG_GETNEXT: + case SNMP_MSG_GETBULK: + default: + view = VACM_SUCCESS; + /* + * XXXWWW: check VACM here to see if "tp" is even worthwhile + */ + } + if (view == VACM_SUCCESS) { + request = netsnmp_add_varbind_to_cache(asp, vbcount, varbind_ptr, + tp); + if (request && asp->pdu->command == SNMP_MSG_GETBULK) { + request->repeat = request->orig_repeat = bulkrep; + } + } + + prevNext = &(varbind_ptr->next_variable); + } + + return SNMPERR_SUCCESS; +} + +/* + * this function is only applicable in getnext like contexts + */ +int +netsnmp_reassign_requests(netsnmp_agent_session *asp) +{ + /* + * assume all the requests have been filled or rejected by the + * subtrees, so reassign the rejected ones to the next subtree in + * the chain + */ + + int i; + + /* + * get old info + */ + netsnmp_tree_cache *old_treecache = asp->treecache; + + /* + * malloc new space + */ + asp->treecache = + (netsnmp_tree_cache *) calloc(asp->treecache_len, + sizeof(netsnmp_tree_cache)); + + if (asp->treecache == NULL) + return SNMP_ERR_GENERR; + + asp->treecache_num = -1; + if (asp->cache_store) { + netsnmp_free_cachemap(asp->cache_store); + asp->cache_store = NULL; + } + + for (i = 0; i < asp->vbcount; i++) { + if (asp->requests[i].requestvb == NULL) { + /* + * Occurs when there's a mixture of still active + * and "endOfMibView" repetitions + */ + continue; + } + if (asp->requests[i].requestvb->type == ASN_NULL) { + if (!netsnmp_add_varbind_to_cache(asp, asp->requests[i].index, + asp->requests[i].requestvb, + asp->requests[i].subtree->next)) { + SNMP_FREE(old_treecache); + } + } else if (asp->requests[i].requestvb->type == ASN_PRIV_RETRY) { + /* + * re-add the same subtree + */ + asp->requests[i].requestvb->type = ASN_NULL; + if (!netsnmp_add_varbind_to_cache(asp, asp->requests[i].index, + asp->requests[i].requestvb, + asp->requests[i].subtree)) { + SNMP_FREE(old_treecache); + } + } + } + + SNMP_FREE(old_treecache); + return SNMP_ERR_NOERROR; +} + +void +netsnmp_delete_request_infos(netsnmp_request_info *reqlist) +{ + while (reqlist) { + netsnmp_free_request_data_sets(reqlist); + reqlist = reqlist->next; + } +} + +#ifndef NETSNMP_FEATURE_REMOVE_DELETE_SUBTREE_CACHE +void +netsnmp_delete_subtree_cache(netsnmp_agent_session *asp) +{ + while (asp->treecache_num >= 0) { + /* + * don't delete subtrees + */ + netsnmp_delete_request_infos(asp->treecache[asp->treecache_num]. + requests_begin); + asp->treecache_num--; + } +} +#endif /* NETSNMP_FEATURE_REMOVE_DELETE_SUBTREE_CACHE */ + +#ifndef NETSNMP_FEATURE_REMOVE_CHECK_ALL_REQUESTS_ERROR +/* + * check all requests for errors + * + * @Note: + * This function is a little different from the others in that + * it does not use any linked lists, instead using the original + * asp requests array. This is of particular importance for + * cases where the linked lists are unreliable. One known instance + * of this scenario occurs when the row_merge helper is used, which + * may temporarily disrupts linked lists during its (and its childrens) + * handling of requests. + */ +int +netsnmp_check_all_requests_error(netsnmp_agent_session *asp, + int look_for_specific) +{ + int i; + + /* + * find any errors marked in the requests + */ + for( i = 0; i < asp->vbcount; ++i ) { + if ((SNMP_ERR_NOERROR != asp->requests[i].status) && + (!look_for_specific || + asp->requests[i].status == look_for_specific)) + return asp->requests[i].status; + } + + return SNMP_ERR_NOERROR; +} +#endif /* NETSNMP_FEATURE_REMOVE_CHECK_ALL_REQUESTS_ERROR */ + +#ifndef NETSNMP_FEATURE_REMOVE_CHECK_REQUESTS_ERROR +int +netsnmp_check_requests_error(netsnmp_request_info *requests) +{ + /* + * find any errors marked in the requests + */ + for (;requests;requests = requests->next) { + if (requests->status != SNMP_ERR_NOERROR) + return requests->status; + } + return SNMP_ERR_NOERROR; +} +#endif /* NETSNMP_FEATURE_REMOVE_CHECK_REQUESTS_ERROR */ + +int +netsnmp_check_requests_status(netsnmp_agent_session *asp, + netsnmp_request_info *requests, + int look_for_specific) +{ + /* + * find any errors marked in the requests + */ + while (requests) { + if(requests->agent_req_info != asp->reqinfo) { + DEBUGMSGTL(("verbose:asp", + "**reqinfo %p doesn't match cached reqinfo %p\n", + asp->reqinfo, requests->agent_req_info)); + } + if (requests->status != SNMP_ERR_NOERROR && + (!look_for_specific || requests->status == look_for_specific) + && (look_for_specific || asp->index == 0 + || requests->index < asp->index)) { + asp->index = requests->index; + asp->status = requests->status; + } + requests = requests->next; + } + return asp->status; +} + +int +netsnmp_check_all_requests_status(netsnmp_agent_session *asp, + int look_for_specific) +{ + int i; + for (i = 0; i <= asp->treecache_num; i++) { + netsnmp_check_requests_status(asp, + asp->treecache[i].requests_begin, + look_for_specific); + } + return asp->status; +} + +int +handle_var_requests(netsnmp_agent_session *asp) +{ + int i, retstatus = SNMP_ERR_NOERROR, + status = SNMP_ERR_NOERROR, final_status = SNMP_ERR_NOERROR; + netsnmp_handler_registration *reginfo; + + asp->reqinfo->asp = asp; + asp->reqinfo->mode = asp->mode; + + /* + * now, have the subtrees in the cache go search for their results + */ + for (i = 0; i <= asp->treecache_num; i++) { + /* + * don't call handlers w/null reginfo. + * - when is this? sub agent disconnected while request processing? + * - should this case encompass more of this subroutine? + * - does check_request_status make send if handlers weren't called? + */ + if(NULL != asp->treecache[i].subtree->reginfo) { + reginfo = asp->treecache[i].subtree->reginfo; + status = netsnmp_call_handlers(reginfo, asp->reqinfo, + asp->treecache[i].requests_begin); + } + else + status = SNMP_ERR_GENERR; + + /* + * find any errors marked in the requests. For later parts of + * SET processing, only check for new errors specific to that + * set processing directive (which must superceed the previous + * errors). + */ + switch (asp->mode) { +#ifndef NETSNMP_NO_WRITE_SUPPORT + case MODE_SET_COMMIT: + retstatus = netsnmp_check_requests_status(asp, + asp->treecache[i]. + requests_begin, + SNMP_ERR_COMMITFAILED); + break; + + case MODE_SET_UNDO: + retstatus = netsnmp_check_requests_status(asp, + asp->treecache[i]. + requests_begin, + SNMP_ERR_UNDOFAILED); + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + default: + retstatus = netsnmp_check_requests_status(asp, + asp->treecache[i]. + requests_begin, 0); + break; + } + + /* + * always take lowest varbind if possible + */ + if (retstatus != SNMP_ERR_NOERROR) { + status = retstatus; + } + + /* + * other things we know less about (no index) + */ + /* + * WWW: drop support for this? + */ + if (final_status == SNMP_ERR_NOERROR && status != SNMP_ERR_NOERROR) { + /* + * we can't break here, since some processing needs to be + * done for all requests anyway (IE, SET handling for UNDO + * needs to be called regardless of previous status + * results. + * WWW: This should be predictable though and + * breaking should be possible in some cases (eg GET, + * GETNEXT, ...) + */ + final_status = status; + } + } + + return final_status; +} + +/* + * loop through our sessions known delegated sessions and check to see + * if they've completed yet. If there are no more delegated sessions, + * check for and process any queued requests + */ +void +netsnmp_check_outstanding_agent_requests(void) +{ + netsnmp_agent_session *asp, *prev_asp = NULL, *next_asp = NULL; + + /* + * deal with delegated requests + */ + for (asp = agent_delegated_list; asp; asp = next_asp) { + next_asp = asp->next; /* save in case we clean up asp */ + if (!netsnmp_check_for_delegated(asp)) { + + /* + * we're done with this one, remove from queue + */ + if (prev_asp != NULL) + prev_asp->next = asp->next; + else + agent_delegated_list = asp->next; + asp->next = NULL; + + /* + * check request status + */ + netsnmp_check_all_requests_status(asp, 0); + + /* + * continue processing or finish up + */ + check_delayed_request(asp); + + /* + * if head was removed, don't drop it if it + * was it re-queued + */ + if ((prev_asp == NULL) && (agent_delegated_list == asp)) { + prev_asp = asp; + } + } else { + + /* + * asp is still on the queue + */ + prev_asp = asp; + } + } + + /* + * if we are processing a set and there are more delegated + * requests, keep waiting before getting to queued requests. + */ + if (netsnmp_processing_set && (NULL != agent_delegated_list)) + return; + + while (netsnmp_agent_queued_list) { + + /* + * if we are processing a set, the first item better be + * the set being (or waiting to be) processed. + */ + netsnmp_assert((!netsnmp_processing_set) || + (netsnmp_processing_set == netsnmp_agent_queued_list)); + + /* + * if the top request is a set, don't pop it + * off if there are delegated requests + */ +#ifndef NETSNMP_NO_WRITE_SUPPORT + if ((netsnmp_agent_queued_list->pdu->command == SNMP_MSG_SET) && + (agent_delegated_list)) { + + netsnmp_assert(netsnmp_processing_set == NULL); + + netsnmp_processing_set = netsnmp_agent_queued_list; + DEBUGMSGTL(("snmp_agent", "SET request remains queued while " + "delegated requests finish, asp = %8p\n", asp)); + break; + } +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + /* + * pop the first request and process it + */ + asp = netsnmp_agent_queued_list; + netsnmp_agent_queued_list = asp->next; + DEBUGMSGTL(("snmp_agent", + "processing queued request, asp = %8p\n", asp)); + + netsnmp_handle_request(asp, asp->status); + + /* + * if we hit a set, stop + */ + if (NULL != netsnmp_processing_set) + break; + } +} + +/** Decide if the requested transaction_id is still being processed + within the agent. This is used to validate whether a delayed cache + (containing possibly freed pointers) is still usable. + + returns SNMPERR_SUCCESS if it's still valid, or SNMPERR_GENERR if not. */ +int +netsnmp_check_transaction_id(int transaction_id) +{ + netsnmp_agent_session *asp; + + for (asp = agent_delegated_list; asp; asp = asp->next) { + if (asp->pdu->transid == transaction_id) + return SNMPERR_SUCCESS; + } + return SNMPERR_GENERR; +} + + +/* + * check_delayed_request(asp) + * + * Called to rexamine a set of requests and continue processing them + * once all the previous (delayed) requests have been handled one way + * or another. + */ + +int +check_delayed_request(netsnmp_agent_session *asp) +{ + int status = SNMP_ERR_NOERROR; + + DEBUGMSGTL(("snmp_agent", "processing delegated request, asp = %8p\n", + asp)); + + switch (asp->mode) { + case SNMP_MSG_GETBULK: + case SNMP_MSG_GETNEXT: + netsnmp_check_all_requests_status(asp, 0); + handle_getnext_loop(asp); + if (netsnmp_check_for_delegated(asp) && + netsnmp_check_transaction_id(asp->pdu->transid) != + SNMPERR_SUCCESS) { + /* + * add to delegated request chain + */ + if (!netsnmp_check_delegated_chain_for(asp)) { + asp->next = agent_delegated_list; + agent_delegated_list = asp; + } + } + break; + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case MODE_SET_COMMIT: + netsnmp_check_all_requests_status(asp, SNMP_ERR_COMMITFAILED); + goto settop; + + case MODE_SET_UNDO: + netsnmp_check_all_requests_status(asp, SNMP_ERR_UNDOFAILED); + goto settop; + + case MODE_SET_BEGIN: + case MODE_SET_RESERVE1: + case MODE_SET_RESERVE2: + case MODE_SET_ACTION: + case MODE_SET_FREE: + settop: + /* If we should do only one pass, this mean we */ + /* should not reenter this function */ + if ((asp->pdu->flags & UCD_MSG_FLAG_ONE_PASS_ONLY)) { + /* We should have finished the processing after the first */ + /* handle_set_loop, so just wrap up */ + break; + } + handle_set_loop(asp); + if (asp->mode != FINISHED_SUCCESS && asp->mode != FINISHED_FAILURE) { + + if (netsnmp_check_for_delegated_and_add(asp)) { + /* + * add to delegated request chain + */ + if (!asp->status) + asp->status = status; + } + + return SNMP_ERR_NOERROR; + } + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + default: + break; + } + + /* + * if we don't have anything outstanding (delegated), wrap up + */ + if (!netsnmp_check_for_delegated(asp)) + return netsnmp_wrap_up_request(asp, status); + + return 1; +} + +/** returns 1 if there are valid GETNEXT requests left. Returns 0 if not. */ +int +check_getnext_results(netsnmp_agent_session *asp) +{ + /* + * get old info + */ + netsnmp_tree_cache *old_treecache = asp->treecache; + int old_treecache_num = asp->treecache_num; + int count = 0; + int i, special = 0; + netsnmp_request_info *request; + + if (asp->mode == SNMP_MSG_GET) { + /* + * Special case for doing INCLUSIVE getNext operations in + * AgentX subagents. + */ + DEBUGMSGTL(("snmp_agent", + "asp->mode == SNMP_MSG_GET in ch_getnext\n")); + asp->mode = asp->oldmode; + special = 1; + } + + for (i = 0; i <= old_treecache_num; i++) { + for (request = old_treecache[i].requests_begin; request; + request = request->next) { + + /* + * If we have just done the special case AgentX GET, then any + * requests which were not INCLUSIVE will now have a wrong + * response, so junk them and retry from the same place (except + * that this time the handler will be called in "inexact" + * mode). + */ + + if (special) { + if (!request->inclusive) { + DEBUGMSGTL(("snmp_agent", + "request %d wasn't inclusive\n", + request->index)); + snmp_set_var_typed_value(request->requestvb, + ASN_PRIV_RETRY, NULL, 0); + } else if (request->requestvb->type == ASN_NULL || + request->requestvb->type == SNMP_NOSUCHINSTANCE || + request->requestvb->type == SNMP_NOSUCHOBJECT) { + /* + * it was inclusive, but no results. Still retry this + * search. + */ + snmp_set_var_typed_value(request->requestvb, + ASN_PRIV_RETRY, NULL, 0); + } + } + + /* + * out of range? + */ + if (snmp_oid_compare(request->requestvb->name, + request->requestvb->name_length, + request->range_end, + request->range_end_len) >= 0) { + /* + * ack, it's beyond the accepted end of range. + */ + /* + * fix it by setting the oid to the end of range oid instead + */ + DEBUGMSGTL(("check_getnext_results", + "request response %d out of range\n", + request->index)); + /* + * I'm not sure why inclusive is set unconditionally here (see + * comments for revision 1.161), but it causes a problem for + * GETBULK over an overridden variable. The bulk-to-next + * handler re-uses the same request for multiple varbinds, + * and once inclusive was set, it was never cleared. So, a + * hack. Instead of setting it to 1, set it to 2, so bulk-to + * next can clear it later. As of the time of this hack, all + * checks of this var are boolean checks (not == 1), so this + * should be safe. Cross your fingers. + */ + request->inclusive = 2; + /* + * XXX: should set this to the original OID? + */ + snmp_set_var_objid(request->requestvb, + request->range_end, + request->range_end_len); + snmp_set_var_typed_value(request->requestvb, ASN_NULL, + NULL, 0); + } + + /* + * mark any existent requests with illegal results as NULL + */ + if (request->requestvb->type == SNMP_ENDOFMIBVIEW) { + /* + * illegal response from a subagent. Change it back to NULL + * xxx-rks: err, how do we know this is a subagent? + */ + request->requestvb->type = ASN_NULL; + request->inclusive = 1; + } + + if (request->requestvb->type == ASN_NULL || + request->requestvb->type == ASN_PRIV_RETRY || + (asp->reqinfo->mode == MODE_GETBULK + && request->repeat > 0)) + count++; + } + } + return count; +} + +/** repeatedly calls getnext handlers looking for an answer till all + requests are satisified. It's expected that one pass has been made + before entering this function */ +int +handle_getnext_loop(netsnmp_agent_session *asp) +{ + int status; + netsnmp_variable_list *var_ptr; + + /* + * loop + */ + while (netsnmp_running) { + + /* + * bail for now if anything is delegated. + */ + if (netsnmp_check_for_delegated(asp)) { + return SNMP_ERR_NOERROR; + } + + /* + * check vacm against results + */ + check_acm(asp, ASN_PRIV_RETRY); + + /* + * need to keep going we're not done yet. + */ + if (!check_getnext_results(asp)) + /* + * nothing left, quit now + */ + break; + + /* + * never had a request (empty pdu), quit now + */ + /* + * XXXWWW: huh? this would be too late, no? shouldn't we + * catch this earlier? + */ + /* + * if (count == 0) + * break; + */ + + DEBUGIF("results") { + DEBUGMSGTL(("results", + "getnext results, before next pass:\n")); + for (var_ptr = asp->pdu->variables; var_ptr; + var_ptr = var_ptr->next_variable) { + DEBUGMSGTL(("results", "\t")); + DEBUGMSGVAR(("results", var_ptr)); + DEBUGMSG(("results", "\n")); + } + } + + netsnmp_reassign_requests(asp); + status = handle_var_requests(asp); + if (status != SNMP_ERR_NOERROR) { + return status; /* should never really happen */ + } + } + return SNMP_ERR_NOERROR; +} + +#ifndef NETSNMP_NO_WRITE_SUPPORT +int +handle_set(netsnmp_agent_session *asp) +{ + int status; + /* + * SETS require 3-4 passes through the var_op_list. + * The first two + * passes verify that all types, lengths, and values are valid + * and may reserve resources and the third does the set and a + * fourth executes any actions. Then the identical GET RESPONSE + * packet is returned. + * If either of the first two passes returns an error, another + * pass is made so that any reserved resources can be freed. + * If the third pass returns an error, another pass is + * made so that + * any changes can be reversed. + * If the fourth pass (or any of the error handling passes) + * return an error, we'd rather not know about it! + */ + if (!(asp->pdu->flags & UCD_MSG_FLAG_ONE_PASS_ONLY)) { + switch (asp->mode) { + case MODE_SET_BEGIN: + snmp_increment_statistic(STAT_SNMPINSETREQUESTS); + asp->rw = WRITE; /* WWW: still needed? */ + asp->mode = MODE_SET_RESERVE1; + asp->status = SNMP_ERR_NOERROR; + break; + + case MODE_SET_RESERVE1: + + if (asp->status != SNMP_ERR_NOERROR) + asp->mode = MODE_SET_FREE; + else + asp->mode = MODE_SET_RESERVE2; + break; + + case MODE_SET_RESERVE2: + if (asp->status != SNMP_ERR_NOERROR) + asp->mode = MODE_SET_FREE; + else + asp->mode = MODE_SET_ACTION; + break; + + case MODE_SET_ACTION: + if (asp->status != SNMP_ERR_NOERROR) + asp->mode = MODE_SET_UNDO; + else + asp->mode = MODE_SET_COMMIT; + break; + + case MODE_SET_COMMIT: + if (asp->status != SNMP_ERR_NOERROR) { + asp->mode = FINISHED_FAILURE; + } else { + asp->mode = FINISHED_SUCCESS; + } + break; + + case MODE_SET_UNDO: + asp->mode = FINISHED_FAILURE; + break; + + case MODE_SET_FREE: + asp->mode = FINISHED_FAILURE; + break; + } + } + + if (asp->mode != FINISHED_SUCCESS && asp->mode != FINISHED_FAILURE) { + DEBUGMSGTL(("agent_set", "doing set mode = %d (%s)\n", asp->mode, + se_find_label_in_slist("agent_mode", asp->mode))); + status = handle_var_requests(asp); + DEBUGMSGTL(("agent_set", "did set mode = %d, status = %d\n", + asp->mode, status)); + if ((status != SNMP_ERR_NOERROR && asp->status == SNMP_ERR_NOERROR) || + status == SNMP_ERR_COMMITFAILED || + status == SNMP_ERR_UNDOFAILED) { + asp->status = status; + } + } + return asp->status; +} + +int +handle_set_loop(netsnmp_agent_session *asp) +{ + while (asp->mode != FINISHED_FAILURE && asp->mode != FINISHED_SUCCESS) { + handle_set(asp); + if (netsnmp_check_for_delegated(asp)) { + return SNMP_ERR_NOERROR; + } + if (asp->pdu->flags & UCD_MSG_FLAG_ONE_PASS_ONLY) { + return asp->status; + } + } + return asp->status; +} +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + +int +netsnmp_handle_request(netsnmp_agent_session *asp, int status) +{ + /* + * if this isn't a delegated request trying to finish, + * processing of a set request should not start until all + * delegated requests have completed, and no other new requests + * should be processed until the set request completes. + */ + if ((0 == netsnmp_check_delegated_chain_for(asp)) && + (asp != netsnmp_processing_set)) { + /* + * if we are processing a set and this is not a delegated + * request, queue the request + */ + if (netsnmp_processing_set) { + netsnmp_add_queued(asp); + DEBUGMSGTL(("snmp_agent", + "request queued while processing set, " + "asp = %8p\n", asp)); + return 1; + } + + /* + * check for set request + */ +#ifndef NETSNMP_NO_WRITE_SUPPORT + if (asp->pdu->command == SNMP_MSG_SET) { + netsnmp_processing_set = asp; + + /* + * if there are delegated requests, we must wait for them + * to finish. + */ + if (agent_delegated_list) { + DEBUGMSGTL(("snmp_agent", "SET request queued while " + "delegated requests finish, asp = %8p\n", + asp)); + netsnmp_add_queued(asp); + return 1; + } + } +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + } + + /* + * process the request + */ + status = handle_pdu(asp); + + /* + * print the results in appropriate debugging mode + */ + DEBUGIF("results") { + netsnmp_variable_list *var_ptr; + DEBUGMSGTL(("results", "request results (status = %d):\n", + status)); + for (var_ptr = asp->pdu->variables; var_ptr; + var_ptr = var_ptr->next_variable) { + DEBUGMSGTL(("results", "\t")); + DEBUGMSGVAR(("results", var_ptr)); + DEBUGMSG(("results", "\n")); + } + } + + /* + * check for uncompleted requests + */ + if (netsnmp_check_for_delegated_and_add(asp)) { + /* + * add to delegated request chain + */ + asp->status = status; + } else { + /* + * if we don't have anything outstanding (delegated), wrap up + */ + return netsnmp_wrap_up_request(asp, status); + } + + return 1; +} + +int +handle_pdu(netsnmp_agent_session *asp) +{ + int status, inclusives = 0; + netsnmp_variable_list *v = NULL; + + /* + * for illegal requests, mark all nodes as ASN_NULL + */ + switch (asp->pdu->command) { + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_INTERNAL_SET_RESERVE2: + case SNMP_MSG_INTERNAL_SET_ACTION: + case SNMP_MSG_INTERNAL_SET_COMMIT: + case SNMP_MSG_INTERNAL_SET_FREE: + case SNMP_MSG_INTERNAL_SET_UNDO: + status = get_set_cache(asp); + if (status != SNMP_ERR_NOERROR) + return status; + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + case SNMP_MSG_GET: + case SNMP_MSG_GETNEXT: + case SNMP_MSG_GETBULK: + for (v = asp->pdu->variables; v != NULL; v = v->next_variable) { + if (v->type == ASN_PRIV_INCL_RANGE) { + /* + * Leave the type for now (it gets set to + * ASN_NULL in netsnmp_add_varbind_to_cache, + * called by create_subnetsnmp_tree_cache below). + * If we set it to ASN_NULL now, we wouldn't be + * able to distinguish INCLUSIVE search + * ranges. + */ + inclusives++; + } else { + snmp_set_var_typed_value(v, ASN_NULL, NULL, 0); + } + } + /* + * fall through + */ + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_INTERNAL_SET_BEGIN: + case SNMP_MSG_INTERNAL_SET_RESERVE1: +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + default: + asp->vbcount = count_varbinds(asp->pdu->variables); + if (asp->vbcount) /* efence doesn't like 0 size allocs */ + asp->requests = (netsnmp_request_info *) + calloc(asp->vbcount, sizeof(netsnmp_request_info)); + /* + * collect varbinds + */ + status = netsnmp_create_subtree_cache(asp); + if (status != SNMP_ERR_NOERROR) + return status; + } + + asp->mode = asp->pdu->command; + switch (asp->mode) { + case SNMP_MSG_GET: + /* + * increment the message type counter + */ + snmp_increment_statistic(STAT_SNMPINGETREQUESTS); + + /* + * check vacm ahead of time + */ + check_acm(asp, SNMP_NOSUCHOBJECT); + + /* + * get the results + */ + status = handle_var_requests(asp); + + /* + * Deal with unhandled results -> noSuchInstance (rather + * than noSuchObject -- in that case, the type will + * already have been set to noSuchObject when we realised + * we couldn't find an appropriate tree). + */ + if (status == SNMP_ERR_NOERROR) + snmp_replace_var_types(asp->pdu->variables, ASN_NULL, + SNMP_NOSUCHINSTANCE); + break; + + case SNMP_MSG_GETNEXT: + snmp_increment_statistic(STAT_SNMPINGETNEXTS); + /* + * fall through + */ + + case SNMP_MSG_GETBULK: /* note: there is no getbulk stat */ + /* + * loop through our mib tree till we find an + * appropriate response to return to the caller. + */ + + if (inclusives) { + /* + * This is a special case for AgentX INCLUSIVE getNext + * requests where a result lexi-equal to the request is okay + * but if such a result does not exist, we still want the + * lexi-next one. So basically we do a GET first, and if any + * of the INCLUSIVE requests are satisfied, we use that + * value. Then, unsatisfied INCLUSIVE requests, and + * non-INCLUSIVE requests get done as normal. + */ + + DEBUGMSGTL(("snmp_agent", "inclusive range(s) in getNext\n")); + asp->oldmode = asp->mode; + asp->mode = SNMP_MSG_GET; + } + + /* + * first pass + */ + status = handle_var_requests(asp); + if (status != SNMP_ERR_NOERROR) { + if (!inclusives) + return status; /* should never really happen */ + else + asp->status = SNMP_ERR_NOERROR; + } + + /* + * loop through our mib tree till we find an + * appropriate response to return to the caller. + */ + + status = handle_getnext_loop(asp); + break; + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: +#ifdef NETSNMP_DISABLE_SET_SUPPORT + return SNMP_ERR_NOTWRITABLE; +#else + /* + * check access permissions first + */ + if (check_acm(asp, SNMP_NOSUCHOBJECT)) + return SNMP_ERR_NOTWRITABLE; + + asp->mode = MODE_SET_BEGIN; + status = handle_set_loop(asp); +#endif + break; + + case SNMP_MSG_INTERNAL_SET_BEGIN: + case SNMP_MSG_INTERNAL_SET_RESERVE1: + case SNMP_MSG_INTERNAL_SET_RESERVE2: + case SNMP_MSG_INTERNAL_SET_ACTION: + case SNMP_MSG_INTERNAL_SET_COMMIT: + case SNMP_MSG_INTERNAL_SET_FREE: + case SNMP_MSG_INTERNAL_SET_UNDO: + asp->pdu->flags |= UCD_MSG_FLAG_ONE_PASS_ONLY; + status = handle_set_loop(asp); + /* + * asp related cache is saved in cleanup + */ + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + case SNMP_MSG_RESPONSE: + snmp_increment_statistic(STAT_SNMPINGETRESPONSES); + return SNMP_ERR_NOERROR; + + case SNMP_MSG_TRAP: + case SNMP_MSG_TRAP2: + snmp_increment_statistic(STAT_SNMPINTRAPS); + return SNMP_ERR_NOERROR; + + default: + /* + * WWW: are reports counted somewhere ? + */ + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + return SNMPERR_GENERR; /* shouldn't get here */ + /* + * WWW + */ + } + return status; +} + +/** set error for a request + * \internal external interface: netsnmp_request_set_error + */ +NETSNMP_STATIC_INLINE int +_request_set_error(netsnmp_request_info *request, int mode, int error_value) +{ + if (!request) + return SNMPERR_NO_VARS; + + request->processed = 1; + request->delegated = REQUEST_IS_NOT_DELEGATED; + + switch (error_value) { + case SNMP_NOSUCHOBJECT: + case SNMP_NOSUCHINSTANCE: + case SNMP_ENDOFMIBVIEW: + /* + * these are exceptions that should be put in the varbind + * in the case of a GET but should be translated for a SET + * into a real error status code and put in the request + */ + switch (mode) { + case MODE_GET: + case MODE_GETNEXT: + case MODE_GETBULK: + request->requestvb->type = error_value; + break; + + /* + * These are technically illegal to set by the + * client APIs for these modes. But accepting + * them here allows the 'sparse_table' helper to + * provide some common table handling processing + * + snmp_log(LOG_ERR, "Illegal error_value %d for mode %d ignored\n", + error_value, mode); + return SNMPERR_VALUE; + */ + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_INTERNAL_SET_RESERVE1: + request->status = SNMP_ERR_NOCREATION; + break; +#endif /* NETSNMP_NO_WRITE_SUPPORT */ + + default: + request->status = SNMP_ERR_NOSUCHNAME; /* WWW: correct? */ + break; + } + break; /* never get here */ + + default: + if (error_value < 0) { + /* + * illegal local error code. translate to generr + */ + /* + * WWW: full translation map? + */ + snmp_log(LOG_ERR, "Illegal error_value %d translated to %d\n", + error_value, SNMP_ERR_GENERR); + request->status = SNMP_ERR_GENERR; + } else { + /* + * WWW: translations and mode checking? + */ + request->status = error_value; + } + break; + } + return SNMPERR_SUCCESS; +} + +/** set error for a request + * @param request request which has error + * @param error_value error value for request + */ +int +netsnmp_request_set_error(netsnmp_request_info *request, int error_value) +{ + if (!request || !request->agent_req_info) + return SNMPERR_NO_VARS; + + return _request_set_error(request, request->agent_req_info->mode, + error_value); +} + +#ifndef NETSNMP_FEATURE_REMOVE_REQUEST_SET_ERROR_IDX +/** set error for a request within a request list + * @param request head of the request list + * @param error_value error value for request + * @param idx index of the request which has the error + */ +int +netsnmp_request_set_error_idx(netsnmp_request_info *request, + int error_value, int idx) +{ + int i; + netsnmp_request_info *req = request; + + if (!request || !request->agent_req_info) + return SNMPERR_NO_VARS; + + /* + * Skip to the indicated varbind + */ + for ( i=2; i<idx; i++) { + req = req->next; + if (!req) + return SNMPERR_NO_VARS; + } + + return _request_set_error(req, request->agent_req_info->mode, + error_value); +} +#endif /* NETSNMP_FEATURE_REMOVE_REQUEST_SET_ERROR_IDX */ + +/** set error for all requests + * @param requests request list + * @param error error value for requests + * @return SNMPERR_SUCCESS, or an error code + */ +NETSNMP_INLINE int +netsnmp_request_set_error_all( netsnmp_request_info *requests, int error) +{ + int mode, rc, result = SNMPERR_SUCCESS; + + if((NULL == requests) || (NULL == requests->agent_req_info)) + return SNMPERR_NO_VARS; + + mode = requests->agent_req_info->mode; /* every req has same mode */ + + for(; requests ; requests = requests->next) { + + /** paranoid sanity checks */ + netsnmp_assert(NULL != requests->agent_req_info); + netsnmp_assert(mode == requests->agent_req_info->mode); + + /* + * set error for this request. Log any errors, save the last + * to return to the user. + */ + if((rc = _request_set_error(requests, mode, error))) { + snmp_log(LOG_WARNING,"got %d while setting request error\n", rc); + result = rc; + } + } + return result; +} + +/** + * Return the difference between pm and the agent start time in hundredths of + * a second. + * \deprecated Don't use in new code. + * + * @param[in] pm An absolute time as e.g. reported by gettimeofday(). + */ +u_long +netsnmp_marker_uptime(marker_t pm) +{ + u_long res; + const_marker_t start = netsnmp_get_agent_starttime(); + + res = uatime_hdiff(start, pm); + return res; +} + +/** + * Return the difference between tv and the agent start time in hundredths of + * a second. + * + * \deprecated Use netsnmp_get_agent_uptime() instead. + * + * @param[in] tv An absolute time as e.g. reported by gettimeofday(). + */ +u_long +netsnmp_timeval_uptime(struct timeval * tv) +{ + return netsnmp_marker_uptime((marker_t) tv); +} + + +struct timeval starttime; +static struct timeval starttimeM; + +/** + * Return a pointer to the variable in which the Net-SNMP start time has + * been stored. + * + * @note Use netsnmp_get_agent_runtime() instead of this function if you need + * to know how much time elapsed since netsnmp_set_agent_starttime() has been + * called. + */ +const_marker_t +netsnmp_get_agent_starttime(void) +{ + return &starttime; +} + +/** + * Report the time that elapsed since the agent start time in hundredths of a + * second. + * + * @see See also netsnmp_set_agent_starttime(). + */ +uint64_t +netsnmp_get_agent_runtime(void) +{ + struct timeval now, delta; + + netsnmp_get_monotonic_clock(&now); + NETSNMP_TIMERSUB(&now, &starttimeM, &delta); + return delta.tv_sec * (uint64_t)100 + delta.tv_usec / 10000; +} + +/** + * Set the time at which Net-SNMP started either to the current time + * (if s == NULL) or to *s (if s is not NULL). + * + * @see See also netsnmp_set_agent_uptime(). + */ +void +netsnmp_set_agent_starttime(marker_t s) +{ + if (s) { + struct timeval nowA, nowM; + + starttime = *(struct timeval*)s; + gettimeofday(&nowA, NULL); + netsnmp_get_monotonic_clock(&nowM); + NETSNMP_TIMERSUB(&starttime, &nowA, &starttimeM); + NETSNMP_TIMERADD(&starttimeM, &nowM, &starttimeM); + } else { + gettimeofday(&starttime, NULL); + netsnmp_get_monotonic_clock(&starttimeM); + } +} + + +/** + * Return the current value of 'sysUpTime' + */ +u_long +netsnmp_get_agent_uptime(void) +{ + struct timeval now, delta; + + netsnmp_get_monotonic_clock(&now); + NETSNMP_TIMERSUB(&now, &starttimeM, &delta); + return delta.tv_sec * 100UL + delta.tv_usec / 10000; +} + +#ifndef NETSNMP_FEATURE_REMOVE_SET_AGENT_UPTIME +/** + * Set the start time from which 'sysUpTime' is computed. + * + * @param[in] hsec New sysUpTime in hundredths of a second. + * + * @see See also netsnmp_set_agent_starttime(). + */ +void +netsnmp_set_agent_uptime(u_long hsec) +{ + struct timeval nowA, nowM; + struct timeval new_uptime; + + gettimeofday(&nowA, NULL); + netsnmp_get_monotonic_clock(&nowM); + new_uptime.tv_sec = hsec / 100; + new_uptime.tv_usec = (uint32_t)(hsec - new_uptime.tv_sec * 100) * 10000L; + NETSNMP_TIMERSUB(&nowA, &new_uptime, &starttime); + NETSNMP_TIMERSUB(&nowM, &new_uptime, &starttimeM); +} +#endif /* NETSNMP_FEATURE_REMOVE_SET_AGENT_UPTIME */ + + +/************************************************************************* + * + * deprecated functions + * + */ + +/** set error for a request + * \deprecated, use netsnmp_request_set_error instead + * @param reqinfo agent_request_info pointer for request + * @param request request_info pointer + * @param error_value error value for requests + * @return error_value + */ +int +netsnmp_set_request_error(netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request, int error_value) +{ + if (!request || !reqinfo) + return error_value; + + _request_set_error(request, reqinfo->mode, error_value); + + return error_value; +} + +/** set error for a request + * \deprecated, use netsnmp_request_set_error instead + * @param mode Net-SNMP agent processing mode + * @param request request_info pointer + * @param error_value error value for requests + * @return error_value + */ +int +netsnmp_set_mode_request_error(int mode, netsnmp_request_info *request, + int error_value) +{ + _request_set_error(request, mode, error_value); + + return error_value; +} + +/** set error for all request + * \deprecated use netsnmp_request_set_error_all + * @param reqinfo agent_request_info pointer for requests + * @param requests request list + * @param error_value error value for requests + * @return error_value + */ +#ifndef NETSNMP_FEATURE_REMOVE_SET_ALL_REQUESTS_ERROR +int +netsnmp_set_all_requests_error(netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests, + int error_value) +{ + netsnmp_request_set_error_all(requests, error_value); + return error_value; +} +#endif /* NETSNMP_FEATURE_REMOVE_SET_ALL_REQUESTS_ERROR */ +/** @} */ |