From 9b72841faf869110fef88e2e012770e4789b6838 Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 10 2020 11:07:37 +0000 Subject: Apply patch net-snmp-5.8-agent-of-death.patch patch_name: net-snmp-5.8-agent-of-death.patch present_in_specfile: true --- diff --git a/agent/agent_trap.c b/agent/agent_trap.c index d49c2dc..667b4ed 100644 --- a/agent/agent_trap.c +++ b/agent/agent_trap.c @@ -174,6 +174,11 @@ _trap_version_incr(int version) case SNMP_VERSION_3: ++_v2_sessions; break; +#ifdef USING_AGENTX_PROTOCOL_MODULE + case AGENTX_VERSION_1: + /* agentx registers in sinks, no need to count */ + break; +#endif default: snmp_log(LOG_ERR, "unknown snmp version %d\n", version); } @@ -201,6 +206,11 @@ _trap_version_decr(int version) _v2_sessions = 0; } break; +#ifdef USING_AGENTX_PROTOCOL_MODULE + case AGENTX_VERSION_1: + /* agentx registers in sinks, no need to count */ + break; +#endif default: snmp_log(LOG_ERR, "unknown snmp version %d\n", version); } diff --git a/agent/agent_trap.c.agent-of-death b/agent/agent_trap.c.agent-of-death new file mode 100644 index 0000000..d49c2dc --- /dev/null +++ b/agent/agent_trap.c.agent-of-death @@ -0,0 +1,1866 @@ +/* + * agent_trap.c + */ +/* Portions of this file are subject to the following copyright(s). See + * the Net-SNMP's COPYING file for more details and other copyrights + * that may apply: + */ +/* + * Portions of this file are copyrighted by: + * Copyright � 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms specified in the COPYING file + * distributed with the Net-SNMP package. + * + * Portions of this file are copyrighted by: + * Copyright (c) 2016 VMware, Inc. All rights reserved. + * Use is subject to license terms specified in the COPYING file + * distributed with the Net-SNMP package. + */ +/** @defgroup agent_trap Trap generation routines for mib modules to use + * @ingroup agent + * + * @{ + */ + +#include +#include + +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_NETDB_H +#include +#endif +#if HAVE_STDLIB_H +#include +#endif +#if HAVE_STRING_H +#include +#else +#include +#endif +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +#if HAVE_SYS_SOCKET_H +#include +#endif +#if HAVE_NETINET_IN_H +#include +#endif +#include + +#include +#include +#include +#include +#include +#include "agent_global_vars.h" + +#include +#include + +#ifdef USING_AGENTX_PROTOCOL_MODULE +#include "agentx/protocol.h" +#endif + +#ifdef USING_NOTIFICATION_SNMPNOTIFYTABLE_DATA_MODULE +#include "mibgroup/notification/snmpNotifyTable_data.h" +#endif + +netsnmp_feature_child_of(agent_trap_all, libnetsnmpagent) + +netsnmp_feature_child_of(trap_vars_with_context, agent_trap_all) +netsnmp_feature_child_of(remove_trap_session, agent_trap_all) + +netsnmp_feature_child_of(send_v3trap,netsnmp_unused) +netsnmp_feature_child_of(send_trap_pdu,netsnmp_unused) + +struct trap_sink { + netsnmp_session *sesp; + struct trap_sink *next; + int pdutype; + int version; +}; + +struct trap_sink *sinks = NULL; + +#ifndef NETSNMP_DISABLE_SNMPV1 +static int _v1_sessions = 0; +#endif /* NETSNMP_DISABLE_SNMPV1 */ +static int _v2_sessions = 0; + +const oid objid_enterprisetrap[] = { NETSNMP_NOTIFICATION_MIB }; +const oid trap_version_id[] = { NETSNMP_SYSTEM_MIB }; +const int enterprisetrap_len = OID_LENGTH(objid_enterprisetrap); +const int trap_version_id_len = OID_LENGTH(trap_version_id); + +#define SNMPV2_TRAPS_PREFIX SNMP_OID_SNMPMODULES,1,1,5 +const oid trap_prefix[] = { SNMPV2_TRAPS_PREFIX }; +const oid cold_start_oid[] = { SNMPV2_TRAPS_PREFIX, 1 }; /* SNMPv2-MIB */ + +#define SNMPV2_TRAP_OBJS_PREFIX SNMP_OID_SNMPMODULES,1,1,4 +const oid snmptrap_oid[] = { SNMPV2_TRAP_OBJS_PREFIX, 1, 0 }; +const oid snmptrapenterprise_oid[] = { SNMPV2_TRAP_OBJS_PREFIX, 3, 0 }; +const oid sysuptime_oid[] = { SNMP_OID_MIB2, 1, 3, 0 }; +const size_t snmptrap_oid_len = OID_LENGTH(snmptrap_oid); +const size_t snmptrapenterprise_oid_len = OID_LENGTH(snmptrapenterprise_oid); +const size_t sysuptime_oid_len = OID_LENGTH(sysuptime_oid); + +#define SNMPV2_COMM_OBJS_PREFIX SNMP_OID_SNMPMODULES,18,1 +const oid agentaddr_oid[] = { SNMPV2_COMM_OBJS_PREFIX, 3, 0 }; +const size_t agentaddr_oid_len = OID_LENGTH(agentaddr_oid); +const oid community_oid[] = { SNMPV2_COMM_OBJS_PREFIX, 4, 0 }; +const size_t community_oid_len = OID_LENGTH(community_oid); +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +char *snmp_trapcommunity = NULL; +#endif + + +#define SNMP_AUTHENTICATED_TRAPS_ENABLED 1 +#define SNMP_AUTHENTICATED_TRAPS_DISABLED 2 + +long snmp_enableauthentraps = SNMP_AUTHENTICATED_TRAPS_DISABLED; +int snmp_enableauthentrapsset = 0; + +/* + * Prototypes + */ + /* + * static void free_trap_session (struct trap_sink *sp); + * static void send_v1_trap (netsnmp_session *, int, int); + * static void send_v2_trap (netsnmp_session *, int, int, int); + */ + + + /******************* + * + * Trap session handling + * + *******************/ + +void +init_traps(void) +{ +} + +static void +free_trap_session(struct trap_sink *sp) +{ + DEBUGMSGTL(("trap", "freeing callback trap session (%p, %p)\n", sp, sp->sesp)); + snmp_close(sp->sesp); + free(sp); +} + +static void +_trap_version_incr(int version) +{ + switch (version) { +#ifndef NETSNMP_DISABLE_SNMPV1 + case SNMP_VERSION_1: + ++_v1_sessions; + break; +#endif +#ifndef NETSNMP_DISABLE_SNMPV2C + case SNMP_VERSION_2c: +#endif + case SNMP_VERSION_3: + ++_v2_sessions; + break; + default: + snmp_log(LOG_ERR, "unknown snmp version %d\n", version); + } + return; +} + +static void +_trap_version_decr(int version) +{ + switch (version) { +#ifndef NETSNMP_DISABLE_SNMPV1 + case SNMP_VERSION_1: + if (--_v1_sessions < 0) { + snmp_log(LOG_ERR,"v1 session count < 0! fixed.\n"); + _v1_sessions = 0; + } + break; +#endif +#ifndef NETSNMP_DISABLE_SNMPV2C + case SNMP_VERSION_2c: +#endif + case SNMP_VERSION_3: + if (--_v2_sessions < 0) { + snmp_log(LOG_ERR,"v2 session count < 0! fixed.\n"); + _v2_sessions = 0; + } + break; + default: + snmp_log(LOG_ERR, "unknown snmp version %d\n", version); + } + return; +} + + +#ifndef NETSNMP_NO_TRAP_STATS +static void +_dump_trap_stats(netsnmp_session *sess) +{ + if (NULL == sess || NULL == sess->trap_stats) + return; + + DEBUGIF("stats:notif") { + DEBUGMSGT_NC(("stats:notif", "%s inform stats\n", sess->paramName)); + DEBUGMSGT_NC(("stats:notif", " %ld sends, last @ %ld\n", + sess->trap_stats->sent_count, + sess->trap_stats->sent_last_sent)); + DEBUGMSGT_NC(("stats:notif", " %ld acks, last @ %ld\n", + sess->trap_stats->ack_count, + sess->trap_stats->ack_last_rcvd)); + DEBUGMSGT_NC(("stats:notif", " %ld failed sends, last @ %ld\n", + sess->trap_stats->sent_fail_count, + sess->trap_stats->sent_last_fail)); + DEBUGMSGT_NC(("stats:notif", " %ld timeouts, last @ %ld\n", + sess->trap_stats->timeouts, + sess->trap_stats->sent_last_timeout)); + DEBUGMSGT_NC(("stats:notif", " %ld v3 errs, last @ %ld\n", + sess->trap_stats->sec_err_count, + sess->trap_stats->sec_err_last)); + } +} +#endif /* NETSNMP_NO_TRAP_STATS */ + +int +netsnmp_add_notification_session(netsnmp_session * ss, int pdutype, + int confirm, int version, const char *name, + const char *tag, const char* profile) +{ + if (NETSNMP_RUNTIME_PROTOCOL_SKIP(version)) { + DEBUGMSGTL(("trap", "skipping trap sink (version 0x%02x disabled)\n", + version)); + return 0; + } + if (snmp_callback_available(SNMP_CALLBACK_APPLICATION, + SNMPD_CALLBACK_REGISTER_NOTIFICATIONS) == + SNMPERR_SUCCESS) { + /* + * something else wants to handle notification registrations + */ + struct agent_add_trap_args args; + DEBUGMSGTL(("trap", "adding callback trap sink (%p)\n", ss)); + args.ss = ss; + args.confirm = confirm; + args.nameData = name; + args.nameLen = (NULL == name) ? 0 : strlen(name); + args.tagData = tag; + args.tagLen = (NULL == tag) ? 0 : strlen(tag); + args.profileData = profile; + args.profileLen = (NULL == profile) ? 0: strlen(profile); + snmp_call_callbacks(SNMP_CALLBACK_APPLICATION, + SNMPD_CALLBACK_REGISTER_NOTIFICATIONS, + (void *) &args); + if (args.rc != SNMPERR_SUCCESS) + return 0; + } else { + /* + * no other support exists, handle it ourselves. + */ + struct trap_sink *new_sink; + + DEBUGMSGTL(("trap", "adding internal trap sink\n")); + new_sink = (struct trap_sink *) malloc(sizeof(*new_sink)); + if (new_sink == NULL) + return 0; + + new_sink->sesp = ss; + new_sink->pdutype = pdutype; + new_sink->version = version; + new_sink->next = sinks; + sinks = new_sink; + } + + _trap_version_incr(version); + + return 1; +} + +/* + * xxx needs update to support embedded NUL. + * xxx should probably also be using and unregister callback, similar to + * how registaration is done. + */ +void +netsnmp_unregister_notification(const char *name, u_char len) +{ + if (snmp_callback_available(SNMP_CALLBACK_APPLICATION, + SNMPD_CALLBACK_UNREGISTER_NOTIFICATIONS) == + SNMPERR_SUCCESS) { + /* + * something else wants to handle notification registrations + */ + struct agent_add_trap_args args; + DEBUGMSGTL(("trap", "removing callback trap sink\n")); + args.nameData = name; + args.nameLen = len; + snmp_call_callbacks(SNMP_CALLBACK_APPLICATION, + SNMPD_CALLBACK_UNREGISTER_NOTIFICATIONS, + (void *) &args); + } else + NETSNMP_LOGONCE((LOG_WARNING, + "netsnmp_unregister_notification not supported\n")); +} + +int +add_trap_session(netsnmp_session * ss, int pdutype, int confirm, + int version) +{ + return netsnmp_add_notification_session(ss, pdutype, confirm, version, + NULL, NULL, NULL); +} + +#ifndef NETSNMP_FEATURE_REMOVE_REMOVE_TRAP_SESSION +int +remove_trap_session(netsnmp_session * ss) +{ + struct trap_sink *sp = sinks, *prev = NULL; + + DEBUGMSGTL(("trap", "removing trap sessions\n")); + while (sp) { + if (sp->sesp == ss) { + if (prev) { + prev->next = sp->next; + } else { + sinks = sp->next; + } + _trap_version_decr(ss->version); + /* + * I don't believe you *really* want to close the session here; + * it may still be in use for other purposes. In particular this + * is awkward for AgentX, since we want to call this function + * from the session's callback. Let's just free the trapsink + * data structure. [jbpn] + */ + /* + * free_trap_session(sp); + */ + DEBUGMSGTL(("trap", "removing trap session (%p, %p)\n", sp, sp->sesp)); + free(sp); + return 1; + } + prev = sp; + sp = sp->next; + } + return 0; +} +#endif /* NETSNMP_FEATURE_REMOVE_REMOVE_TRAP_SESSION */ + +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +netsnmp_session * +netsnmp_create_v1v2_notification_session(const char *sink, const char* sinkport, + const char *com, const char *src, + int version, int pdutype, + const char *name, const char *tag, + const char* profile) +{ + netsnmp_transport *t; + netsnmp_session session, *sesp; + netsnmp_tdomain_spec tspec; + char tmp[SPRINT_MAX_LEN]; + int rc; + const char *client_addr = NULL; + + if (NETSNMP_RUNTIME_PROTOCOL_SKIP(version)) { + config_perror("SNMP version disabled"); + DEBUGMSGTL(("trap", "skipping trap sink (version 0x%02x disabled)\n", + version)); + return NULL; + } + + snmp_sess_init(&session); + session.version = version; + if (com) { + session.community = (u_char *) NETSNMP_REMOVE_CONST(char *, com); + session.community_len = strlen(com); + } + + /* + * for informs, set retries to default + */ + if (SNMP_MSG_INFORM == pdutype) { + session.timeout = SNMP_DEFAULT_TIMEOUT; + session.retries = SNMP_DEFAULT_RETRIES; + } + + memset(&tspec, 0, sizeof(netsnmp_tdomain_spec)); + + /* + * use specified soure or client addr, if available. If no, and + * if the sink is localhost, bind to localhost, to reduce open ports. + */ + if (NULL != src) + tspec.source = src; + else { + client_addr = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CLIENT_ADDR); + if ((NULL == client_addr) && + ((0 == strcmp("localhost",sink)) || + (0 == strcmp("127.0.0.1",sink)))) + client_addr = "localhost"; + tspec.source = client_addr; + } + session.localname = NETSNMP_REMOVE_CONST(char *,tspec.source); + + tspec.application = "snmptrap"; + if (NULL == sinkport) + tspec.target = sink; + else { + snprintf(tmp, sizeof(tmp)-1,"%s:%s", sink, sinkport); + tspec.target = tmp; + } + tspec.default_domain = NULL; + tspec.default_target = sinkport; + t = netsnmp_tdomain_transport_tspec(&tspec); + if ((NULL == t) || + ((sesp = snmp_add(&session, t, NULL, NULL)) == NULL)) { + /** diagnose snmp_open errors with the input netsnmp_session pointer */ + snmp_sess_perror("snmpd: netsnmp_create_notification_session", + &session); + /* transport freed by snmp_add */ + return NULL; + } + + rc = netsnmp_add_notification_session(sesp, pdutype, + (pdutype == SNMP_MSG_INFORM), + version, name, tag, profile); + if (0 == rc) + return NULL; + + return sesp; +} + +int +create_trap_session_with_src(const char *sink, const char* sinkport, + const char *com, const char *src, int version, + int pdutype) +{ + void *ss = netsnmp_create_v1v2_notification_session(sink, sinkport, com, + src, version, pdutype, + NULL, NULL, NULL); + return (ss != NULL); +} + +int +create_trap_session2(const char *sink, const char* sinkport, + char *com, int version, int pdutype) +{ + return create_trap_session_with_src(sink, sinkport, com, NULL, version, + pdutype); +} + +int +create_trap_session(char *sink, u_short sinkport, + char *com, int version, int pdutype) +{ + void *ss; + char buf[sizeof(sinkport) * 3 + 2]; + if (sinkport != 0) { + sprintf(buf, ":%hu", sinkport); + snmp_log(LOG_NOTICE, + "Using a separate port number is deprecated, please correct " + "the sink specification instead"); + } + ss = netsnmp_create_v1v2_notification_session(sink, sinkport ? buf : NULL, + com, NULL, version, pdutype, + NULL, NULL, NULL); + return (ss != NULL); +} + +#endif /* support for community based SNMP */ + +void +snmpd_free_trapsinks(void) +{ + struct trap_sink *sp = sinks; + DEBUGMSGTL(("trap", "freeing trap sessions\n")); + while (sp) { + sinks = sinks->next; + _trap_version_decr(sp->version); + free_trap_session(sp); + sp = sinks; + } +} + + /******************* + * + * Trap handling + * + *******************/ + + +netsnmp_pdu* +convert_v2pdu_to_v1( netsnmp_pdu* template_v2pdu ) +{ + netsnmp_pdu *template_v1pdu; + netsnmp_variable_list *first_vb, *vblist; + netsnmp_variable_list *var; + + /* + * Make a copy of the v2 Trap PDU + * before starting to convert this + * into a v1 Trap PDU. + */ + template_v1pdu = snmp_clone_pdu( template_v2pdu); + if (!template_v1pdu) { + snmp_log(LOG_WARNING, + "send_trap: failed to copy v1 template PDU\n"); + return NULL; + } + template_v1pdu->command = SNMP_MSG_TRAP; + first_vb = template_v1pdu->variables; + vblist = template_v1pdu->variables; + + /* + * The first varbind should be the system uptime. + */ + if (!vblist || + snmp_oid_compare(vblist->name, vblist->name_length, + sysuptime_oid, sysuptime_oid_len)) { + snmp_log(LOG_WARNING, + "send_trap: no v2 sysUptime varbind to set from\n"); + snmp_free_pdu(template_v1pdu); + return NULL; + } + template_v1pdu->time = *vblist->val.integer; + vblist = vblist->next_variable; + + /* + * The second varbind should be the snmpTrapOID. + */ + if (!vblist || + snmp_oid_compare(vblist->name, vblist->name_length, + snmptrap_oid, snmptrap_oid_len)) { + snmp_log(LOG_WARNING, + "send_trap: no v2 trapOID varbind to set from\n"); + snmp_free_pdu(template_v1pdu); + return NULL; + } + + /* + * Check the v2 varbind list for any varbinds + * that are not valid in an SNMPv1 trap. + * This basically means Counter64 values. + * + * RFC 2089 said to omit such varbinds from the list. + * RFC 2576/3584 say to drop the trap completely. + */ + for (var = vblist->next_variable; var; var = var->next_variable) { + if ( var->type == ASN_COUNTER64 ) { + snmp_log(LOG_WARNING, + "send_trap: v1 traps can't carry Counter64 varbinds\n"); + snmp_free_pdu(template_v1pdu); + return NULL; + } + } + + /* + * Set the generic & specific trap types, + * and the enterprise field from the v2 varbind list. + * If there's an agentIPAddress varbind, set the agent_addr too + */ + if (!snmp_oid_compare(vblist->val.objid, OID_LENGTH(trap_prefix), + trap_prefix, OID_LENGTH(trap_prefix))) { + /* + * For 'standard' traps, extract the generic trap type + * from the snmpTrapOID value, and take the enterprise + * value from the 'snmpEnterprise' varbind. + */ + template_v1pdu->trap_type = + vblist->val.objid[OID_LENGTH(trap_prefix)] - 1; + template_v1pdu->specific_type = 0; + + var = find_varbind_in_list( vblist, + snmptrapenterprise_oid, + snmptrapenterprise_oid_len); + if (var) { + template_v1pdu->enterprise_length = var->val_len/sizeof(oid); + template_v1pdu->enterprise = + snmp_duplicate_objid(var->val.objid, + template_v1pdu->enterprise_length); + } else { + template_v1pdu->enterprise = NULL; + template_v1pdu->enterprise_length = 0; /* XXX ??? */ + } + } else { + /* + * For enterprise-specific traps, split the snmpTrapOID value + * into enterprise and specific trap + */ + size_t len = vblist->val_len / sizeof(oid); + if ( len <= 2 ) { + snmp_log(LOG_WARNING, + "send_trap: v2 trapOID too short (%d)\n", (int)len); + snmp_free_pdu(template_v1pdu); + return NULL; + } + template_v1pdu->trap_type = SNMP_TRAP_ENTERPRISESPECIFIC; + template_v1pdu->specific_type = vblist->val.objid[len - 1]; + len--; + if (vblist->val.objid[len-1] == 0) + len--; + SNMP_FREE(template_v1pdu->enterprise); + template_v1pdu->enterprise = + snmp_duplicate_objid(vblist->val.objid, len); + template_v1pdu->enterprise_length = len; + } + var = find_varbind_in_list( vblist, agentaddr_oid, + agentaddr_oid_len); + if (var) { + memcpy(template_v1pdu->agent_addr, + var->val.string, 4); + } + + /* + * The remainder of the v2 varbind list is kept + * as the v2 varbind list. Update the PDU and + * free the two redundant varbinds. + */ + template_v1pdu->variables = vblist->next_variable; + vblist->next_variable = NULL; + snmp_free_varbind( first_vb ); + + return template_v1pdu; +} + +/* + * Set t_oid from the PDU enterprise & specific trap fields. + */ +int +netsnmp_build_trap_oid(netsnmp_pdu *pdu, oid *t_oid, size_t *t_oid_len) +{ + if (NULL == pdu || NULL == t_oid || NULL == t_oid_len) + return SNMPERR_GENERR; + if (pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC) { + if (*t_oid_len < (pdu->enterprise_length + 2)) + return SNMPERR_LONG_OID; + memcpy(t_oid, pdu->enterprise, pdu->enterprise_length*sizeof(oid)); + *t_oid_len = pdu->enterprise_length; + t_oid[(*t_oid_len)++] = 0; + t_oid[(*t_oid_len)++] = pdu->specific_type; + } else { + /** use cold_start_oid as template */ + if (*t_oid_len < OID_LENGTH(cold_start_oid)) + return SNMPERR_LONG_OID; + memcpy(t_oid, cold_start_oid, sizeof(cold_start_oid)); + t_oid[9] = pdu->trap_type + 1; /* set actual trap type */ + *t_oid_len = OID_LENGTH(cold_start_oid); + } + return SNMPERR_SUCCESS; +} + +netsnmp_pdu* +convert_v1pdu_to_v2( netsnmp_pdu* template_v1pdu ) +{ + netsnmp_pdu *template_v2pdu; + netsnmp_variable_list *var; + oid enterprise[MAX_OID_LEN]; + size_t enterprise_len; + + /* + * Make a copy of the v1 Trap PDU + * before starting to convert this + * into a v2 Trap PDU. + */ + template_v2pdu = snmp_clone_pdu( template_v1pdu); + if (!template_v2pdu) { + snmp_log(LOG_WARNING, + "send_trap: failed to copy v2 template PDU\n"); + return NULL; + } + template_v2pdu->command = SNMP_MSG_TRAP2; + + /* + * Insert an snmpTrapOID varbind before the original v1 varbind list + * either using one of the standard defined trap OIDs, + * or constructing this from the PDU enterprise & specific trap fields + */ + var = NULL; + enterprise_len = OID_LENGTH(enterprise); + if ((netsnmp_build_trap_oid(template_v1pdu, enterprise, &enterprise_len) + != SNMPERR_SUCCESS) || + !snmp_varlist_add_variable( &var, + snmptrap_oid, snmptrap_oid_len, + ASN_OBJECT_ID, + (u_char*)enterprise, enterprise_len*sizeof(oid))) { + snmp_log(LOG_WARNING, + "send_trap: failed to insert copied snmpTrapOID varbind\n"); + snmp_free_pdu(template_v2pdu); + return NULL; + } + var->next_variable = template_v2pdu->variables; + template_v2pdu->variables = var; + + /* + * Insert a sysUptime varbind at the head of the v2 varbind list + */ + var = NULL; + if (!snmp_varlist_add_variable( &var, + sysuptime_oid, sysuptime_oid_len, + ASN_TIMETICKS, + (u_char*)&(template_v1pdu->time), + sizeof(template_v1pdu->time))) { + snmp_log(LOG_WARNING, + "send_trap: failed to insert copied sysUptime varbind\n"); + snmp_free_pdu(template_v2pdu); + return NULL; + } + var->next_variable = template_v2pdu->variables; + template_v2pdu->variables = var; + + /* + * Append the other three conversion varbinds, + * (snmpTrapAgentAddr, snmpTrapCommunity & snmpTrapEnterprise) + * if they're not already present. + * But don't bomb out completely if there are problems. + */ + var = find_varbind_in_list( template_v2pdu->variables, + agentaddr_oid, agentaddr_oid_len); + if (!var && (template_v1pdu->agent_addr[0] + || template_v1pdu->agent_addr[1] + || template_v1pdu->agent_addr[2] + || template_v1pdu->agent_addr[3])) { + if (!snmp_varlist_add_variable( &(template_v2pdu->variables), + agentaddr_oid, agentaddr_oid_len, + ASN_IPADDRESS, + (u_char*)&(template_v1pdu->agent_addr), + sizeof(template_v1pdu->agent_addr))) + snmp_log(LOG_WARNING, + "send_trap: failed to append snmpTrapAddr varbind\n"); + } + var = find_varbind_in_list( template_v2pdu->variables, + community_oid, community_oid_len); + if (!var && template_v1pdu->community) { + if (!snmp_varlist_add_variable( &(template_v2pdu->variables), + community_oid, community_oid_len, + ASN_OCTET_STR, + template_v1pdu->community, + template_v1pdu->community_len)) + snmp_log(LOG_WARNING, + "send_trap: failed to append snmpTrapCommunity varbind\n"); + } + var = find_varbind_in_list( template_v2pdu->variables, + snmptrapenterprise_oid, + snmptrapenterprise_oid_len); + if (!var) { + if (!snmp_varlist_add_variable( &(template_v2pdu->variables), + snmptrapenterprise_oid, snmptrapenterprise_oid_len, + ASN_OBJECT_ID, + (u_char*)template_v1pdu->enterprise, + template_v1pdu->enterprise_length*sizeof(oid))) + snmp_log(LOG_WARNING, + "send_trap: failed to append snmpEnterprise varbind\n"); + } + return template_v2pdu; +} + +/** + * This function allows you to make a distinction between generic + * traps from different classes of equipment. For example, you may want + * to handle a SNMP_TRAP_LINKDOWN trap for a particular device in a + * different manner to a generic system SNMP_TRAP_LINKDOWN trap. + * + * + * @param trap is the generic trap type. The trap types are: + * - SNMP_TRAP_COLDSTART: + * cold start + * - SNMP_TRAP_WARMSTART: + * warm start + * - SNMP_TRAP_LINKDOWN: + * link down + * - SNMP_TRAP_LINKUP: + * link up + * - SNMP_TRAP_AUTHFAIL: + * authentication failure + * - SNMP_TRAP_EGPNEIGHBORLOSS: + * egp neighbor loss + * - SNMP_TRAP_ENTERPRISESPECIFIC: + * enterprise specific + * + * @param specific is the specific trap value. + * + * @param enterprise is an enterprise oid in which you want to send specific + * traps from. + * + * @param enterprise_length is the length of the enterprise oid, use macro, + * OID_LENGTH, to compute length. + * + * @param vars is used to supply list of variable bindings to form an SNMPv2 + * trap. + * + * @param context currently unused + * + * @param flags currently unused + * + * @return void + * + * @see send_easy_trap + * @see send_v2trap + */ +int +netsnmp_send_traps(int trap, int specific, + const oid * enterprise, int enterprise_length, + netsnmp_variable_list * vars, + const char * context, int flags) +{ + netsnmp_pdu *template_v1pdu; + netsnmp_pdu *template_v2pdu; + netsnmp_variable_list *vblist = NULL; + netsnmp_variable_list *trap_vb; + netsnmp_variable_list *var; + in_addr_t *pdu_in_addr_t; + u_long uptime; + struct trap_sink *sink; + const char *v1trapaddress; + int res = 0; + + DEBUGMSGTL(( "trap", "send_trap %d %d ", trap, specific)); + DEBUGMSGOID(("trap", enterprise, enterprise_length)); + DEBUGMSG(( "trap", "\n")); + + if (vars) { + vblist = snmp_clone_varbind( vars ); + if (!vblist) { + snmp_log(LOG_WARNING, + "send_trap: failed to clone varbind list\n"); + return -1; + } + } + + if ( trap == -1 ) { + /* + * Construct the SNMPv2-style notification PDU + */ + if (!vblist) { + snmp_log(LOG_WARNING, + "send_trap: called with NULL v2 information\n"); + return -1; + } + template_v2pdu = snmp_pdu_create(SNMP_MSG_TRAP2); + if (!template_v2pdu) { + snmp_log(LOG_WARNING, + "send_trap: failed to construct v2 template PDU\n"); + snmp_free_varbind(vblist); + return -1; + } + + /* + * Check the varbind list we've been given. + * If it starts with a 'sysUptime.0' varbind, then use that. + * Otherwise, prepend a suitable 'sysUptime.0' varbind. + */ + if (!snmp_oid_compare( vblist->name, vblist->name_length, + sysuptime_oid, sysuptime_oid_len )) { + template_v2pdu->variables = vblist; + trap_vb = vblist->next_variable; + } else { + uptime = netsnmp_get_agent_uptime(); + var = NULL; + snmp_varlist_add_variable( &var, + sysuptime_oid, sysuptime_oid_len, + ASN_TIMETICKS, (u_char*)&uptime, sizeof(uptime)); + if (!var) { + snmp_log(LOG_WARNING, + "send_trap: failed to insert sysUptime varbind\n"); + snmp_free_pdu(template_v2pdu); + snmp_free_varbind(vblist); + return -1; + } + template_v2pdu->variables = var; + var->next_variable = vblist; + trap_vb = vblist; + } + + /* + * 'trap_vb' should point to the snmpTrapOID.0 varbind, + * identifying the requested trap. If not then bomb out. + * If it's a 'standard' trap, then we need to append an + * snmpEnterprise varbind (if there isn't already one). + */ + if (!trap_vb || + snmp_oid_compare(trap_vb->name, trap_vb->name_length, + snmptrap_oid, snmptrap_oid_len)) { + snmp_log(LOG_WARNING, + "send_trap: no v2 trapOID varbind provided\n"); + snmp_free_pdu(template_v2pdu); + return -1; + } + if (!snmp_oid_compare(vblist->val.objid, OID_LENGTH(trap_prefix), + trap_prefix, OID_LENGTH(trap_prefix))) { + var = find_varbind_in_list( template_v2pdu->variables, + snmptrapenterprise_oid, + snmptrapenterprise_oid_len); + if (!var && + !snmp_varlist_add_variable( &(template_v2pdu->variables), + snmptrapenterprise_oid, snmptrapenterprise_oid_len, + ASN_OBJECT_ID, + enterprise, enterprise_length*sizeof(oid))) { + snmp_log(LOG_WARNING, + "send_trap: failed to add snmpEnterprise to v2 trap\n"); + snmp_free_pdu(template_v2pdu); + return -1; + } + } + + + /* + * If everything's OK, convert the v2 template into an SNMPv1 trap PDU. + */ + template_v1pdu = convert_v2pdu_to_v1( template_v2pdu ); + if (!template_v1pdu) { + snmp_log(LOG_WARNING, + "send_trap: failed to convert v2->v1 template PDU\n"); + } + + } else { + /* + * Construct the SNMPv1 trap PDU.... + */ + template_v1pdu = snmp_pdu_create(SNMP_MSG_TRAP); + if (!template_v1pdu) { + snmp_log(LOG_WARNING, + "send_trap: failed to construct v1 template PDU\n"); + snmp_free_varbind(vblist); + return -1; + } + template_v1pdu->trap_type = trap; + template_v1pdu->specific_type = specific; + template_v1pdu->time = netsnmp_get_agent_uptime(); + + if (snmp_clone_mem((void **) &template_v1pdu->enterprise, + enterprise, enterprise_length * sizeof(oid))) { + snmp_log(LOG_WARNING, + "send_trap: failed to set v1 enterprise OID\n"); + snmp_free_varbind(vblist); + snmp_free_pdu(template_v1pdu); + return -1; + } + template_v1pdu->enterprise_length = enterprise_length; + + template_v1pdu->flags |= UCD_MSG_FLAG_FORCE_PDU_COPY; + template_v1pdu->variables = vblist; + + /* + * ... and convert it into an SNMPv2-style notification PDU. + */ + + template_v2pdu = convert_v1pdu_to_v2( template_v1pdu ); + if (!template_v2pdu) { + snmp_log(LOG_WARNING, + "send_trap: failed to convert v1->v2 template PDU\n"); + } + } + + /* + * Check whether we're ignoring authFail traps + */ + if (template_v1pdu) { + if (template_v1pdu->trap_type == SNMP_TRAP_AUTHFAIL && + snmp_enableauthentraps == SNMP_AUTHENTICATED_TRAPS_DISABLED) { + snmp_free_pdu(template_v1pdu); + snmp_free_pdu(template_v2pdu); + return 0; + } + + /* + * Ensure that the v1 trap PDU includes the local IP address + */ + pdu_in_addr_t = (in_addr_t *) template_v1pdu->agent_addr; + v1trapaddress = netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_TRAP_ADDR); + if (v1trapaddress != NULL) { + /* "v1trapaddress" was specified in config, try to resolve it */ + res = netsnmp_gethostbyname_v4(v1trapaddress, pdu_in_addr_t); + } + if (v1trapaddress == NULL || res < 0) { + /* "v1trapaddress" was not specified in config or the resolution failed, + * try any local address */ + *pdu_in_addr_t = get_myaddr(); + } + + } + + if (template_v2pdu) { + /* A context name was provided, so copy it and its length to the v2 pdu + * template. */ + if (context != NULL) + { + template_v2pdu->contextName = strdup(context); + template_v2pdu->contextNameLen = strlen(context); + } + } + + /* + * Now loop through the list of trap sinks + * and call the trap callback routines, + * providing an appropriately formatted PDU in each case + */ + for (sink = sinks; sink; sink = sink->next) { +#ifndef NETSNMP_DISABLE_SNMPV1 + if (sink->version == SNMP_VERSION_1) { + if (template_v1pdu && + !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DISABLE_V1)) { + send_trap_to_sess(sink->sesp, template_v1pdu); + } + } else +#endif + if (template_v2pdu) { + template_v2pdu->command = sink->pdutype; + send_trap_to_sess(sink->sesp, template_v2pdu); + } + } +#ifndef NETSNMP_DISABLE_SNMPV1 + if (template_v1pdu && _v1_sessions) + snmp_call_callbacks(SNMP_CALLBACK_APPLICATION, + SNMPD_CALLBACK_SEND_TRAP1, template_v1pdu); +#endif + if (template_v2pdu && _v2_sessions) + snmp_call_callbacks(SNMP_CALLBACK_APPLICATION, + SNMPD_CALLBACK_SEND_TRAP2, template_v2pdu); + snmp_free_pdu(template_v1pdu); + snmp_free_pdu(template_v2pdu); + return 0; +} + + +void +send_enterprise_trap_vars(int trap, + int specific, + const oid * enterprise, int enterprise_length, + netsnmp_variable_list * vars) +{ + netsnmp_send_traps(trap, specific, + enterprise, enterprise_length, + vars, NULL, 0); + return; +} + +/** + * Handles stats for basic traps (really just send failed +*/ +int +handle_trap_callback(int op, netsnmp_session * session, int reqid, + netsnmp_pdu *pdu, void *magic) +{ + if (NULL == session) + return 0; + + DEBUGMSGTL(("trap", "handle_trap_callback for session %s\n", + session->paramName ? session->paramName : "UNKNOWN")); + switch (op) { + + case NETSNMP_CALLBACK_OP_SEND_FAILED: + DEBUGMSGTL(("trap", "failed to send an inform for reqid=%d\n", reqid)); +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) { + session->trap_stats->sent_last_fail = netsnmp_get_agent_uptime(); + ++session->trap_stats->sent_fail_count; + } +#endif /* NETSNMP_NO_TRAP_STATS */ + break; + + case NETSNMP_CALLBACK_OP_SEC_ERROR: + DEBUGMSGTL(("trap", "sec error sending a trap for reqid=%d\n", + reqid)); +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) { + session->trap_stats->sec_err_last = netsnmp_get_agent_uptime(); + ++session->trap_stats->sec_err_count; + } +#endif /* NETSNMP_NO_TRAP_STATS */ + break; + + case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: + case NETSNMP_CALLBACK_OP_TIMED_OUT: + case NETSNMP_CALLBACK_OP_RESEND: + default: + DEBUGMSGTL(("trap", + "received op=%d for reqid=%d when trying to send a trap\n", + op, reqid)); + } +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) + _dump_trap_stats(session); +#endif /* NETSNMP_NO_TRAP_STATS */ + + return 1; +} + + +/** + * Captures responses or the lack there of from INFORMs that were sent + * 1) a response is received from an INFORM + * 2) one isn't received and the retries/timeouts have failed +*/ +int +handle_inform_response(int op, netsnmp_session * session, + int reqid, netsnmp_pdu *pdu, + void *magic) +{ + if (NULL == session) + return 0; + + DEBUGMSGTL(("trap", "handle_inform_response for session %s\n", + session->paramName ? session->paramName : "UNKNOWN")); + switch (op) { + + case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: + snmp_increment_statistic(STAT_SNMPINPKTS); + if (pdu->command != SNMP_MSG_REPORT) { + DEBUGMSGTL(("trap", "received the inform response for reqid=%d\n", + reqid)); +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) { + ++session->trap_stats->ack_count; + session->trap_stats->ack_last_rcvd = netsnmp_get_agent_uptime(); + } +#endif /* NETSNMP_NO_TRAP_STATS */ + break; + } else { + int type = session->s_snmp_errno ? session->s_snmp_errno : + snmpv3_get_report_type(pdu); + DEBUGMSGTL(("trap", "received report %d for inform reqid=%d\n", + type, reqid)); + /* + * xxx-rks: what stats, if any, to bump for other report types? + * - ignore NOT_IN_TIME, as agent will sync and retry. + */ + if (SNMPERR_AUTHENTICATION_FAILURE != type) + break; + } + /** AUTH failures fall through to sec error */ + /* FALL THROUGH */ + + case NETSNMP_CALLBACK_OP_SEC_ERROR: + DEBUGMSGTL(("trap", "sec error sending an inform for reqid=%d\n", + reqid)); +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) { + session->trap_stats->sec_err_last = netsnmp_get_agent_uptime(); + ++session->trap_stats->sec_err_count; + } +#endif /* NETSNMP_NO_TRAP_STATS */ + break; + + case NETSNMP_CALLBACK_OP_TIMED_OUT: + DEBUGMSGTL(("trap", + "received a timeout sending an inform for reqid=%d\n", + reqid)); +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) { + ++session->trap_stats->timeouts; + session->trap_stats->sent_last_timeout = + netsnmp_get_agent_uptime(); + } +#endif /* NETSNMP_NO_TRAP_STATS */ + break; + + case NETSNMP_CALLBACK_OP_RESEND: + DEBUGMSGTL(("trap", "resending an inform for reqid=%d\n", reqid)); +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) + session->trap_stats->sent_last_sent = netsnmp_get_agent_uptime(); +#endif /* NETSNMP_NO_TRAP_STATS */ + break; + + case NETSNMP_CALLBACK_OP_SEND_FAILED: + DEBUGMSGTL(("trap", "failed to send an inform for reqid=%d\n", reqid)); +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) { + session->trap_stats->sent_last_fail = netsnmp_get_agent_uptime(); + ++session->trap_stats->sent_fail_count; + } +#endif /* NETSNMP_NO_TRAP_STATS */ + break; + + default: + DEBUGMSGTL(("trap", "received op=%d for reqid=%d when trying to send an inform\n", op, reqid)); + } + +#ifndef NETSNMP_NO_TRAP_STATS + if (session->trap_stats) + _dump_trap_stats(session); +#endif /* NETSNMP_NO_TRAP_STATS */ + + return 1; +} + + +/* + * send_trap_to_sess: sends a trap to a session but assumes that the + * pdu is constructed correctly for the session type. + */ +void +send_trap_to_sess(netsnmp_session * sess, netsnmp_pdu *template_pdu) +{ + netsnmp_pdu *pdu; + int result; + + if (!sess || !template_pdu) + return; + + if (NETSNMP_RUNTIME_PROTOCOL_SKIP(sess->version)) { + DEBUGMSGTL(("trap", "not sending trap type=%d, version %02lx disabled\n", + template_pdu->command, sess->version)); + return; + } + DEBUGMSGTL(("trap", "sending trap type=%d, version=%ld\n", + template_pdu->command, sess->version)); + +#ifndef NETSNMP_DISABLE_SNMPV1 + if (sess->version == SNMP_VERSION_1 && + (template_pdu->command != SNMP_MSG_TRAP)) + return; /* Skip v1 sinks for v2 only traps */ + if (sess->version != SNMP_VERSION_1 && + (template_pdu->command == SNMP_MSG_TRAP)) + return; /* Skip v2+ sinks for v1 only traps */ +#endif + template_pdu->version = sess->version; + pdu = snmp_clone_pdu(template_pdu); + if(!pdu) { + snmp_log(LOG_WARNING, "send_trap: failed to clone PDU\n"); + return; + } + + pdu->sessid = sess->sessid; /* AgentX only ? */ + /* + * RFC 3414 sayeth: + * + * - If an SNMP engine uses a msgID for correlating Response messages to + * outstanding Request messages, then it MUST use different msgIDs in + * all such Request messages that it sends out during a Time Window + * (150 seconds) period. + * + * A Command Generator or Notification Originator Application MUST use + * different request-ids in all Request PDUs that it sends out during + * a TimeWindow (150 seconds) period. + */ + pdu->reqid = snmp_get_next_reqid(); + pdu->msgid = snmp_get_next_msgid(); + +#ifndef NETSNMP_NO_TRAP_STATS + /** allocate space for trap stats */ + if (NULL == sess->trap_stats) { + sess->trap_stats = SNMP_MALLOC_TYPEDEF(netsnmp_trap_stats); + if (NULL == sess->trap_stats) + snmp_log(LOG_ERR, "malloc for %s trap stats failed\n", + sess->paramName ? sess->paramName : "UNKNOWN"); + } +#endif /* NETSNMP_NO_TRAP_STATS */ + + if ( template_pdu->command == SNMP_MSG_INFORM +#ifdef USING_AGENTX_PROTOCOL_MODULE + || template_pdu->command == AGENTX_MSG_NOTIFY +#endif + ) { + result = + snmp_async_send(sess, pdu, &handle_inform_response, NULL); + } else { + if ((sess->version == SNMP_VERSION_3) && + (pdu->command == SNMP_MSG_TRAP2) && + (sess->securityEngineIDLen == 0)) { + u_char tmp[SPRINT_MAX_LEN]; + + int len = snmpv3_get_engineID(tmp, sizeof(tmp)); + pdu->securityEngineID = netsnmp_memdup(tmp, len); + pdu->securityEngineIDLen = len; + } + + result = snmp_async_send(sess, pdu, &handle_trap_callback, NULL); + } + + if (result == 0) { + snmp_sess_perror("snmpd: send_trap", sess); + snmp_free_pdu(pdu); + /** trap stats for failure handled in callback */ + } else { + snmp_increment_statistic(STAT_SNMPOUTTRAPS); + snmp_increment_statistic(STAT_SNMPOUTPKTS); +#ifndef NETSNMP_NO_TRAP_STATS + if (sess->trap_stats) { + sess->trap_stats->sent_last_sent = netsnmp_get_agent_uptime(); + ++sess->trap_stats->sent_count; + _dump_trap_stats(sess); + } +#endif /* NETSNMP_NO_TRAP_STATS */ + } +} + +void +send_trap_vars(int trap, int specific, netsnmp_variable_list * vars) +{ + if (trap == SNMP_TRAP_ENTERPRISESPECIFIC) + send_enterprise_trap_vars(trap, specific, objid_enterprisetrap, + OID_LENGTH(objid_enterprisetrap), vars); + else + send_enterprise_trap_vars(trap, specific, trap_version_id, + OID_LENGTH(trap_version_id), vars); +} + +#ifndef NETSNMP_FEATURE_REMOVE_TRAP_VARS_WITH_CONTEXT +/* Send a trap under a context */ +void send_trap_vars_with_context(int trap, int specific, + netsnmp_variable_list *vars, const char *context) +{ + if (trap == SNMP_TRAP_ENTERPRISESPECIFIC) + netsnmp_send_traps(trap, specific, objid_enterprisetrap, + OID_LENGTH(objid_enterprisetrap), vars, + context, 0); + else + netsnmp_send_traps(trap, specific, trap_version_id, + OID_LENGTH(trap_version_id), vars, + context, 0); + +} +#endif /* NETSNMP_FEATURE_REMOVE_TRAP_VARS_WITH_CONTEXT */ + +/** + * Sends an SNMPv1 trap (or the SNMPv2 equivalent) to the list of + * configured trap destinations (or "sinks"), using the provided + * values for the generic trap type and specific trap value. + * + * This function eventually calls send_enterprise_trap_vars. If the + * trap type is not set to SNMP_TRAP_ENTERPRISESPECIFIC the enterprise + * and enterprise_length paramater is set to the pre defined NETSNMP_SYSTEM_MIB + * oid and length respectively. If the trap type is set to + * SNMP_TRAP_ENTERPRISESPECIFIC the enterprise and enterprise_length + * parameters are set to the pre-defined NETSNMP_NOTIFICATION_MIB oid and length + * respectively. + * + * @param trap is the generic trap type. + * + * @param specific is the specific trap value. + * + * @return void + * + * @see send_enterprise_trap_vars + * @see send_v2trap + */ + +void +send_easy_trap(int trap, int specific) +{ + send_trap_vars(trap, specific, NULL); +} + +/** + * Uses the supplied list of variable bindings to form an SNMPv2 trap, + * which is sent to SNMPv2-capable sinks on the configured list. + * An equivalent INFORM is sent to the configured list of inform sinks. + * Sinks that can only handle SNMPv1 traps are skipped. + * + * This function eventually calls send_enterprise_trap_vars. If the + * trap type is not set to SNMP_TRAP_ENTERPRISESPECIFIC the enterprise + * and enterprise_length paramater is set to the pre defined NETSNMP_SYSTEM_MIB + * oid and length respectively. If the trap type is set to + * SNMP_TRAP_ENTERPRISESPECIFIC the enterprise and enterprise_length + * parameters are set to the pre-defined NETSNMP_NOTIFICATION_MIB oid and length + * respectively. + * + * @param vars is used to supply list of variable bindings to form an SNMPv2 + * trap. + * + * @return void + * + * @see send_easy_trap + * @see send_enterprise_trap_vars + */ + +void +send_v2trap(netsnmp_variable_list * vars) +{ + send_trap_vars(-1, -1, vars); +} + +/** + * Similar to send_v2trap(), with the added ability to specify a context. If + * the last parameter is NULL, then this call is equivalent to send_v2trap(). + * + * @param vars is used to supply the list of variable bindings for the trap. + * + * @param context is used to specify the context of the trap. + * + * @return void + * + * @see send_v2trap + */ +#ifndef NETSNMP_FEATURE_REMOVE_SEND_V3TRAP +void send_v3trap(netsnmp_variable_list *vars, const char *context) +{ + netsnmp_send_traps(-1, -1, + trap_version_id, OID_LENGTH(trap_version_id), + vars, context, 0); +} +#endif /* NETSNMP_FEATURE_REMOVE_SEND_V3TRAP */ + +#ifndef NETSNMP_FEATURE_REMOVE_SEND_TRAP_PDU +void +send_trap_pdu(netsnmp_pdu *pdu) +{ + send_trap_vars(-1, -1, pdu->variables); +} +#endif /* NETSNMP_FEATURE_REMOVE_SEND_TRAP_PDU */ + + + + /******************* + * + * Config file handling + * + *******************/ + +void +snmpd_parse_config_authtrap(const char *token, char *cptr) +{ + int i; + + i = atoi(cptr); + if (i == 0) { + if (strcmp(cptr, "enable") == 0) { + i = SNMP_AUTHENTICATED_TRAPS_ENABLED; + } else if (strcmp(cptr, "disable") == 0) { + i = SNMP_AUTHENTICATED_TRAPS_DISABLED; + } + } + if (i < 1 || i > 2) { + config_perror("authtrapenable must be 1 or 2"); + } else { + if (strcmp(token, "pauthtrapenable") == 0) { + if (snmp_enableauthentrapsset < 0) { + /* + * This is bogus (and shouldn't happen anyway) -- the value + * of snmpEnableAuthenTraps.0 is already configured + * read-only. + */ + snmp_log(LOG_WARNING, + "ignoring attempted override of read-only snmpEnableAuthenTraps.0\n"); + return; + } else { + snmp_enableauthentrapsset++; + } + } else { + if (snmp_enableauthentrapsset > 0) { + /* + * This is bogus (and shouldn't happen anyway) -- we already + * read a persistent value of snmpEnableAuthenTraps.0, which + * we should ignore in favour of this one. + */ + snmp_log(LOG_WARNING, + "ignoring attempted override of read-only snmpEnableAuthenTraps.0\n"); + /* + * Fall through and copy in this value. + */ + } + snmp_enableauthentrapsset = -1; + } + snmp_enableauthentraps = i; + } +} + +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +static void +_parse_config_sink(const char *token, char *cptr, int version, int type) +{ + char *sp, *cp, *pp = NULL, *src = NULL; + char *st, *name = NULL, *tag = NULL, *profile = NULL; + int done = 0; + + if (!snmp_trapcommunity) + snmp_trapcommunity = strdup("public"); + sp = strtok_r(cptr, " \t\n", &st); + /* + * check for optional arguments + */ + do { + if (*sp != '-') { + done = 1; + continue; + } + if (strcmp(sp, "-name") == 0) + name = strtok_r(NULL, " \t\n", &st); + else if (strcmp(sp, "-tag") == 0) + tag = strtok_r(NULL, " \t\n", &st); + else if (strcmp(sp, "-profile") == 0) + profile = strtok_r(NULL, " \t\n", &st); + else if (strcmp(sp, "-s") == 0) + src = strtok_r(NULL, " \t\n", &st); + else + netsnmp_config_warn("ignoring unknown argument: %s", sp); + sp = strtok_r(NULL, " \t\n", &st); + } while (!done); + cp = strtok_r(NULL, " \t\n", &st); + if (cp) + pp = strtok_r(NULL, " \t\n", &st); + if (pp) + config_pwarn("The separate port argument for sinks is deprecated"); + if (netsnmp_create_v1v2_notification_session(sp, pp, + cp ? cp : snmp_trapcommunity, + src, version, type, name, tag, + profile) == NULL) { + netsnmp_config_error("cannot create sink: %s", cptr); + } +} +#endif + +#ifndef NETSNMP_DISABLE_SNMPV1 +void +snmpd_parse_config_trapsink(const char *token, char *cptr) +{ + _parse_config_sink(token, cptr, SNMP_VERSION_1, SNMP_MSG_TRAP); +} +#endif + +#ifndef NETSNMP_DISABLE_SNMPV2C +void +snmpd_parse_config_trap2sink(const char *word, char *cptr) +{ + _parse_config_sink(word, cptr, SNMP_VERSION_2c, SNMP_MSG_TRAP2); +} + +void +snmpd_parse_config_informsink(const char *word, char *cptr) +{ + _parse_config_sink(word, cptr, SNMP_VERSION_2c, SNMP_MSG_INFORM); +} +#endif + +/* + * this must be standardized somewhere, right? + */ +#define MAX_ARGS 128 + +static int traptype; + +static void +trapOptProc(int argc, char *const *argv, int opt) +{ + switch (opt) { + case 'C': + while (*optarg) { + switch (*optarg++) { + case 'i': + traptype = SNMP_MSG_INFORM; + break; + default: + config_perror("unknown argument passed to -C"); + break; + } + } + break; + } +} + +netsnmp_session * +netsnmp_create_v3user_notification_session(const char *dest, const char *user, + int level, const char *context, + int pdutype, const u_char *engineId, + size_t engineId_len, const char *src, + const char *notif_name, + const char *notif_tag, + const char* notif_profile) +{ + netsnmp_session session, *ss = NULL; + struct usmUser *usmUser; + netsnmp_tdomain_spec tspec; + netsnmp_transport *transport; + u_char tmp_engineId[SPRINT_MAX_LEN]; + int rc; + + if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DISABLE_V3)) { + netsnmp_config_error("SNMPv3 disabled, cannot create notification session"); + return NULL; + } + if (NULL == dest || NULL == user) + return NULL; + + /** authlevel */ + if ((SNMP_SEC_LEVEL_AUTHPRIV != level) && + (SNMP_SEC_LEVEL_AUTHNOPRIV != level) && + (SNMP_SEC_LEVEL_NOAUTH != level)) { + DEBUGMSGTL(("trap:v3user_notif_sess", "bad level %d\n", level)); + return NULL; + } + + /** need engineId to look up users */ + if (NULL == engineId) { + engineId_len = snmpv3_get_engineID( tmp_engineId, sizeof(tmp_engineId)); + engineId = tmp_engineId; + } + + usmUser = usm_get_user(NETSNMP_REMOVE_CONST(u_char *,engineId), + engineId_len, NETSNMP_REMOVE_CONST(char *,user)); + if (NULL == usmUser) { + DEBUGMSGTL(("trap:v3user_notif_sess", "usmUser %s not found\n", user)); + return NULL; + } + + snmp_sess_init(&session); + + session.version = SNMP_VERSION_3; + + session.peername = NETSNMP_REMOVE_CONST(char*,dest); + + session.securityName = NETSNMP_REMOVE_CONST(char*,user); + session.securityNameLen = strlen(user); + + if (NULL != context) { + session.contextName = NETSNMP_REMOVE_CONST(char*,context); + session.contextNameLen = strlen(context); + } + + session.securityLevel = level; + + /** auth prot */ + if (NULL != usmUser->authProtocol) { + session.securityAuthProto = + snmp_duplicate_objid(usmUser->authProtocol, + usmUser->authProtocolLen); + session.securityAuthProtoLen = usmUser->authProtocolLen; + if (NULL == session.securityAuthProto) + goto bail; + } + + /** authkey */ + if (((SNMP_SEC_LEVEL_AUTHPRIV == level) || + (SNMP_SEC_LEVEL_AUTHNOPRIV == level)) && + (usmUser->flags & USMUSER_FLAG_KEEP_MASTER_KEY)) { + netsnmp_assert(usmUser->authKeyKuLen > 0); + memcpy(session.securityAuthKey, usmUser->authKeyKu, + usmUser->authKeyKuLen); + session.securityAuthKeyLen = usmUser->authKeyKuLen; + } + + /** priv prot */ + if (NULL != usmUser->privProtocol) { + session.securityPrivProto = + snmp_duplicate_objid(usmUser->privProtocol, + usmUser->privProtocolLen); + session.securityPrivProtoLen = usmUser->privProtocolLen; + if (NULL == session.securityPrivProto) + goto bail; + } + + /** privkey */ + if ((SNMP_SEC_LEVEL_AUTHPRIV == level) && + (usmUser->flags & USMUSER_FLAG_KEEP_MASTER_KEY)) { + netsnmp_assert(usmUser->privKeyKuLen > 0); + memcpy(session.securityPrivKey, usmUser->privKeyKu, + usmUser->privKeyKuLen); + session.securityPrivKeyLen = usmUser->privKeyKuLen; + } + + /** engineId */ + session.contextEngineID = netsnmp_memdup(usmUser->engineID, + usmUser->engineIDLen); + session.contextEngineIDLen = usmUser->engineIDLen; + + /** open the tranport */ + + memset(&tspec, 0, sizeof(netsnmp_tdomain_spec)); + tspec.application = "snmptrap"; + tspec.target = session.peername; + tspec.default_domain = NULL; + tspec.default_target = NULL; + tspec.source = src; + transport = netsnmp_tdomain_transport_tspec(&tspec); + if (transport == NULL) { + DEBUGMSGTL(("trap:v3user_notif_sess", "could not create transport\n")); + goto bail; + } + + if ((rc = netsnmp_sess_config_and_open_transport(&session, transport)) + != SNMPERR_SUCCESS) { + DEBUGMSGTL(("trap:v3user_notif_sess", "config/open failed\n")); + goto bail; + } + + ss = snmp_add(&session, transport, NULL, NULL); + if (!ss) { + DEBUGMSGTL(("trap:v3user_notif_sess", "snmp_add failed\n")); + goto bail; + } + + if (netsnmp_add_notification_session(ss, pdutype, + (pdutype == SNMP_MSG_INFORM), + ss->version, notif_name, notif_tag, + notif_profile) != 1) { + DEBUGMSGTL(("trap:v3user_notif_sess", "add notification failed\n")); + snmp_sess_close(ss); + ss = NULL; + goto bail; + } + + bail: + /** free any allocated mem in session */ + SNMP_FREE(session.securityAuthProto); + SNMP_FREE(session.securityPrivProto); + + return ss; +} + +void +snmpd_parse_config_trapsess(const char *word, char *cptr) +{ + char *argv[MAX_ARGS], *cp = cptr; + char *profile = NULL, *name = NULL, *tag = NULL; + int argn, rc; + netsnmp_session session, *ss; + netsnmp_transport *transport; + size_t len; + char tmp[SPRINT_MAX_LEN]; + char *clientaddr_save = NULL; + + /* + * inform or trap? default to trap + */ + traptype = SNMP_MSG_TRAP2; + + do { + if (strncmp(cp, "-profile", 8) == 0) { + cp = skip_token(cp); + cp = copy_nword(cp, tmp, SPRINT_MAX_LEN); + profile = strdup(tmp); + } else if (strncmp(cp, "-name", 5) == 0) { + cp = skip_token(cp); + cp = copy_nword(cp, tmp, SPRINT_MAX_LEN); + name = strdup(tmp); + } else if (strncmp(cp, "-tag", 5) == 0) { + cp = skip_token(cp); + cp = copy_nword(cp, tmp, SPRINT_MAX_LEN); + tag = strdup(tmp); + } else + break; + } while(cp); + + /* + * create the argv[] like array + */ + argv[0] = strdup("snmpd-trapsess"); /* bogus entry for getopt() */ + for (argn = 1; cp && argn < MAX_ARGS; argn++) { + cp = copy_nword(cp, tmp, SPRINT_MAX_LEN); + argv[argn] = strdup(tmp); + } + + /** parse args (also initializes session) */ + netsnmp_parse_args(argn, argv, &session, "C:", trapOptProc, + NETSNMP_PARSE_ARGS_NOLOGGING | + NETSNMP_PARSE_ARGS_NOZERO); + + if (NETSNMP_RUNTIME_PROTOCOL_SKIP(session.version)) { + config_perror("snmpd: protocol version disabled at runtime"); + for (; argn > 0; argn--) + free(argv[argn - 1]); + goto cleanup; + } + + if (NULL != session.localname) { + clientaddr_save = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CLIENT_ADDR); + if (clientaddr_save) + clientaddr_save = strdup(clientaddr_save); + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CLIENT_ADDR, + session.localname); + } + + transport = netsnmp_transport_open_client("snmptrap", session.peername); + + if (NULL != session.localname) + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CLIENT_ADDR, clientaddr_save); + + if (transport == NULL) { + config_perror("snmpd: failed to parse this line."); + for (; argn > 0; argn--) + free(argv[argn - 1]); + goto cleanup; + } + if ((rc = netsnmp_sess_config_and_open_transport(&session, transport)) + != SNMPERR_SUCCESS) { + session.s_snmp_errno = rc; + session.s_errno = 0; + for (; argn > 0; argn--) + free(argv[argn - 1]); + goto cleanup; + } + ss = snmp_add(&session, transport, NULL, NULL); + for (; argn > 0; argn--) + free(argv[argn - 1]); + + if (!ss) { + config_perror + ("snmpd: failed to parse this line or the remote trap receiver is down. Possible cause:"); + snmp_sess_perror("snmpd: snmpd_parse_config_trapsess()", &session); + goto cleanup; + } + + /* + * If this is an SNMPv3 TRAP session, then the agent is + * the authoritative engine, so set the engineID accordingly + */ + if (ss->version == SNMP_VERSION_3 && + traptype != SNMP_MSG_INFORM && + ss->securityEngineIDLen == 0) { + u_char tmp[SPRINT_MAX_LEN]; + + len = snmpv3_get_engineID( tmp, sizeof(tmp)); + ss->securityEngineID = netsnmp_memdup(tmp, len); + ss->securityEngineIDLen = len; + } + +#ifndef NETSNMP_DISABLE_SNMPV1 + if ((ss->version == SNMP_VERSION_1) && + !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DISABLE_V1)) + traptype = SNMP_MSG_TRAP; +#endif + netsnmp_add_notification_session(ss, traptype, + (traptype == SNMP_MSG_INFORM), + ss->version, name, tag, profile); + + cleanup: + SNMP_FREE(clientaddr_save); + SNMP_FREE(profile); + SNMP_FREE(name); + SNMP_FREE(tag); +} + +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +void +snmpd_parse_config_trapcommunity(const char *word, char *cptr) +{ + if (snmp_trapcommunity != NULL) { + free(snmp_trapcommunity); + } + snmp_trapcommunity = (char *) malloc(strlen(cptr) + 1); + if (snmp_trapcommunity != NULL) { + copy_nword(cptr, snmp_trapcommunity, strlen(cptr) + 1); + } +} + +void +snmpd_free_trapcommunity(void) +{ + if (snmp_trapcommunity) { + free(snmp_trapcommunity); + snmp_trapcommunity = NULL; + } +} +#endif +/** @} */ diff --git a/agent/mibgroup/agentx/master.c b/agent/mibgroup/agentx/master.c index 7ce062a..93d5621 100644 --- a/agent/mibgroup/agentx/master.c +++ b/agent/mibgroup/agentx/master.c @@ -280,6 +280,11 @@ agentx_got_response(int operation, netsnmp_free_delegated_cache(cache); return 0; + case NETSNMP_CALLBACK_OP_RESEND: + DEBUGMSGTL(("agentx/master", "resend on session %8p req=0x%x\n", + session, (unsigned)reqid)); + return 0; + case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: /* * This session is alive diff --git a/agent/mibgroup/agentx/master.c.agent-of-death b/agent/mibgroup/agentx/master.c.agent-of-death new file mode 100644 index 0000000..7ce062a --- /dev/null +++ b/agent/mibgroup/agentx/master.c.agent-of-death @@ -0,0 +1,616 @@ +/* + * AgentX master agent + */ +/* Portions of this file are subject to the following copyright(s). See + * the Net-SNMP's COPYING file for more details and other copyrights + * that may apply: + */ +/* + * Portions of this file are copyrighted by: + * Copyright � 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms specified in the COPYING file + * distributed with the Net-SNMP package. + */ + + +#include +#include +#if HAVE_IO_H +#include +#endif + +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#if HAVE_STRING_H +#include +#else +#include +#endif +#if HAVE_NETINET_IN_H +#include +#endif +#if HAVE_SYS_SOCKET_H +#include +#endif +#include + +#if HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#define SNMP_NEED_REQUEST_LIST +#include +#include +#include "snmpd.h" +#include "agentx/protocol.h" +#include "agentx/master_admin.h" + +netsnmp_feature_require(handler_mark_requests_as_delegated) +netsnmp_feature_require(unix_socket_paths) +netsnmp_feature_require(free_agent_snmp_session_by_session) + +void +real_init_master(void) +{ + netsnmp_session sess, *session = NULL; + char *agentx_sockets; + char *cp1; + + if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE) != MASTER_AGENT) + return; + + if (netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_X_SOCKET)) { + agentx_sockets = strdup(netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_X_SOCKET)); +#ifdef NETSNMP_AGENTX_DOM_SOCK_ONLY + if (agentx_sockets[0] != '/') { + /* unix:/path */ + if (agentx_sockets[5] != '/') { + snmp_log(LOG_ERR, + "Error: %s transport is not supported, disabling agentx/master.\n", agentx_sockets); + SNMP_FREE(agentx_sockets); + return; + } + } +#endif + } else { + agentx_sockets = strdup(""); + } + + + DEBUGMSGTL(("agentx/master", "initializing...\n")); + snmp_sess_init(&sess); + sess.version = AGENTX_VERSION_1; + sess.flags |= SNMP_FLAGS_STREAM_SOCKET; + sess.timeout = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_AGENTX_TIMEOUT); + sess.retries = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_AGENTX_RETRIES); + +#ifdef NETSNMP_TRANSPORT_UNIX_DOMAIN + { + int agentx_dir_perm = + netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_X_DIR_PERM); + if (agentx_dir_perm == 0) + agentx_dir_perm = NETSNMP_AGENT_DIRECTORY_MODE; + netsnmp_unix_create_path_with_mode(agentx_dir_perm); + } +#endif + + cp1 = agentx_sockets; + while (cp1) { + netsnmp_transport *t; + /* + * If the AgentX socket string contains multiple descriptors, + * then pick this apart and handle them one by one. + * + */ + sess.peername = cp1; + cp1 = strchr(sess.peername, ','); + if (cp1 != NULL) { + *cp1++ = '\0'; + } + + /* + * Let 'snmp_open' interpret the descriptor. + */ + sess.local_port = AGENTX_PORT; /* Indicate server & set default port */ + sess.callback = handle_master_agentx_packet; + errno = 0; + t = netsnmp_transport_open_server("agentx", sess.peername); + if (t == NULL) { + /* + * diagnose snmp_open errors with the input netsnmp_session + * pointer. + */ + char buf[1024]; + if (!netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) { + snprintf(buf, sizeof(buf), + "Error: Couldn't open a master agentx socket to " + "listen on (%s)", sess.peername); + snmp_sess_perror(buf, &sess); + exit(1); + } else { + snprintf(buf, sizeof(buf), + "Warning: Couldn't open a master agentx socket to " + "listen on (%s)", sess.peername); + netsnmp_sess_log_error(LOG_WARNING, buf, &sess); + } + } else { +#ifdef NETSNMP_TRANSPORT_UNIX_DOMAIN + if (t->domain == netsnmp_UnixDomain && t->local != NULL) { + /* + * Apply any settings to the ownership/permissions of the + * AgentX socket + */ + int agentx_sock_perm = + netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_X_SOCK_PERM); + int agentx_sock_user = + netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_X_SOCK_USER); + int agentx_sock_group = + netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_X_SOCK_GROUP); + + char name[sizeof(struct sockaddr_un) + 1]; + memcpy(name, t->local, t->local_length); + name[t->local_length] = '\0'; + + if (agentx_sock_perm != 0) + chmod(name, agentx_sock_perm); + + if (agentx_sock_user || agentx_sock_group) { + /* + * If either of user or group haven't been set, + * then leave them unchanged. + */ + if (agentx_sock_user == 0 ) + agentx_sock_user = -1; + if (agentx_sock_group == 0 ) + agentx_sock_group = -1; + chown(name, agentx_sock_user, agentx_sock_group); + } + } +#endif + session = + snmp_add_full(&sess, t, NULL, agentx_parse, NULL, NULL, + agentx_realloc_build, agentx_check_packet, NULL); + } + if (session == NULL) { + netsnmp_transport_free(t); + } + } + +#ifdef NETSNMP_TRANSPORT_UNIX_DOMAIN + netsnmp_unix_dont_create_path(); +#endif + + SNMP_FREE(agentx_sockets); + DEBUGMSGTL(("agentx/master", "initializing... DONE\n")); +} + + /* + * Handle the response from an AgentX subagent, + * merging the answers back into the original query + */ +int +agentx_got_response(int operation, + netsnmp_session * session, + int reqid, netsnmp_pdu *pdu, void *magic) +{ + netsnmp_delegated_cache *cache = (netsnmp_delegated_cache *) magic; + int i, ret; + netsnmp_request_info *requests, *request; + netsnmp_variable_list *var; + netsnmp_session *ax_session; + + cache = netsnmp_handler_check_cache(cache); + if (!cache) { + DEBUGMSGTL(("agentx/master", "response too late on session %8p\n", + session)); + /* response is too late, free the cache */ + if (magic) + netsnmp_free_delegated_cache((netsnmp_delegated_cache*) magic); + return 1; + } + requests = cache->requests; + + switch (operation) { + case NETSNMP_CALLBACK_OP_TIMED_OUT:{ + void *s = snmp_sess_pointer(session); + DEBUGMSGTL(("agentx/master", "timeout on session %8p req=0x%x\n", + session, (unsigned)reqid)); + + netsnmp_handler_mark_requests_as_delegated(requests, + REQUEST_IS_NOT_DELEGATED); + netsnmp_set_request_error(cache->reqinfo, requests, + /* XXXWWW: should be index=0 */ + SNMP_ERR_GENERR); + + /* + * This is a bit sledgehammer because the other sessions on this + * transport may be okay (e.g. some thread in the subagent has + * wedged, but the others are alright). OTOH the overwhelming + * probability is that the whole agent has died somehow. + */ + + if (s != NULL) { + netsnmp_transport *t = snmp_sess_transport(s); + close_agentx_session(session, -1); + + if (t != NULL) { + DEBUGMSGTL(("agentx/master", "close transport\n")); + t->f_close(t); + } else { + DEBUGMSGTL(("agentx/master", "NULL transport??\n")); + } + } else { + DEBUGMSGTL(("agentx/master", "NULL sess_pointer??\n")); + } + ax_session = (netsnmp_session *) cache->localinfo; + netsnmp_free_agent_snmp_session_by_session(ax_session, NULL); + netsnmp_free_delegated_cache(cache); + return 0; + } + + case NETSNMP_CALLBACK_OP_DISCONNECT: + case NETSNMP_CALLBACK_OP_SEND_FAILED: + if (operation == NETSNMP_CALLBACK_OP_DISCONNECT) { + DEBUGMSGTL(("agentx/master", "disconnect on session %8p\n", + session)); + } else { + DEBUGMSGTL(("agentx/master", "send failed on session %8p\n", + session)); + } + close_agentx_session(session, -1); + netsnmp_handler_mark_requests_as_delegated(requests, + REQUEST_IS_NOT_DELEGATED); + netsnmp_set_request_error(cache->reqinfo, requests, /* XXXWWW: should be index=0 */ + SNMP_ERR_GENERR); + netsnmp_free_delegated_cache(cache); + return 0; + + case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: + /* + * This session is alive + */ + CLEAR_SNMP_STRIKE_FLAGS(session->flags); + break; + default: + snmp_log(LOG_ERR, "Unknown operation %d in agentx_got_response\n", + operation); + netsnmp_free_delegated_cache(cache); + return 0; + } + + DEBUGMSGTL(("agentx/master", "got response errstat=%ld, (req=0x%x,trans=" + "0x%x,sess=0x%x)\n", + pdu->errstat, (unsigned)pdu->reqid, (unsigned)pdu->transid, + (unsigned)pdu->sessid)); + + if (pdu->errstat != AGENTX_ERR_NOERROR) { + /* [RFC 2471 - 7.2.5.2.] + * + * 1) For any received AgentX response PDU, if res.error is + * not `noError', the SNMP response PDU's error code is + * set to this value. If res.error contains an AgentX + * specific value (e.g. `parseError'), the SNMP response + * PDU's error code is set to a value of genErr instead. + * Also, the SNMP response PDU's error index is set to + * the index of the variable binding corresponding to the + * failed VarBind in the subagent's AgentX response PDU. + * + * All other AgentX response PDUs received due to + * processing this SNMP request are ignored. Processing + * is complete; the SNMP Response PDU is ready to be sent + * (see section 7.2.6, "Sending the SNMP Response-PDU"). + */ + int err; + + DEBUGMSGTL(("agentx/master", + "agentx_got_response() error branch\n")); + + switch (pdu->errstat) { + case AGENTX_ERR_PARSE_FAILED: + case AGENTX_ERR_REQUEST_DENIED: + case AGENTX_ERR_PROCESSING_ERROR: + err = SNMP_ERR_GENERR; + break; + default: + err = pdu->errstat; + } + + ret = 0; + for (request = requests, i = 1; request; + request = request->next, i++) { + if (i == pdu->errindex) { + /* + * Mark this varbind as the one generating the error. + * Note that the AgentX errindex may not match the + * position in the original SNMP PDU (request->index) + */ + netsnmp_set_request_error(cache->reqinfo, request, + err); + ret = 1; + } + request->delegated = REQUEST_IS_NOT_DELEGATED; + } + if (!ret) { + /* + * ack, unknown, mark the first one + */ + netsnmp_set_request_error(cache->reqinfo, requests, + SNMP_ERR_GENERR); + } + netsnmp_free_delegated_cache(cache); + DEBUGMSGTL(("agentx/master", "end error branch\n")); + return 1; + } else if (cache->reqinfo->mode == MODE_GET || + cache->reqinfo->mode == MODE_GETNEXT || + cache->reqinfo->mode == MODE_GETBULK) { + /* + * Replace varbinds for data request types, but not SETs. + */ + DEBUGMSGTL(("agentx/master", + "agentx_got_response() beginning...\n")); + for (var = pdu->variables, request = requests; request && var; + request = request->next, var = var->next_variable) { + /* + * Otherwise, process successful requests + */ + DEBUGMSGTL(("agentx/master", + " handle_agentx_response: processing: ")); + DEBUGMSGOID(("agentx/master", var->name, var->name_length)); + DEBUGMSG(("agentx/master", "\n")); + if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_VERBOSE)) { + DEBUGMSGTL(("agentx/master", " >> ")); + DEBUGMSGVAR(("agentx/master", var)); + DEBUGMSG(("agentx/master", "\n")); + } + + /* + * update the oid in the original request + */ + if (var->type != SNMP_ENDOFMIBVIEW) { + snmp_set_var_typed_value(request->requestvb, var->type, + var->val.string, var->val_len); + snmp_set_var_objid(request->requestvb, var->name, + var->name_length); + } + request->delegated = REQUEST_IS_NOT_DELEGATED; + } + + if (request || var) { + /* + * ack, this is bad. The # of varbinds don't match and + * there is no way to fix the problem + */ + snmp_log(LOG_ERR, + "response to agentx request illegal. bailing out.\n"); + netsnmp_set_request_error(cache->reqinfo, requests, + SNMP_ERR_GENERR); + } + + if (cache->reqinfo->mode == MODE_GETBULK) + netsnmp_bulk_to_next_fix_requests(requests); + } else { + /* + * mark set requests as handled + */ + for (request = requests; request; request = request->next) { + request->delegated = REQUEST_IS_NOT_DELEGATED; + } + } + DEBUGMSGTL(("agentx/master", + "handle_agentx_response() finishing...\n")); + netsnmp_free_delegated_cache(cache); + return 1; +} + +/* + * + * AgentX State diagram. [mode] = internal mode it's mapped from: + * + * TESTSET -success-> COMMIT -success-> CLEANUP + * [RESERVE1] [ACTION] [COMMIT] + * | | + * | \--failure-> UNDO + * | [UNDO] + * | + * --failure-> CLEANUP + * [FREE] + */ +int +agentx_master_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + netsnmp_session *ax_session = (netsnmp_session *) handler->myvoid; + netsnmp_request_info *request = requests; + netsnmp_pdu *pdu; + void *cb_data; + int result; + + DEBUGMSGTL(("agentx/master", + "agentx master handler starting, mode = 0x%02x\n", + reqinfo->mode)); + + if (!ax_session) { + netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR); + return SNMP_ERR_NOERROR; + } + + /* + * build a new pdu based on the pdu type coming in + */ + switch (reqinfo->mode) { + case MODE_GET: + pdu = snmp_pdu_create(AGENTX_MSG_GET); + break; + + case MODE_GETNEXT: + pdu = snmp_pdu_create(AGENTX_MSG_GETNEXT); + break; + + case MODE_GETBULK: /* WWWXXX */ + pdu = snmp_pdu_create(AGENTX_MSG_GETNEXT); + break; + +#ifndef NETSNMP_NO_WRITE_SUPPORT + case MODE_SET_RESERVE1: + pdu = snmp_pdu_create(AGENTX_MSG_TESTSET); + break; + + case MODE_SET_RESERVE2: + /* + * don't do anything here for AgentX. Assume all is fine + * and go on since AgentX only has one test phase. + */ + return SNMP_ERR_NOERROR; + + case MODE_SET_ACTION: + pdu = snmp_pdu_create(AGENTX_MSG_COMMITSET); + break; + + case MODE_SET_UNDO: + pdu = snmp_pdu_create(AGENTX_MSG_UNDOSET); + break; + + case MODE_SET_COMMIT: + case MODE_SET_FREE: + pdu = snmp_pdu_create(AGENTX_MSG_CLEANUPSET); + break; +#endif /* !NETSNMP_NO_WRITE_SUPPORT */ + + default: + snmp_log(LOG_WARNING, + "unsupported mode for agentx/master called\n"); + return SNMP_ERR_NOERROR; + } + + if (!pdu) { + netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR); + return SNMP_ERR_NOERROR; + } + + pdu->version = AGENTX_VERSION_1; + pdu->reqid = snmp_get_next_transid(); + pdu->transid = reqinfo->asp->pdu->transid; + pdu->sessid = ax_session->subsession->sessid; + if (reginfo->contextName) { + pdu->community = (u_char *) strdup(reginfo->contextName); + pdu->community_len = strlen(reginfo->contextName); + pdu->flags |= AGENTX_MSG_FLAG_NON_DEFAULT_CONTEXT; + } + if (ax_session->subsession->flags & AGENTX_MSG_FLAG_NETWORK_BYTE_ORDER) + pdu->flags |= AGENTX_MSG_FLAG_NETWORK_BYTE_ORDER; + + while (request) { + + size_t nlen = request->requestvb->name_length; + oid *nptr = request->requestvb->name; + + DEBUGMSGTL(("agentx/master","request for variable (")); + DEBUGMSGOID(("agentx/master", nptr, nlen)); + DEBUGMSG(("agentx/master", ")\n")); + + /* + * loop through all the requests and create agentx ones out of them + */ + + if (reqinfo->mode == MODE_GETNEXT || reqinfo->mode == MODE_GETBULK) { + + if (snmp_oid_compare(nptr, nlen, request->subtree->start_a, + request->subtree->start_len) < 0) { + DEBUGMSGTL(("agentx/master","inexact request preceding region (")); + DEBUGMSGOID(("agentx/master", request->subtree->start_a, + request->subtree->start_len)); + DEBUGMSG(("agentx/master", ")\n")); + nptr = request->subtree->start_a; + nlen = request->subtree->start_len; + request->inclusive = 1; + } + + if (request->inclusive) { + DEBUGMSGTL(("agentx/master", "INCLUSIVE varbind ")); + DEBUGMSGOID(("agentx/master", nptr, nlen)); + DEBUGMSG(("agentx/master", " scoped to ")); + DEBUGMSGOID(("agentx/master", request->range_end, + request->range_end_len)); + DEBUGMSG(("agentx/master", "\n")); + snmp_pdu_add_variable(pdu, nptr, nlen, ASN_PRIV_INCL_RANGE, + (u_char *) request->range_end, + request->range_end_len * + sizeof(oid)); + request->inclusive = 0; + } else { + DEBUGMSGTL(("agentx/master", "EXCLUSIVE varbind ")); + DEBUGMSGOID(("agentx/master", nptr, nlen)); + DEBUGMSG(("agentx/master", " scoped to ")); + DEBUGMSGOID(("agentx/master", request->range_end, + request->range_end_len)); + DEBUGMSG(("agentx/master", "\n")); + snmp_pdu_add_variable(pdu, nptr, nlen, ASN_PRIV_EXCL_RANGE, + (u_char *) request->range_end, + request->range_end_len * + sizeof(oid)); + } + } else { + snmp_pdu_add_variable(pdu, request->requestvb->name, + request->requestvb->name_length, + request->requestvb->type, + request->requestvb->val.string, + request->requestvb->val_len); + } + + /* + * mark the request as delayed + */ + if (pdu->command != AGENTX_MSG_CLEANUPSET) + request->delegated = REQUEST_IS_DELEGATED; + else + request->delegated = REQUEST_IS_NOT_DELEGATED; + + /* + * next... + */ + request = request->next; + } + + /* + * When the master sends a CleanupSet PDU, it will never get a response + * back from the subagent. So we shouldn't allocate the + * netsnmp_delegated_cache structure in this case. + */ + if (pdu->command != AGENTX_MSG_CLEANUPSET) + cb_data = netsnmp_create_delegated_cache(handler, reginfo, + reqinfo, requests, + (void *) ax_session); + else + cb_data = NULL; + + /* + * send the requests out. + */ + DEBUGMSGTL(("agentx/master", "sending pdu (req=0x%x,trans=0x%x,sess=0x%x)\n", + (unsigned)pdu->reqid, (unsigned)pdu->transid, (unsigned)pdu->sessid)); + result = snmp_async_send(ax_session, pdu, agentx_got_response, cb_data); + if (result == 0) { + snmp_free_pdu(pdu); + if (cb_data) + netsnmp_free_delegated_cache((netsnmp_delegated_cache*) cb_data); + } + + return SNMP_ERR_NOERROR; +} diff --git a/snmplib/snmp_api.c b/snmplib/snmp_api.c index 554767a..bc1c7ae 100644 --- a/snmplib/snmp_api.c +++ b/snmplib/snmp_api.c @@ -352,6 +352,7 @@ static int snmpv3_build(u_char ** pkt, size_t * pkt_len, netsnmp_pdu *pdu); static int snmp_parse_version(u_char *, size_t); static int snmp_resend_request(struct session_list *slp, + netsnmp_request_list *orp, netsnmp_request_list *rp, int incr_retries); static void register_default_handlers(void); @@ -5717,7 +5718,7 @@ _sess_process_packet_handle_pdu(void *sessp, netsnmp_session * sp, * * inifinite resend */ if (rp->retries <= sp->retries) { - snmp_resend_request(slp, rp, TRUE); + snmp_resend_request(slp, orp, rp, TRUE); break; } else { /* We're done with retries, so no longer waiting for a response */ @@ -6662,9 +6663,22 @@ snmp_timeout(void) snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); } +static void +remove_request(struct snmp_internal_session *isp, + netsnmp_request_list *orp, netsnmp_request_list *rp) +{ + if (orp) + orp->next_request = rp->next_request; + else + isp->requests = rp->next_request; + if (isp->requestsEnd == rp) + isp->requestsEnd = orp; + snmp_free_pdu(rp->pdu); +} + static int -snmp_resend_request(struct session_list *slp, netsnmp_request_list *rp, - int incr_retries) +snmp_resend_request(struct session_list *slp, netsnmp_request_list *orp, + netsnmp_request_list *rp, int incr_retries) { struct snmp_internal_session *isp; netsnmp_session *sp; @@ -6731,9 +6745,11 @@ snmp_resend_request(struct session_list *slp, netsnmp_request_list *rp, sp->s_snmp_errno = SNMPERR_BAD_SENDTO; sp->s_errno = errno; snmp_set_detail(strerror(errno)); - if (rp->callback) + if (rp->callback) { rp->callback(NETSNMP_CALLBACK_OP_SEND_FAILED, sp, rp->pdu->reqid, rp->pdu, rp->cb_data); + remove_request(isp, orp, rp); + } return -1; } else { netsnmp_get_monotonic_clock(&now); @@ -6813,19 +6829,12 @@ snmp_sess_timeout(void *sessp) callback(NETSNMP_CALLBACK_OP_TIMED_OUT, sp, rp->pdu->reqid, rp->pdu, magic); } - if (orp) - orp->next_request = rp->next_request; - else - isp->requests = rp->next_request; - if (isp->requestsEnd == rp) - isp->requestsEnd = orp; - snmp_free_pdu(rp->pdu); + remove_request(isp, orp, rp); freeme = rp; continue; /* don't update orp below */ } else { - if (snmp_resend_request(slp, rp, TRUE)) { + if (snmp_resend_request(slp, orp, rp, TRUE)) break; - } } } orp = rp; diff --git a/snmplib/snmp_api.c.agent-of-death b/snmplib/snmp_api.c.agent-of-death new file mode 100644 index 0000000..554767a --- /dev/null +++ b/snmplib/snmp_api.c.agent-of-death @@ -0,0 +1,7909 @@ + +/* 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: + */ +/****************************************************************** + Copyright 1989, 1991, 1992 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 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. + * + * Portions of this file are copyrighted by: + * Copyright (c) 2016 VMware, Inc. All rights reserved. + * Use is subject to license terms specified in the COPYING file + * distributed with the Net-SNMP package. + */ + +/** @defgroup library The Net-SNMP library + * @{ + */ +/* + * snmp_api.c - API for access to snmp. + */ +#include +#include + +#include +#include +#if HAVE_STDLIB_H +#include +#endif +#if HAVE_STRING_H +#include +#else +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#include +#if HAVE_SYS_PARAM_H +#include +#endif +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +#if HAVE_NETINET_IN_H +#include +#endif +#if HAVE_ARPA_INET_H +#include +#endif +#if HAVE_SYS_SELECT_H +#include +#endif +#if HAVE_IO_H +#include +#endif +#if HAVE_SYS_SOCKET_H +#include +#endif +#if HAVE_SYS_UN_H +#include +#endif +#if HAVE_NETDB_H +#include +#endif +#if HAVE_NET_IF_DL_H +#ifndef dynix +#include +#else +#include +#endif +#endif +#include + +#if HAVE_LOCALE_H +#include +#endif + +#if HAVE_DMALLOC_H +#include +#endif + +#define SNMP_NEED_REQUEST_LIST +#include +#include +#include +#include + +#include +#include /* for xdump & {build,parse}_var_op */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef NETSNMP_SECMOD_USM +#include +#endif +#ifdef NETSNMP_SECMOD_KSM +#include +#endif +#include +#include +#include +#include +#include +#include +#if defined(NETSNMP_USE_OPENSSL) && defined(HAVE_LIBSSL) +#include +#include +#endif + +netsnmp_feature_child_of(statistics, libnetsnmp) +netsnmp_feature_child_of(snmp_api, libnetsnmp) +netsnmp_feature_child_of(oid_is_subtree, snmp_api) +netsnmp_feature_child_of(snmpv3_probe_contextEngineID_rfc5343, snmp_api) + +static void _init_snmp(void); + +static int _snmp_store_needed = 0; + +#include "../agent/mibgroup/agentx/protocol.h" +#include +#ifndef timercmp +#define timercmp(tvp, uvp, cmp) \ + /* CSTYLED */ \ + ((tvp)->tv_sec cmp (uvp)->tv_sec || \ + ((tvp)->tv_sec == (uvp)->tv_sec && \ + /* CSTYLED */ \ + (tvp)->tv_usec cmp (uvp)->tv_usec)) +#endif +#ifndef timerclear +#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0 +#endif + +/* + * Globals. + */ +#ifndef NETSNMP_STREAM_QUEUE_LEN +#define NETSNMP_STREAM_QUEUE_LEN 5 +#endif + +#ifndef BSD4_3 +#define BSD4_2 +#endif + +static oid default_enterprise[] = { 1, 3, 6, 1, 4, 1, 3, 1, 1 }; +/* + * enterprises.cmu.systems.cmuSNMP + */ + +#define DEFAULT_COMMUNITY "public" +#define DEFAULT_RETRIES 5 +#define DEFAULT_TIMEOUT ONE_SEC +#define DEFAULT_REMPORT SNMP_PORT +#define DEFAULT_ENTERPRISE default_enterprise +#define DEFAULT_TIME 0 + +/* + * Internal information about the state of the snmp session. + */ +struct snmp_internal_session { + netsnmp_request_list *requests; /* Info about outstanding requests */ + netsnmp_request_list *requestsEnd; /* ptr to end of list */ + int (*hook_pre) (netsnmp_session *, netsnmp_transport *, + void *, int); + int (*hook_parse) (netsnmp_session *, netsnmp_pdu *, + u_char *, size_t); + int (*hook_post) (netsnmp_session *, netsnmp_pdu *, int); + int (*hook_build) (netsnmp_session *, netsnmp_pdu *, + u_char *, size_t *); + int (*hook_realloc_build) (netsnmp_session *, + netsnmp_pdu *, u_char **, + size_t *, size_t *); + int (*check_packet) (u_char *, size_t); + netsnmp_pdu *(*hook_create_pdu) (netsnmp_transport *, + void *, size_t); + + u_char *packet; /* curr rcv packet data (may be incomplete) */ + size_t packet_len; /* length of data received so far */ + size_t packet_size; /* size of buffer for packet data */ + + u_char *obuf; /* send packet buffer */ + size_t obuf_size; /* size of buffer for packet data */ + u_char *opacket; /* send packet data (within obuf) */ + size_t opacket_len; /* length of data */ +}; + +/* + * information about received packet + */ +typedef struct snmp_rcv_packet_s { + u_char *packet; + size_t packet_len; + void *opaque; + int olength; +} snmp_rcv_packet; + +static const char *api_errors[-SNMPERR_MAX + 1] = { + "No error", /* SNMPERR_SUCCESS */ + "Generic error", /* SNMPERR_GENERR */ + "Invalid local port", /* SNMPERR_BAD_LOCPORT */ + "Unknown host", /* SNMPERR_BAD_ADDRESS */ + "Unknown session", /* SNMPERR_BAD_SESSION */ + "Too long", /* SNMPERR_TOO_LONG */ + "No socket", /* SNMPERR_NO_SOCKET */ + "Cannot send V2 PDU on V1 session", /* SNMPERR_V2_IN_V1 */ + "Cannot send V1 PDU on V2 session", /* SNMPERR_V1_IN_V2 */ + "Bad value for non-repeaters", /* SNMPERR_BAD_REPEATERS */ + "Bad value for max-repetitions", /* SNMPERR_BAD_REPETITIONS */ + "Error building ASN.1 representation", /* SNMPERR_BAD_ASN1_BUILD */ + "Failure in sendto", /* SNMPERR_BAD_SENDTO */ + "Bad parse of ASN.1 type", /* SNMPERR_BAD_PARSE */ + "Bad version specified", /* SNMPERR_BAD_VERSION */ + "Bad source party specified", /* SNMPERR_BAD_SRC_PARTY */ + "Bad destination party specified", /* SNMPERR_BAD_DST_PARTY */ + "Bad context specified", /* SNMPERR_BAD_CONTEXT */ + "Bad community specified", /* SNMPERR_BAD_COMMUNITY */ + "Cannot send noAuth/Priv", /* SNMPERR_NOAUTH_DESPRIV */ + "Bad ACL definition", /* SNMPERR_BAD_ACL */ + "Bad Party definition", /* SNMPERR_BAD_PARTY */ + "Session abort failure", /* SNMPERR_ABORT */ + "Unknown PDU type", /* SNMPERR_UNKNOWN_PDU */ + "Timeout", /* SNMPERR_TIMEOUT */ + "Failure in recvfrom", /* SNMPERR_BAD_RECVFROM */ + "Unable to determine contextEngineID", /* SNMPERR_BAD_ENG_ID */ + "No securityName specified", /* SNMPERR_BAD_SEC_NAME */ + "Unable to determine securityLevel", /* SNMPERR_BAD_SEC_LEVEL */ + "ASN.1 parse error in message", /* SNMPERR_ASN_PARSE_ERR */ + "Unknown security model in message", /* SNMPERR_UNKNOWN_SEC_MODEL */ + "Invalid message (e.g. msgFlags)", /* SNMPERR_INVALID_MSG */ + "Unknown engine ID", /* SNMPERR_UNKNOWN_ENG_ID */ + "Unknown user name", /* SNMPERR_UNKNOWN_USER_NAME */ + "Unsupported security level", /* SNMPERR_UNSUPPORTED_SEC_LEVEL */ + "Authentication failure (incorrect password, community or key)", /* SNMPERR_AUTHENTICATION_FAILURE */ + "Not in time window", /* SNMPERR_NOT_IN_TIME_WINDOW */ + "Decryption error", /* SNMPERR_DECRYPTION_ERR */ + "SCAPI general failure", /* SNMPERR_SC_GENERAL_FAILURE */ + "SCAPI sub-system not configured", /* SNMPERR_SC_NOT_CONFIGURED */ + "Key tools not available", /* SNMPERR_KT_NOT_AVAILABLE */ + "Unknown Report message", /* SNMPERR_UNKNOWN_REPORT */ + "USM generic error", /* SNMPERR_USM_GENERICERROR */ + "USM unknown security name (no such user exists)", /* SNMPERR_USM_UNKNOWNSECURITYNAME */ + "USM unsupported security level (this user has not been configured for that level of security)", /* SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL */ + "USM encryption error", /* SNMPERR_USM_ENCRYPTIONERROR */ + "USM authentication failure (incorrect password or key)", /* SNMPERR_USM_AUTHENTICATIONFAILURE */ + "USM parse error", /* SNMPERR_USM_PARSEERROR */ + "USM unknown engineID", /* SNMPERR_USM_UNKNOWNENGINEID */ + "USM not in time window", /* SNMPERR_USM_NOTINTIMEWINDOW */ + "USM decryption error", /* SNMPERR_USM_DECRYPTIONERROR */ + "MIB not initialized", /* SNMPERR_NOMIB */ + "Value out of range", /* SNMPERR_RANGE */ + "Sub-id out of range", /* SNMPERR_MAX_SUBID */ + "Bad sub-id in object identifier", /* SNMPERR_BAD_SUBID */ + "Object identifier too long", /* SNMPERR_LONG_OID */ + "Bad value name", /* SNMPERR_BAD_NAME */ + "Bad value notation", /* SNMPERR_VALUE */ + "Unknown Object Identifier", /* SNMPERR_UNKNOWN_OBJID */ + "No PDU in snmp_send", /* SNMPERR_NULL_PDU */ + "Missing variables in PDU", /* SNMPERR_NO_VARS */ + "Bad variable type", /* SNMPERR_VAR_TYPE */ + "Out of memory (malloc failure)", /* SNMPERR_MALLOC */ + "Kerberos related error", /* SNMPERR_KRB5 */ + "Protocol error", /* SNMPERR_PROTOCOL */ + "OID not increasing", /* SNMPERR_OID_NONINCREASING */ + "Context probe", /* SNMPERR_JUST_A_CONTEXT_PROBE */ + "Configuration data found but the transport can't be configured", /* SNMPERR_TRANSPORT_NO_CONFIG */ + "Transport configuration failed", /* SNMPERR_TRANSPORT_CONFIG_ERROR */ +}; + +static const char *secLevelName[] = { + "BAD_SEC_LEVEL", + "noAuthNoPriv", + "authNoPriv", + "authPriv" +}; + +/* + * Multiple threads may changes these variables. + * Suggest using the Single API, which does not use Sessions. + * + * Reqid may need to be protected. Time will tell... + * + */ +/* + * MTCRITICAL_RESOURCE + */ +/* + * use token in comments to individually protect these resources + */ +struct session_list *Sessions = NULL; /* MT_LIB_SESSION */ +static long Reqid = 0; /* MT_LIB_REQUESTID */ +static long Msgid = 0; /* MT_LIB_MESSAGEID */ +static long Sessid = 0; /* MT_LIB_SESSIONID */ +static long Transid = 0; /* MT_LIB_TRANSID */ +int snmp_errno = 0; +/* + * END MTCRITICAL_RESOURCE + */ + +/* + * global error detail storage + */ +static char snmp_detail[192]; +static int snmp_detail_f = 0; + +/* + * Prototypes. + */ +static int snmp_parse(void *, netsnmp_session *, netsnmp_pdu *, + u_char *, size_t); + +static void snmpv3_calc_msg_flags(int, int, u_char *); +static int snmpv3_verify_msg(netsnmp_request_list *, netsnmp_pdu *); +static int snmpv3_build(u_char ** pkt, size_t * pkt_len, + size_t * offset, netsnmp_session * session, + netsnmp_pdu *pdu); +static int snmp_parse_version(u_char *, size_t); +static int snmp_resend_request(struct session_list *slp, + netsnmp_request_list *rp, + int incr_retries); +static void register_default_handlers(void); +static struct session_list *snmp_sess_copy(netsnmp_session * pss); + +/* + * return configured max message size for outgoing packets + */ +int +netsnmp_max_send_msg_size(void) +{ + u_int max = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_MSG_SEND_MAX); + if (0 == max) + max = SNMP_MAX_PACKET_LEN; + else if (max < SNMP_MIN_MAX_LEN) + max = SNMP_MIN_MAX_LEN; /* minimum max size per SNMP specs */ + else if (max > SNMP_MAX_PACKET_LEN) + max = SNMP_MAX_PACKET_LEN; + + return max; +} + +#ifndef HAVE_STRERROR +const char * +strerror(int err) +{ + extern const char *sys_errlist[]; + extern int sys_nerr; + + if (err < 0 || err >= sys_nerr) + return "Unknown error"; + return sys_errlist[err]; +} +#endif + +const char * +snmp_pdu_type(int type) +{ + static char unknown[20]; + switch(type) { + case SNMP_MSG_GET: + return "GET"; + case SNMP_MSG_GETNEXT: + return "GETNEXT"; + case SNMP_MSG_GETBULK: + return "GETBULK"; +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: + return "SET"; +#endif /* !NETSNMP_NO_WRITE_SUPPORT */ + case SNMP_MSG_RESPONSE: + return "RESPONSE"; + case SNMP_MSG_INFORM: + return "INFORM"; + case SNMP_MSG_TRAP2: + return "TRAP2"; + case SNMP_MSG_REPORT: + return "REPORT"; + default: + snprintf(unknown, sizeof(unknown), "?0x%2X?", type); + return unknown; + } +} + +#define DEBUGPRINTPDUTYPE(token, type) \ + DEBUGDUMPSECTION(token, snmp_pdu_type(type)) + +long +snmp_get_next_reqid(void) +{ + long retVal; + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_REQUESTID); + retVal = 1 + Reqid; /*MTCRITICAL_RESOURCE */ + if (!retVal) + retVal = 2; + Reqid = retVal; + if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_16BIT_IDS)) + retVal &= 0x7fff; /* mask to 15 bits */ + else + retVal &= 0x7fffffff; /* mask to 31 bits */ + + if (!retVal) { + Reqid = retVal = 2; + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_REQUESTID); + return retVal; +} + +long +snmp_get_next_msgid(void) +{ + long retVal; + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_MESSAGEID); + retVal = 1 + Msgid; /*MTCRITICAL_RESOURCE */ + if (!retVal) + retVal = 2; + Msgid = retVal; + if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_16BIT_IDS)) + retVal &= 0x7fff; /* mask to 15 bits */ + else + retVal &= 0x7fffffff; /* mask to 31 bits */ + + if (!retVal) { + Msgid = retVal = 2; + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_MESSAGEID); + return retVal; +} + +long +snmp_get_next_sessid(void) +{ + long retVal; + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSIONID); + retVal = 1 + Sessid; /*MTCRITICAL_RESOURCE */ + if (!retVal) + retVal = 2; + Sessid = retVal; + if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_16BIT_IDS)) + retVal &= 0x7fff; /* mask to 15 bits */ + else + retVal &= 0x7fffffff; /* mask to 31 bits */ + + if (!retVal) { + Sessid = retVal = 2; + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSIONID); + return retVal; +} + +long +snmp_get_next_transid(void) +{ + long retVal; + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_TRANSID); + retVal = 1 + Transid; /*MTCRITICAL_RESOURCE */ + if (!retVal) + retVal = 2; + Transid = retVal; + if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_16BIT_IDS)) + retVal &= 0x7fff; /* mask to 15 bits */ + else + retVal &= 0x7fffffff; /* mask to 31 bits */ + + if (!retVal) { + Transid = retVal = 2; + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_TRANSID); + return retVal; +} + +void +snmp_perror(const char *prog_string) +{ + const char *str; + int xerr; + xerr = snmp_errno; /*MTCRITICAL_RESOURCE */ + str = snmp_api_errstring(xerr); + snmp_log(LOG_ERR, "%s: %s\n", prog_string, str); +} + +void +snmp_set_detail(const char *detail_string) +{ + if (detail_string != NULL) { + strlcpy((char *) snmp_detail, detail_string, sizeof(snmp_detail)); + snmp_detail_f = 1; + } +} + +/* + * returns pointer to static data + */ +/* + * results not guaranteed in multi-threaded use + */ +const char * +snmp_api_errstring(int snmp_errnumber) +{ + const char *msg = ""; + static char msg_buf[SPRINT_MAX_LEN]; + + if (snmp_errnumber >= SNMPERR_MAX && snmp_errnumber <= SNMPERR_GENERR) { + msg = api_errors[-snmp_errnumber]; + } else if (snmp_errnumber != SNMPERR_SUCCESS) { + msg = NULL; + } + if (!msg) { + snprintf(msg_buf, sizeof(msg_buf), "Unknown error: %d", snmp_errnumber); + msg_buf[sizeof(msg_buf)-1] = '\0'; + } else if (snmp_detail_f) { + snprintf(msg_buf, sizeof(msg_buf), "%s (%s)", msg, snmp_detail); + msg_buf[sizeof(msg_buf)-1] = '\0'; + snmp_detail_f = 0; + } else { + strlcpy(msg_buf, msg, sizeof(msg_buf)); + } + + return (msg_buf); +} + +/* + * snmp_error - return error data + * Inputs : address of errno, address of snmp_errno, address of string + * Caller must free the string returned after use. + */ +void +snmp_error(netsnmp_session * psess, + int *p_errno, int *p_snmp_errno, char **p_str) +{ + char buf[SPRINT_MAX_LEN]; + int snmp_errnumber; + + if (p_errno) + *p_errno = psess->s_errno; + if (p_snmp_errno) + *p_snmp_errno = psess->s_snmp_errno; + if (p_str == NULL) + return; + + strcpy(buf, ""); + snmp_errnumber = psess->s_snmp_errno; + if (snmp_errnumber >= SNMPERR_MAX && snmp_errnumber <= SNMPERR_GENERR) { + if (snmp_detail_f) { + snprintf(buf, sizeof(buf), "%s (%s)", api_errors[-snmp_errnumber], + snmp_detail); + buf[sizeof(buf)-1] = '\0'; + snmp_detail_f = 0; + } + else + strlcpy(buf, api_errors[-snmp_errnumber], sizeof(buf)); + } else { + if (snmp_errnumber) { + snprintf(buf, sizeof(buf), "Unknown Error %d", snmp_errnumber); + buf[sizeof(buf)-1] = '\0'; + } + } + + /* + * append a useful system errno interpretation. + */ + if (psess->s_errno) { + const char* error = strerror(psess->s_errno); + if(error == NULL) + error = "Unknown Error"; + snprintf (&buf[strlen(buf)], sizeof(buf)-strlen(buf), + " (%s)", error); + } + buf[sizeof(buf)-1] = '\0'; + *p_str = strdup(buf); +} + +/* + * snmp_sess_error - same as snmp_error for single session API use. + */ +void +snmp_sess_error(void *sessp, int *p_errno, int *p_snmp_errno, char **p_str) +{ + struct session_list *slp = (struct session_list *) sessp; + + if ((slp) && (slp->session)) + snmp_error(slp->session, p_errno, p_snmp_errno, p_str); +} + +/* + * netsnmp_sess_log_error(): print a error stored in a session pointer + */ +void +netsnmp_sess_log_error(int priority, + const char *prog_string, netsnmp_session * ss) +{ + char *err; + snmp_error(ss, NULL, NULL, &err); + snmp_log(priority, "%s: %s\n", prog_string, err); + SNMP_FREE(err); +} + +/* + * snmp_sess_perror(): print a error stored in a session pointer + */ +void +snmp_sess_perror(const char *prog_string, netsnmp_session * ss) +{ + netsnmp_sess_log_error(LOG_ERR, prog_string, ss); +} + +long int netsnmp_random(void) +{ +#if defined(HAVE_RANDOM) + return random(); +#elif defined(HAVE_LRAND48) + return lrand48(); +#elif defined(HAVE_RAND) + return rand(); +#else +#error "Neither random(), nor lrand48() nor rand() are available" +#endif +} + +void netsnmp_srandom(unsigned int seed) +{ +#if defined(HAVE_SRANDOM) + srandom(seed); +#elif defined(HAVE_SRAND48) + srand48(seed); +#elif defined(HAVE_SRAND) + srand(seed); +#else +#error "Neither srandom(), nor srand48() nor srand() are available" +#endif +} + +/* + * Primordial SNMP library initialization. + * Initializes mutex locks. + * Invokes minimum required initialization for displaying MIB objects. + * Gets initial request ID for all transactions, + * and finds which port SNMP over UDP uses. + * SNMP over AppleTalk is not currently supported. + * + * Warning: no debug messages here. + */ +static char _init_snmp_init_done = 0; +static void +_init_snmp(void) +{ + + struct timeval tv; + long tmpReqid, tmpMsgid; + + if (_init_snmp_init_done) + return; + _init_snmp_init_done = 1; + Reqid = 1; + + snmp_res_init(); /* initialize the mt locking structures */ +#ifndef NETSNMP_DISABLE_MIB_LOADING + netsnmp_init_mib_internals(); +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + netsnmp_tdomain_init(); + + gettimeofday(&tv, (struct timezone *) 0); + /* + * Now = tv; + */ + + /* + * get pseudo-random values for request ID and message ID + */ + netsnmp_srandom((unsigned)(tv.tv_sec ^ tv.tv_usec)); + tmpReqid = netsnmp_random(); + tmpMsgid = netsnmp_random(); + + /* + * don't allow zero value to repeat init + */ + if (tmpReqid == 0) + tmpReqid = 1; + if (tmpMsgid == 0) + tmpMsgid = 1; + Reqid = tmpReqid; + Msgid = tmpMsgid; + + netsnmp_register_default_domain("snmp", "udp udp6"); + netsnmp_register_default_domain("snmptrap", "udp udp6"); + + netsnmp_register_default_target("snmp", "udp", ":161"); + netsnmp_register_default_target("snmp", "tcp", ":161"); + netsnmp_register_default_target("snmp", "udp6", ":161"); + netsnmp_register_default_target("snmp", "tcp6", ":161"); + netsnmp_register_default_target("snmp", "dtlsudp", ":10161"); + netsnmp_register_default_target("snmp", "tlstcp", ":10161"); + netsnmp_register_default_target("snmp", "ipx", "/36879"); + + netsnmp_register_default_target("snmptrap", "udp", ":162"); + netsnmp_register_default_target("snmptrap", "tcp", ":162"); + netsnmp_register_default_target("snmptrap", "udp6", ":162"); + netsnmp_register_default_target("snmptrap", "tcp6", ":162"); + netsnmp_register_default_target("snmptrap", "dtlsudp", ":10162"); + netsnmp_register_default_target("snmptrap", "tlstcp", ":10162"); + netsnmp_register_default_target("snmptrap", "ipx", "/36880"); + + netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_HEX_OUTPUT_LENGTH, 16); + netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_RETRIES, + DEFAULT_RETRIES); + netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_MIB_ERRORS, 1); + +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_REVERSE_ENCODE, + NETSNMP_DEFAULT_ASNENCODING_DIRECTION); +#endif +} + +/* + * Initializes the session structure. + * May perform one time minimal library initialization. + * No MIB file processing is done via this call. + */ +void +snmp_sess_init(netsnmp_session * session) +{ + _init_snmp(); + + /* + * initialize session to default values + */ + + memset(session, 0, sizeof(netsnmp_session)); + session->timeout = SNMP_DEFAULT_TIMEOUT; + session->retries = SNMP_DEFAULT_RETRIES; + session->version = SNMP_DEFAULT_VERSION; + session->securityModel = SNMP_DEFAULT_SECMODEL; + session->rcvMsgMaxSize = SNMP_MAX_MSG_SIZE; + session->sndMsgMaxSize = netsnmp_max_send_msg_size(); + session->flags |= SNMP_FLAGS_DONT_PROBE; +} + + +static void +register_default_handlers(void) +{ + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "dumpPacket", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DUMP_PACKET); + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "reverseEncodeBER", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_REVERSE_ENCODE); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "defaultPort", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DEFAULT_PORT); +#ifndef NETSNMP_FEATURE_REMOVE_RUNTIME_DISABLE_VERSION + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "disableSNMPv3", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_V3); +#endif /* NETSNMP_FEATURE_REMOVE_RUNTIME_DISABLE_VERSION */ +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +#ifndef NETSNMP_FEATURE_REMOVE_RUNTIME_DISABLE_VERSION +#if !defined(NETSNMP_DISABLE_SNMPV1) + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "disableSNMPv1", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_V1); +#endif +#if !defined(NETSNMP_DISABLE_SNMPV2C) + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "disableSNMPv2c", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_V2c); +#endif +#endif /* NETSNMP_FEATURE_REMOVE_RUNTIME_DISABLE_VERSION */ + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defCommunity", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_COMMUNITY); +#endif /* !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) */ + netsnmp_ds_register_premib(ASN_BOOLEAN, "snmp", "noTokenWarnings", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_NO_TOKEN_WARNINGS); + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "noRangeCheck", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_CHECK_RANGE); + netsnmp_ds_register_premib(ASN_OCTET_STR, "snmp", "persistentDir", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PERSISTENT_DIR); + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "tempFilePattern", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_TEMP_FILE_PATTERN); + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "noDisplayHint", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_NO_DISPLAY_HINT); + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "16bitIDs", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_16BIT_IDS); + netsnmp_ds_register_premib(ASN_OCTET_STR, "snmp", "clientaddr", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_CLIENT_ADDR); + netsnmp_ds_register_premib(ASN_BOOLEAN, "snmp", "clientaddrUsesPort", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_CLIENT_ADDR_USES_PORT); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "serverSendBuf", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SERVERSENDBUF); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "serverRecvBuf", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SERVERRECVBUF); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "clientSendBuf", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_CLIENTSENDBUF); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "clientRecvBuf", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_CLIENTRECVBUF); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "sendMessageMaxSize", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_MSG_SEND_MAX); + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "noPersistentLoad", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD); + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "noPersistentSave", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_SAVE); + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", + "noContextEngineIDDiscovery", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_NO_DISCOVERY); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "timeout", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_TIMEOUT); + netsnmp_ds_register_config(ASN_INTEGER, "snmp", "retries", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_RETRIES); + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "outputPrecision", + NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OUTPUT_PRECISION); + + + netsnmp_register_service_handlers(); +} + +static int init_snmp_init_done = 0; /* To prevent double init's. */ +/** + * Calls the functions to do config file loading and mib module parsing + * in the correct order. + * + * @param type label for the config file "type" + * + * @return void + * + * @see init_agent + */ +void +init_snmp(const char *type) +{ + if (init_snmp_init_done) { + return; + } + + init_snmp_init_done = 1; + + /* + * make the type available everywhere else + */ + if (type && !netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_APPTYPE)) { + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_APPTYPE, type); + } + + _init_snmp(); + + /* + * set our current locale properly to initialize isprint() type functions + */ +#ifdef HAVE_SETLOCALE + setlocale(LC_CTYPE, ""); +#endif + + snmp_debug_init(); /* should be done first, to turn on debugging ASAP */ + netsnmp_container_init_list(); + init_callbacks(); + init_snmp_logging(); + snmp_init_statistics(); + register_mib_handlers(); + register_default_handlers(); + init_snmp_transport(); + init_snmpv3(type); + init_snmp_alarm(); + init_snmp_enum(type); + init_vacm(); +#if defined(NETSNMP_USE_OPENSSL) && defined(HAVE_LIBSSL) && NETSNMP_TRANSPORT_TLSBASE_DOMAIN + netsnmp_certs_init(); +#endif +#ifdef DNSSEC_LOCAL_VALIDATION + netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "dnssecWarnOnly", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DNSSEC_WARN_ONLY); +#endif + + read_premib_configs(); +#ifndef NETSNMP_DISABLE_MIB_LOADING + netsnmp_init_mib(); +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + + read_configs(); + +} /* end init_snmp() */ + +/** + * set a flag indicating that the persistent store needs to be saved. + */ +void +snmp_store_needed(const char *type) +{ + DEBUGMSGTL(("snmp_store", "setting needed flag...\n")); + _snmp_store_needed = 1; +} + +void +snmp_store_if_needed(void) +{ + if (0 == _snmp_store_needed) + return; + + DEBUGMSGTL(("snmp_store", "store needed...\n")); + snmp_store(netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_APPTYPE)); + _snmp_store_needed = 0; +} + +void +snmp_store(const char *type) +{ + DEBUGMSGTL(("snmp_store", "storing stuff...\n")); + snmp_save_persistent(type); + snmp_call_callbacks(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_STORE_DATA, NULL); + snmp_clean_persistent(type); +} + + +/** + * Shuts down the application, saving any needed persistent storage, + * and appropriate clean up. + * + * @param type Label for the config file "type" used + * + * @return void + */ +void +snmp_shutdown(const char *type) +{ + snmp_store(type); + snmp_call_callbacks(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SHUTDOWN, NULL); + shutdown_snmp_logging(); + snmp_alarm_unregister_all(); + snmp_close_sessions(); +#ifndef NETSNMP_DISABLE_MIB_LOADING + shutdown_mib(); +#endif /* NETSNMP_DISABLE_MIB_LOADING */ +#if defined(NETSNMP_USE_OPENSSL) && defined(HAVE_LIBSSL) && NETSNMP_TRANSPORT_TLSBASE_DOMAIN + netsnmp_certs_shutdown(); +#endif +#if !defined(NETSNMP_FEATURE_REMOVE_FILTER_SOURCE) + netsnmp_transport_filter_cleanup(); +#endif + unregister_all_config_handlers(); + netsnmp_container_free_list(); + clear_sec_mod(); + clear_snmp_enum(); + netsnmp_clear_tdomain_list(); + clear_callback(); + netsnmp_ds_shutdown(); + netsnmp_clear_default_target(); + netsnmp_clear_default_domain(); + shutdown_secmod(); + shutdown_snmp_transport(); + shutdown_data_list(); + snmp_debug_shutdown(); /* should be done last */ + + init_snmp_init_done = 0; + _init_snmp_init_done = 0; +} + +/* + * inserts session into session list + */ +void snmp_session_insert(struct session_list *slp) +{ + if (NULL == slp) + return; + + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + slp->next = Sessions; + Sessions = slp; + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); +} + +/* + * Sets up the session with the snmp_session information provided by the user. + * Then opens and binds the necessary low-level transport. A handle to the + * created session is returned (this is NOT the same as the pointer passed to + * snmp_open()). On any error, NULL is returned and snmp_errno is set to the + * appropriate error code. + */ +netsnmp_session * +snmp_open(netsnmp_session *session) +{ + struct session_list *slp; + slp = (struct session_list *) snmp_sess_open(session); + if (!slp) { + return NULL; + } + + snmp_session_insert(slp); + + return (slp->session); +} + +/* + * extended open + */ +netsnmp_feature_child_of(snmp_open_ex, netsnmp_unused) +#ifndef NETSNMP_FEATURE_REMOVE_SNMP_OPEN_EX +netsnmp_session * +snmp_open_ex(netsnmp_session *session, + int (*fpre_parse) (netsnmp_session *, netsnmp_transport *, + void *, int), + int (*fparse) (netsnmp_session *, netsnmp_pdu *, u_char *, + size_t), + int (*fpost_parse) (netsnmp_session *, netsnmp_pdu *, int), + + int (*fbuild) (netsnmp_session *, netsnmp_pdu *, u_char *, + size_t *), + int (*frbuild) (netsnmp_session *, netsnmp_pdu *, + u_char **, size_t *, size_t *), + int (*fcheck) (u_char *, size_t) + ) +{ + struct session_list *slp; + slp = (struct session_list *) snmp_sess_open(session); + if (!slp) { + return NULL; + } + slp->internal->hook_pre = fpre_parse; + slp->internal->hook_parse = fparse; + slp->internal->hook_post = fpost_parse; + slp->internal->hook_build = fbuild; + slp->internal->hook_realloc_build = frbuild; + slp->internal->check_packet = fcheck; + + snmp_session_insert(slp); + + return (slp->session); +} +#endif /* NETSNMP_FEATURE_REMOVE_SNMP_OPEN_EX */ + +static struct session_list * +_sess_copy(netsnmp_session * in_session) +{ + struct session_list *slp; + struct snmp_internal_session *isp; + netsnmp_session *session; + struct snmp_secmod_def *sptr; + char *cp; + u_char *ucp; + + in_session->s_snmp_errno = 0; + in_session->s_errno = 0; + + /* + * Copy session structure and link into list + */ + slp = (struct session_list *) calloc(1, sizeof(struct session_list)); + if (slp == NULL) { + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + + slp->transport = NULL; + + isp = (struct snmp_internal_session *)calloc(1, sizeof(struct snmp_internal_session)); + + if (isp == NULL) { + snmp_sess_close(slp); + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + + slp->internal = isp; + slp->session = (netsnmp_session *)malloc(sizeof(netsnmp_session)); + if (slp->session == NULL) { + snmp_sess_close(slp); + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + memmove(slp->session, in_session, sizeof(netsnmp_session)); + session = slp->session; + + /* + * zero out pointers so if we have to free the session we wont free mem + * owned by in_session + */ + session->localname = NULL; + session->peername = NULL; + session->community = NULL; + session->contextEngineID = NULL; + session->contextName = NULL; + session->securityEngineID = NULL; + session->securityName = NULL; + session->securityAuthProto = NULL; + session->securityPrivProto = NULL; + /* + * session now points to the new structure that still contains pointers to + * data allocated elsewhere. Some of this data is copied to space malloc'd + * here, and the pointer replaced with the new one. + */ + + if (in_session->peername != NULL) { + session->peername = + netsnmp_strdup_and_null((u_char*)in_session->peername, + strlen(in_session->peername)); + if (session->peername == NULL) { + snmp_sess_close(slp); + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + } + + /* + * Fill in defaults if necessary + */ +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) + if (in_session->community_len != SNMP_DEFAULT_COMMUNITY_LEN) { + ucp = (u_char *) malloc(in_session->community_len); + if (ucp != NULL) + memmove(ucp, in_session->community, in_session->community_len); + } else { + if ((cp = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_COMMUNITY)) != NULL) { + session->community_len = strlen(cp); + ucp = (u_char *) malloc(session->community_len); + if (ucp) + memmove(ucp, cp, session->community_len); + } else { +#ifdef NETSNMP_NO_ZEROLENGTH_COMMUNITY + session->community_len = strlen(DEFAULT_COMMUNITY); + ucp = (u_char *) malloc(session->community_len); + if (ucp) + memmove(ucp, DEFAULT_COMMUNITY, session->community_len); +#else + ucp = (u_char *) strdup(""); +#endif + } + } + + if (ucp == NULL) { + snmp_sess_close(slp); + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + session->community = ucp; /* replace pointer with pointer to new data */ +#endif + + if (session->securityLevel <= 0) { + session->securityLevel = + netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SECLEVEL); + } + + if (in_session->securityEngineIDLen > 0) { + ucp = (u_char *) malloc(in_session->securityEngineIDLen); + if (ucp == NULL) { + snmp_sess_close(slp); + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + memmove(ucp, in_session->securityEngineID, + in_session->securityEngineIDLen); + session->securityEngineID = ucp; + + } + + if (in_session->contextEngineIDLen > 0) { + ucp = (u_char *) malloc(in_session->contextEngineIDLen); + if (ucp == NULL) { + snmp_sess_close(slp); + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + memmove(ucp, in_session->contextEngineID, + in_session->contextEngineIDLen); + session->contextEngineID = ucp; + } else if (in_session->securityEngineIDLen > 0) { + /* + * default contextEngineID to securityEngineIDLen if defined + */ + ucp = (u_char *) malloc(in_session->securityEngineIDLen); + if (ucp == NULL) { + snmp_sess_close(slp); + in_session->s_snmp_errno = SNMPERR_MALLOC; + return (NULL); + } + memmove(ucp, in_session->securityEngineID, + in_session->securityEngineIDLen); + session->contextEngineID = ucp; + session->contextEngineIDLen = in_session->securityEngineIDLen; + } + + if (in_session->contextName) { + session->contextName = strdup(in_session->contextName); + if (session->contextName == NULL) { + snmp_sess_close(slp); + return (NULL); + } + session->contextNameLen = in_session->contextNameLen; + } else { + if ((cp = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CONTEXT)) != NULL) + cp = strdup(cp); + else + cp = strdup(SNMP_DEFAULT_CONTEXT); + if (cp == NULL) { + snmp_sess_close(slp); + return (NULL); + } + session->contextName = cp; + session->contextNameLen = strlen(cp); + } + + if (in_session->securityName) { + session->securityName = strdup(in_session->securityName); + if (session->securityName == NULL) { + snmp_sess_close(slp); + return (NULL); + } + } else if ((cp = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_SECNAME)) != NULL) { + cp = strdup(cp); + if (cp == NULL) { + snmp_sess_close(slp); + return (NULL); + } + session->securityName = cp; + session->securityNameLen = strlen(cp); + } + + if (session->retries == SNMP_DEFAULT_RETRIES) { + int retry = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_RETRIES); + if (retry < 0) + session->retries = DEFAULT_RETRIES; + else + session->retries = retry; + } + if (session->timeout == SNMP_DEFAULT_TIMEOUT) { + int timeout = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_TIMEOUT); + if (timeout <= 0) + session->timeout = DEFAULT_TIMEOUT; + else + session->timeout = timeout * ONE_SEC; + } + session->sessid = snmp_get_next_sessid(); + + snmp_call_callbacks(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SESSION_INIT, + session); + + if ((sptr = find_sec_mod(session->securityModel)) != NULL) { + /* + * security module specific copying + */ + if (sptr->session_setup) { + int ret = (*sptr->session_setup) (in_session, session); + if (ret != SNMPERR_SUCCESS) { + snmp_sess_close(slp); + return NULL; + } + } + + /* + * security module specific opening + */ + if (sptr->session_open) { + int ret = (*sptr->session_open) (session); + if (ret != SNMPERR_SUCCESS) { + snmp_sess_close(slp); + return NULL; + } + } + } + + /* Anything below this point should only be done if the transport + had no say in the matter */ + if (session->securityLevel == 0) + session->securityLevel = SNMP_SEC_LEVEL_NOAUTH; + + return (slp); +} + +static struct session_list * +snmp_sess_copy(netsnmp_session * pss) +{ + struct session_list *psl; + psl = _sess_copy(pss); + if (!psl) { + if (!pss->s_snmp_errno) { + pss->s_snmp_errno = SNMPERR_GENERR; + } + SET_SNMP_ERROR(pss->s_snmp_errno); + } + return psl; +} + +#ifndef NETSNMP_FEATURE_REMOVE_SNMPV3_PROBE_CONTEXTENGINEID_RFC5343 +/** + * probe for engineID using RFC 5343 probing mechanisms + * + * Designed to be a callback for within a security model's probe_engineid hook. + * Since it's likely multiple security models won't have engineIDs to + * probe for then this function is a callback likely to be used by + * multiple future security models. E.G. both SSH and DTLS. + */ +int +snmpv3_probe_contextEngineID_rfc5343(void *slp, netsnmp_session *session) { + netsnmp_pdu *pdu = NULL, *response = NULL; + static oid snmpEngineIDoid[] = { 1,3,6,1,6,3,10,2,1,1,0}; + static size_t snmpEngineIDoid_len = 11; + + static char probeEngineID[] = { (char)0x80, 0, 0, 0, 6 }; + static size_t probeEngineID_len = sizeof(probeEngineID); + + int status; + + pdu = snmp_pdu_create(SNMP_MSG_GET); + if (!pdu) + return SNMP_ERR_GENERR; + pdu->version = SNMP_VERSION_3; + /* don't require a securityName */ + if (session->securityName) { + pdu->securityName = strdup(session->securityName); + pdu->securityNameLen = strlen(pdu->securityName); + } + pdu->securityLevel = SNMP_SEC_LEVEL_NOAUTH; + pdu->securityModel = session->securityModel; + pdu->contextEngineID = netsnmp_memdup(probeEngineID, probeEngineID_len); + if (!pdu->contextEngineID) { + snmp_log(LOG_ERR, "failed to clone memory for rfc5343 probe\n"); + snmp_free_pdu(pdu); + return SNMP_ERR_GENERR; + } + pdu->contextEngineIDLen = probeEngineID_len; + + snmp_add_null_var(pdu, snmpEngineIDoid, snmpEngineIDoid_len); + + DEBUGMSGTL(("snmp_api", "probing for engineID using rfc5343 methods...\n")); + session->flags |= SNMP_FLAGS_DONT_PROBE; /* prevent recursion */ + status = snmp_sess_synch_response(slp, pdu, &response); + + if ((response == NULL) || (status != STAT_SUCCESS)) { + snmp_log(LOG_ERR, "failed rfc5343 contextEngineID probing\n"); + return SNMP_ERR_GENERR; + } + + /* check that the response makes sense */ + if (NULL != response->variables && + NULL != response->variables->name && + snmp_oid_compare(response->variables->name, + response->variables->name_length, + snmpEngineIDoid, snmpEngineIDoid_len) == 0 && + ASN_OCTET_STR == response->variables->type && + NULL != response->variables->val.string && + response->variables->val_len > 0) { + session->contextEngineID = + netsnmp_memdup(response->variables->val.string, + response->variables->val_len); + if (!session->contextEngineID) { + snmp_log(LOG_ERR, "failed rfc5343 contextEngineID probing: memory allocation failed\n"); + return SNMP_ERR_GENERR; + } + + /* technically there likely isn't a securityEngineID but just + in case anyone goes looking we might as well have one */ + session->securityEngineID = + netsnmp_memdup(response->variables->val.string, + response->variables->val_len); + if (!session->securityEngineID) { + snmp_log(LOG_ERR, "failed rfc5343 securityEngineID probing: memory allocation failed\n"); + return SNMP_ERR_GENERR; + } + + session->securityEngineIDLen = session->contextEngineIDLen = + response->variables->val_len; + + if (snmp_get_do_debugging()) { + size_t i; + DEBUGMSGTL(("snmp_sess_open", + " probe found engineID: ")); + for (i = 0; i < session->securityEngineIDLen; i++) + DEBUGMSG(("snmp_sess_open", "%02x", + session->securityEngineID[i])); + DEBUGMSG(("snmp_sess_open", "\n")); + } + } + return SNMPERR_SUCCESS; +} +#endif /* NETSNMP_FEATURE_REMOVE_SNMPV3_PROBE_CONTEXTENGINEID_RFC5343 */ + + +/** + * probe for peer engineID + * + * @param slp session list pointer. + * @param in_session session for errors + * + * @note + * - called by _sess_open(), snmp_sess_add_ex() + * - in_session is the user supplied session provided to those functions. + * - the first session in slp should the internal allocated copy of in_session + * + * @return 0 : error + * @return 1 : ok + * + */ +int +snmpv3_engineID_probe(struct session_list *slp, + netsnmp_session * in_session) +{ + netsnmp_session *session; + int status; + struct snmp_secmod_def *sptr = NULL; + + if (slp == NULL || slp->session == NULL) { + return 0; + } + + session = slp->session; + netsnmp_assert_or_return(session != NULL, 0); + sptr = find_sec_mod(session->securityModel); + + /* + * If we are opening a V3 session and we don't know engineID we must probe + * it -- this must be done after the session is created and inserted in the + * list so that the response can handled correctly. + */ + + if (session->version == SNMP_VERSION_3 && + (0 == (session->flags & SNMP_FLAGS_DONT_PROBE))) { + if (NULL != sptr && NULL != sptr->probe_engineid) { + DEBUGMSGTL(("snmp_api", "probing for engineID using security model callback...\n")); + /* security model specific mechanism of determining engineID */ + status = (*sptr->probe_engineid) (slp, in_session); + if (status != SNMPERR_SUCCESS) + return 0; + } else { + /* XXX: default to the default RFC5343 contextEngineID Probe? */ + return 0; + } + } + + /* + * see if there is a hook to call now that we're done probing for an + * engineID + */ + if (sptr && sptr->post_probe_engineid) { + status = (*sptr->post_probe_engineid)(slp, in_session); + if (status != SNMPERR_SUCCESS) + return 0; + } + + return 1; +} + +/*******************************************************************-o-****** + * netsnmp_sess_config_transport + * + * Parameters: + * *in_session + * *in_transport + * + * Returns: + * SNMPERR_SUCCESS - Yay + * SNMPERR_GENERR - Generic Error + * SNMPERR_TRANSPORT_CONFIG_ERROR - Transport rejected config + * SNMPERR_TRANSPORT_NO_CONFIG - Transport can't config + */ +int +netsnmp_sess_config_transport(netsnmp_container *transport_configuration, + netsnmp_transport *transport) +{ + /* Optional supplimental transport configuration information and + final call to actually open the transport */ + if (transport_configuration) { + DEBUGMSGTL(("snmp_sess", "configuring transport\n")); + if (transport->f_config) { + netsnmp_iterator *iter; + netsnmp_transport_config *config_data; + int ret = 0; + + iter = CONTAINER_ITERATOR(transport_configuration); + if (NULL == iter) { + return SNMPERR_GENERR; + } + + for(config_data = (netsnmp_transport_config*)ITERATOR_FIRST(iter); config_data; + config_data = (netsnmp_transport_config*)ITERATOR_NEXT(iter)) { + ret = transport->f_config(transport, config_data->key, + config_data->value); + if (ret) + break; + } + ITERATOR_RELEASE(iter); + if (ret) + return SNMPERR_TRANSPORT_CONFIG_ERROR; + } else { + return SNMPERR_TRANSPORT_NO_CONFIG; + } + } + return SNMPERR_SUCCESS; +} + + +/** + * Copies configuration from the session and calls f_open + * This function copies any configuration stored in the session + * pointer to the transport if it has a f_config pointer and then + * calls the transport's f_open function to actually open the + * connection. + * + * @param in_session A pointer to the session that config information is in. + * @param transport A pointer to the transport to config/open. + * + * @return SNMPERR_SUCCESS : on success + */ + +/*******************************************************************-o-****** + * netsnmp_sess_config_transport + * + * Parameters: + * *in_session + * *in_transport + * + * Returns: + * SNMPERR_SUCCESS - Yay + * SNMPERR_GENERR - Generic Error + * SNMPERR_TRANSPORT_CONFIG_ERROR - Transport rejected config + * SNMPERR_TRANSPORT_NO_CONFIG - Transport can't config + */ +int +netsnmp_sess_config_and_open_transport(netsnmp_session *in_session, + netsnmp_transport *transport) +{ + int rc; + + DEBUGMSGTL(("snmp_sess", "opening transport: %x\n", transport->flags & NETSNMP_TRANSPORT_FLAG_OPENED)); + + /* don't double open */ + if (transport->flags & NETSNMP_TRANSPORT_FLAG_OPENED) + return SNMPERR_SUCCESS; + + if ((rc = netsnmp_sess_config_transport(in_session->transport_configuration, + transport)) != SNMPERR_SUCCESS) { + in_session->s_snmp_errno = rc; + in_session->s_errno = 0; + return rc; + } + + if (transport->f_open) + transport = transport->f_open(transport); + + if (transport == NULL) { + DEBUGMSGTL(("snmp_sess", "couldn't interpret peername\n")); + in_session->s_snmp_errno = SNMPERR_BAD_ADDRESS; + in_session->s_errno = errno; + snmp_set_detail(in_session->peername); + return SNMPERR_BAD_ADDRESS; + } + + /** if transport has a max size, make sure session is the same (or less) */ + if (in_session->rcvMsgMaxSize > transport->msgMaxSize) { + DEBUGMSGTL(("snmp_sess", + "limiting session rcv size to transport max\n")); + in_session->rcvMsgMaxSize = transport->msgMaxSize; + } + + if (in_session->sndMsgMaxSize > transport->msgMaxSize) { + DEBUGMSGTL(("snmp_sess", + "limiting session snd size to transport max\n")); + in_session->sndMsgMaxSize = transport->msgMaxSize; + } + + transport->flags |= NETSNMP_TRANSPORT_FLAG_OPENED; + DEBUGMSGTL(("snmp_sess", "done opening transport: %x\n", transport->flags & NETSNMP_TRANSPORT_FLAG_OPENED)); + return SNMPERR_SUCCESS; +} + +/*******************************************************************-o-****** + * snmp_sess_open + * + * Parameters: + * *in_session + * + * Returns: + * Pointer to a session in the session list -OR- FIX -- right? + * NULL on failure. + * + * The "spin-free" version of snmp_open. + */ +static void * +_sess_open(netsnmp_session * in_session) +{ + netsnmp_transport *transport = NULL; + int rc; + + in_session->s_snmp_errno = 0; + in_session->s_errno = 0; + + _init_snmp(); + + { + char *clientaddr_save = NULL; + + if (NULL != in_session->localname) { + clientaddr_save = + netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CLIENT_ADDR); + if (clientaddr_save) + clientaddr_save = strdup(clientaddr_save); + + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CLIENT_ADDR, + in_session->localname); + } + + if (in_session->flags & SNMP_FLAGS_STREAM_SOCKET) { + transport = + netsnmp_tdomain_transport_full("snmp", in_session->peername, + in_session->local_port, "tcp,tcp6", + NULL); + } else { + transport = + netsnmp_tdomain_transport_full("snmp", in_session->peername, + in_session->local_port, "udp,udp6", + NULL); + } + + if (NULL != in_session->localname) + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CLIENT_ADDR, clientaddr_save); + free(clientaddr_save); + } + + if (transport == NULL) { + DEBUGMSGTL(("_sess_open", "couldn't interpret peername\n")); + in_session->s_snmp_errno = SNMPERR_BAD_ADDRESS; + in_session->s_errno = errno; + snmp_set_detail(in_session->peername); + return NULL; + } + + /* Optional supplimental transport configuration information and + final call to actually open the transport */ + if ((rc = netsnmp_sess_config_and_open_transport(in_session, transport)) + != SNMPERR_SUCCESS) { + transport = NULL; + return NULL; + } + +#if defined(SO_BROADCAST) && defined(SOL_SOCKET) + if ( in_session->flags & SNMP_FLAGS_UDP_BROADCAST) { + int b = 1; + int rc; + + rc = setsockopt(transport->sock, SOL_SOCKET, SO_BROADCAST, + (char *)&b, sizeof(b)); + + if ( rc != 0 ) { + in_session->s_snmp_errno = SNMPERR_BAD_ADDRESS; /* good as any? */ + in_session->s_errno = errno; + + DEBUGMSGTL(("_sess_open", "couldn't enable UDP_BROADCAST\n")); + return NULL; + } + } +#endif + + return snmp_sess_add(in_session, transport, NULL, NULL); +} + +/* + * EXTENDED SESSION API ------------------------------------------ + * + * snmp_sess_add_ex, snmp_sess_add, snmp_add + * + * Analogous to snmp_open family of functions, but taking a netsnmp_transport + * pointer as an extra argument. Unlike snmp_open et al. it doesn't attempt + * to interpret the in_session->peername as a transport endpoint specifier, + * but instead uses the supplied transport. JBPN + * + */ + +netsnmp_session * +snmp_add(netsnmp_session * in_session, + netsnmp_transport *transport, + int (*fpre_parse) (netsnmp_session *, netsnmp_transport *, void *, + int), int (*fpost_parse) (netsnmp_session *, + netsnmp_pdu *, int)) +{ + struct session_list *slp; + slp = (struct session_list *) snmp_sess_add_ex(in_session, transport, + fpre_parse, NULL, + fpost_parse, NULL, NULL, + NULL, NULL); + if (slp == NULL) { + return NULL; + } + + snmp_session_insert(slp); + + return (slp->session); +} + +netsnmp_session * +snmp_add_full(netsnmp_session * in_session, + netsnmp_transport *transport, + int (*fpre_parse) (netsnmp_session *, netsnmp_transport *, + void *, int), + int (*fparse) (netsnmp_session *, netsnmp_pdu *, u_char *, + size_t), + int (*fpost_parse) (netsnmp_session *, netsnmp_pdu *, int), + int (*fbuild) (netsnmp_session *, netsnmp_pdu *, u_char *, + size_t *), int (*frbuild) (netsnmp_session *, + netsnmp_pdu *, + u_char **, + size_t *, + size_t *), + int (*fcheck) (u_char *, size_t), + netsnmp_pdu *(*fcreate_pdu) (netsnmp_transport *, void *, + size_t)) +{ + struct session_list *slp; + slp = (struct session_list *) snmp_sess_add_ex(in_session, transport, + fpre_parse, fparse, + fpost_parse, fbuild, + frbuild, fcheck, + fcreate_pdu); + if (slp == NULL) { + return NULL; + } + + snmp_session_insert(slp); + + return (slp->session); +} + + + +void * +snmp_sess_add_ex(netsnmp_session * in_session, + netsnmp_transport *transport, + int (*fpre_parse) (netsnmp_session *, netsnmp_transport *, + void *, int), + int (*fparse) (netsnmp_session *, netsnmp_pdu *, u_char *, + size_t), + int (*fpost_parse) (netsnmp_session *, netsnmp_pdu *, + int), + int (*fbuild) (netsnmp_session *, netsnmp_pdu *, u_char *, + size_t *), + int (*frbuild) (netsnmp_session *, netsnmp_pdu *, + u_char **, size_t *, size_t *), + int (*fcheck) (u_char *, size_t), + netsnmp_pdu *(*fcreate_pdu) (netsnmp_transport *, void *, + size_t)) +{ + struct session_list *slp; + int rc; + + _init_snmp(); + + if (transport == NULL) + return NULL; + + if (NULL != in_session && (in_session->rcvMsgMaxSize < SNMP_MIN_MAX_LEN || + in_session->sndMsgMaxSize < SNMP_MIN_MAX_LEN)) { + DEBUGMSGTL(("snmp_sess_add", + "invalid session (msg sizes). need snmp_sess_init")); + in_session = NULL; /* force transport cleanup below */ + } + + if (in_session == NULL) { + transport->f_close(transport); + netsnmp_transport_free(transport); + return NULL; + } + + /* if the transport hasn't been fully opened yet, open it now */ + if ((rc = netsnmp_sess_config_and_open_transport(in_session, transport)) + != SNMPERR_SUCCESS) { + return NULL; + } + + if (transport->f_setup_session) { + if (SNMPERR_SUCCESS != + transport->f_setup_session(transport, in_session)) { + netsnmp_transport_free(transport); + return NULL; + } + } + + + DEBUGMSGTL(("snmp_sess_add", "fd %d\n", transport->sock)); + + + if ((slp = snmp_sess_copy(in_session)) == NULL) { + transport->f_close(transport); + netsnmp_transport_free(transport); + return (NULL); + } + + slp->transport = transport; + slp->internal->hook_pre = fpre_parse; + slp->internal->hook_parse = fparse; + slp->internal->hook_post = fpost_parse; + slp->internal->hook_build = fbuild; + slp->internal->hook_realloc_build = frbuild; + slp->internal->check_packet = fcheck; + slp->internal->hook_create_pdu = fcreate_pdu; + + /** don't let session max exceed transport max */ + if (slp->session->rcvMsgMaxSize > transport->msgMaxSize) { + DEBUGMSGTL(("snmp_sess_add", + "limiting session rcv size to transport max\n")); + slp->session->rcvMsgMaxSize = transport->msgMaxSize; + } + if (slp->session->sndMsgMaxSize > transport->msgMaxSize) { + DEBUGMSGTL(("snmp_sess_add", + "limiting session snd size to transport max\n")); + slp->session->sndMsgMaxSize = transport->msgMaxSize; + } + + if (slp->session->version == SNMP_VERSION_3) { + DEBUGMSGTL(("snmp_sess_add", + "adding v3 session -- maybe engineID probe now\n")); + if (!snmpv3_engineID_probe(slp, slp->session)) { + DEBUGMSGTL(("snmp_sess_add", "engine ID probe failed\n")); + snmp_sess_close(slp); + return NULL; + } + } + + slp->session->flags &= ~SNMP_FLAGS_DONT_PROBE; + + return (void *) slp; +} /* end snmp_sess_add_ex() */ + + + +void * +snmp_sess_add(netsnmp_session * in_session, + netsnmp_transport *transport, + int (*fpre_parse) (netsnmp_session *, netsnmp_transport *, + void *, int), + int (*fpost_parse) (netsnmp_session *, netsnmp_pdu *, int)) +{ + return snmp_sess_add_ex(in_session, transport, fpre_parse, NULL, + fpost_parse, NULL, NULL, NULL, NULL); +} + + + +void * +snmp_sess_open(netsnmp_session * pss) +{ + void *pvoid; + pvoid = _sess_open(pss); + if (!pvoid) { + SET_SNMP_ERROR(pss->s_snmp_errno); + } + return pvoid; +} + +int +create_user_from_session(netsnmp_session * session) { +#ifdef NETSNMP_SECMOD_USM + return usm_create_user_from_session(session); +#else + snmp_log(LOG_ERR, "create_user_from_session called when USM wasn't compiled in"); + netsnmp_assert(0 == 1); + return SNMP_ERR_GENERR; +#endif +} + + +/* + * Do a "deep free()" of a netsnmp_session. + * + * CAUTION: SHOULD ONLY BE USED FROM snmp_sess_close() OR SIMILAR. + * (hence it is static) + */ + +static void +snmp_free_session(netsnmp_session * s) +{ + if (s) { + SNMP_FREE(s->localname); + SNMP_FREE(s->peername); + SNMP_FREE(s->community); + SNMP_FREE(s->contextEngineID); + SNMP_FREE(s->contextName); + SNMP_FREE(s->securityEngineID); + SNMP_FREE(s->securityName); + SNMP_FREE(s->securityAuthProto); + SNMP_FREE(s->securityPrivProto); + SNMP_FREE(s->paramName); +#ifndef NETSNMP_NO_TRAP_STATS + SNMP_FREE(s->trap_stats); +#endif /* NETSNMP_NO_TRAP_STATS */ + + /* + * clear session from any callbacks + */ + netsnmp_callback_clear_client_arg(s, 0, 0); + + free((char *) s); + } +} + +/* + * Close the input session. Frees all data allocated for the session, + * dequeues any pending requests, and closes any sockets allocated for + * the session. Returns 0 on error, 1 otherwise. + */ +int +snmp_sess_close(void *sessp) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_transport *transport; + struct snmp_internal_session *isp; + netsnmp_session *sesp = NULL; + struct snmp_secmod_def *sptr; + + if (slp == NULL) { + return 0; + } + + if (slp->session != NULL && + (sptr = find_sec_mod(slp->session->securityModel)) != NULL && + sptr->session_close != NULL) { + (*sptr->session_close) (slp->session); + } + + isp = slp->internal; + slp->internal = NULL; + + if (isp) { + netsnmp_request_list *rp, *orp; + + SNMP_FREE(isp->packet); + + /* + * Free each element in the input request list. + */ + rp = isp->requests; + while (rp) { + orp = rp; + rp = rp->next_request; + if (orp->callback) { + orp->callback(NETSNMP_CALLBACK_OP_TIMED_OUT, + slp->session, orp->pdu->reqid, + orp->pdu, orp->cb_data); + } + snmp_free_pdu(orp->pdu); + free((char *) orp); + } + + free((char *) isp); + } + + transport = slp->transport; + slp->transport = NULL; + + if (transport) { + transport->f_close(transport); + netsnmp_transport_free(transport); + } + + sesp = slp->session; + slp->session = NULL; + + /* + * The following is necessary to avoid memory leakage when closing AgentX + * sessions that may have multiple subsessions. These hang off the main + * session at ->subsession, and chain through ->next. + */ + + if (sesp != NULL && sesp->subsession != NULL) { + netsnmp_session *subsession = sesp->subsession, *tmpsub; + + while (subsession != NULL) { + DEBUGMSGTL(("snmp_sess_close", + "closing session %p, subsession %p\n", sesp, + subsession)); + tmpsub = subsession->next; + snmp_free_session(subsession); + subsession = tmpsub; + } + } + + snmp_free_session(sesp); + free((char *) slp); + return 1; +} + +int +snmp_close(netsnmp_session * session) +{ + struct session_list *slp = NULL, *oslp = NULL; + + { /*MTCRITICAL_RESOURCE */ + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + if (Sessions && Sessions->session == session) { /* If first entry */ + slp = Sessions; + Sessions = slp->next; + } else { + for (slp = Sessions; slp; slp = slp->next) { + if (slp->session == session) { + if (oslp) /* if we found entry that points here */ + oslp->next = slp->next; /* link around this entry */ + break; + } + oslp = slp; + } + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); + } /*END MTCRITICAL_RESOURCE */ + if (slp == NULL) { + return 0; + } + return snmp_sess_close((void *) slp); +} + +int +snmp_close_sessions(void) +{ + struct session_list *slp; + + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + while (Sessions) { + slp = Sessions; + Sessions = Sessions->next; + snmp_sess_close((void *) slp); + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); + return 1; +} + +static void +snmpv3_calc_msg_flags(int sec_level, int msg_command, u_char * flags) +{ + *flags = 0; + if (sec_level == SNMP_SEC_LEVEL_AUTHNOPRIV) + *flags = SNMP_MSG_FLAG_AUTH_BIT; + else if (sec_level == SNMP_SEC_LEVEL_AUTHPRIV) + *flags = SNMP_MSG_FLAG_AUTH_BIT | SNMP_MSG_FLAG_PRIV_BIT; + + if (SNMP_CMD_CONFIRMED(msg_command)) + *flags |= SNMP_MSG_FLAG_RPRT_BIT; + + return; +} + +static int +snmpv3_verify_msg(netsnmp_request_list *rp, netsnmp_pdu *pdu) +{ + netsnmp_pdu *rpdu; + + if (!rp || !rp->pdu || !pdu) + return 0; + /* + * Reports don't have to match anything according to the spec + */ + if (pdu->command == SNMP_MSG_REPORT) + return 1; + rpdu = rp->pdu; + if (rp->request_id != pdu->reqid || rpdu->reqid != pdu->reqid) + return 0; + if (rpdu->version != pdu->version) + return 0; + if (rpdu->securityModel != pdu->securityModel) + return 0; + if (rpdu->securityLevel != pdu->securityLevel) + return 0; + + if (rpdu->contextEngineIDLen != pdu->contextEngineIDLen || + memcmp(rpdu->contextEngineID, pdu->contextEngineID, + pdu->contextEngineIDLen)) + return 0; + if (rpdu->contextNameLen != pdu->contextNameLen || + memcmp(rpdu->contextName, pdu->contextName, pdu->contextNameLen)) + return 0; + + /* tunneled transports don't have a securityEngineID... that's + USM specific (and maybe other future ones) */ + if (pdu->securityModel == SNMP_SEC_MODEL_USM && + (rpdu->securityEngineIDLen != pdu->securityEngineIDLen || + memcmp(rpdu->securityEngineID, pdu->securityEngineID, + pdu->securityEngineIDLen))) + return 0; + + /* the securityName must match though regardless of secmodel */ + if (rpdu->securityNameLen != pdu->securityNameLen || + memcmp(rpdu->securityName, pdu->securityName, + pdu->securityNameLen)) + return 0; + return 1; +} + + +/* + * SNMPv3 + * * Takes a session and a pdu and serializes the ASN PDU into the area + * * pointed to by packet. out_length is the size of the data area available. + * * Returns the length of the completed packet in out_length. If any errors + * * occur, -1 is returned. If all goes well, 0 is returned. + */ +static int +snmpv3_build(u_char ** pkt, size_t * pkt_len, size_t * offset, + netsnmp_session * session, netsnmp_pdu *pdu) +{ + int ret; + + session->s_snmp_errno = 0; + session->s_errno = 0; + + /* + * do validation for PDU types + */ + switch (pdu->command) { + case SNMP_MSG_RESPONSE: + case SNMP_MSG_TRAP2: + case SNMP_MSG_REPORT: + netsnmp_assert(0 == (pdu->flags & UCD_MSG_FLAG_EXPECT_RESPONSE)); + /* FALL THROUGH */ + case SNMP_MSG_INFORM: +#ifndef NETSNMP_NOTIFY_ONLY + case SNMP_MSG_GET: + case SNMP_MSG_GETNEXT: +#endif /* ! NETSNMP_NOTIFY_ONLY */ +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: +#endif /* !NETSNMP_NO_WRITE_SUPPORT */ + if (pdu->errstat == SNMP_DEFAULT_ERRSTAT) + pdu->errstat = 0; + if (pdu->errindex == SNMP_DEFAULT_ERRINDEX) + pdu->errindex = 0; + break; + +#ifndef NETSNMP_NOTIFY_ONLY + case SNMP_MSG_GETBULK: + if (pdu->max_repetitions < 0) { + session->s_snmp_errno = SNMPERR_BAD_REPETITIONS; + return -1; + } + if (pdu->non_repeaters < 0) { + session->s_snmp_errno = SNMPERR_BAD_REPEATERS; + return -1; + } + break; +#endif /* ! NETSNMP_NOTIFY_ONLY */ + + case SNMP_MSG_TRAP: + session->s_snmp_errno = SNMPERR_V1_IN_V2; + return -1; + + default: + session->s_snmp_errno = SNMPERR_UNKNOWN_PDU; + return -1; + } + + /* Do we need to set the session security engineid? */ + if (pdu->securityEngineIDLen == 0) { + if (session->securityEngineIDLen) { + snmpv3_clone_engineID(&pdu->securityEngineID, + &pdu->securityEngineIDLen, + session->securityEngineID, + session->securityEngineIDLen); + } + } + + /* Do we need to set the session context engineid? */ + if (pdu->contextEngineIDLen == 0) { + if (session->contextEngineIDLen) { + snmpv3_clone_engineID(&pdu->contextEngineID, + &pdu->contextEngineIDLen, + session->contextEngineID, + session->contextEngineIDLen); + } else if (pdu->securityEngineIDLen) { + snmpv3_clone_engineID(&pdu->contextEngineID, + &pdu->contextEngineIDLen, + pdu->securityEngineID, + pdu->securityEngineIDLen); + } + } + + if (pdu->contextName == NULL) { + if (!session->contextName) { + session->s_snmp_errno = SNMPERR_BAD_CONTEXT; + return -1; + } + pdu->contextName = strdup(session->contextName); + if (pdu->contextName == NULL) { + session->s_snmp_errno = SNMPERR_GENERR; + return -1; + } + pdu->contextNameLen = session->contextNameLen; + } + if (pdu->securityModel == SNMP_DEFAULT_SECMODEL) { + pdu->securityModel = session->securityModel; + if (pdu->securityModel == SNMP_DEFAULT_SECMODEL) { + pdu->securityModel = se_find_value_in_slist("snmp_secmods", netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SECMODEL)); + + if (pdu->securityModel <= 0) { + pdu->securityModel = SNMP_SEC_MODEL_USM; + } + } + } + if (pdu->securityNameLen == 0 && pdu->securityName == NULL) { + if (session->securityModel != SNMP_SEC_MODEL_TSM && + session->securityNameLen == 0) { + session->s_snmp_errno = SNMPERR_BAD_SEC_NAME; + return -1; + } + if (session->securityName) { + pdu->securityName = strdup(session->securityName); + if (pdu->securityName == NULL) { + session->s_snmp_errno = SNMPERR_GENERR; + return -1; + } + pdu->securityNameLen = session->securityNameLen; + } else { + pdu->securityName = strdup(""); + session->securityName = strdup(""); + } + } + if (pdu->securityLevel == 0) { + if (session->securityLevel == 0) { + session->s_snmp_errno = SNMPERR_BAD_SEC_LEVEL; + return -1; + } + pdu->securityLevel = session->securityLevel; + } + DEBUGMSGTL(("snmp_build", + "Building SNMPv3 message (secName:\"%s\", secLevel:%s)...\n", + ((session->securityName) ? (char *) session->securityName : + ((pdu->securityName) ? (char *) pdu->securityName : + "ERROR: undefined")), secLevelName[pdu->securityLevel])); + + DEBUGDUMPSECTION("send", "SNMPv3 Message"); +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + if (!(pdu->flags & UCD_MSG_FLAG_FORWARD_ENCODE)) { + ret = snmpv3_packet_realloc_rbuild(pkt, pkt_len, offset, + session, pdu, NULL, 0); + } else { +#endif + ret = snmpv3_packet_build(session, pdu, *pkt, pkt_len, NULL, 0); +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + } +#endif + DEBUGINDENTLESS(); + if (-1 != ret) { + session->s_snmp_errno = ret; + } + + return ret; + +} /* end snmpv3_build() */ + + + + +static u_char * +snmpv3_header_build(netsnmp_session * session, netsnmp_pdu *pdu, + u_char * packet, size_t * out_length, + size_t length, u_char ** msg_hdr_e) +{ + u_char *global_hdr, *global_hdr_e; + u_char *cp; + u_char msg_flags; + long max_size; + long sec_model; + u_char *pb, *pb0e; + + /* + * Save current location and build SEQUENCE tag and length placeholder + * * for SNMP message sequence (actual length inserted later) + */ + cp = asn_build_sequence(packet, out_length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), + length); + if (cp == NULL) + return NULL; + if (msg_hdr_e != NULL) + *msg_hdr_e = cp; + pb0e = cp; + + + /* + * store the version field - msgVersion + */ + DEBUGDUMPHEADER("send", "SNMP Version Number"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), (long *) &pdu->version, + sizeof(pdu->version)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + global_hdr = cp; + /* + * msgGlobalData HeaderData + */ + DEBUGDUMPSECTION("send", "msgGlobalData"); + cp = asn_build_sequence(cp, out_length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), 0); + if (cp == NULL) + return NULL; + global_hdr_e = cp; + + + /* + * msgID + */ + DEBUGDUMPHEADER("send", "msgID"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &pdu->msgid, + sizeof(pdu->msgid)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * msgMaxSize + */ + max_size = netsnmp_max_send_msg_size(); + if (session->rcvMsgMaxSize < max_size) + max_size = session->rcvMsgMaxSize; + DEBUGDUMPHEADER("send:msgMaxSize1", "msgMaxSize"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &max_size, + sizeof(max_size)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * msgFlags + */ + snmpv3_calc_msg_flags(pdu->securityLevel, pdu->command, &msg_flags); + DEBUGDUMPHEADER("send", "msgFlags"); + cp = asn_build_string(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_OCTET_STR), &msg_flags, + sizeof(msg_flags)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * msgSecurityModel + */ + sec_model = pdu->securityModel; + DEBUGDUMPHEADER("send", "msgSecurityModel"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &sec_model, + sizeof(sec_model)); + DEBUGINDENTADD(-4); /* return from global data indent */ + if (cp == NULL) + return NULL; + + + /* + * insert actual length of globalData + */ + pb = asn_build_sequence(global_hdr, out_length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), + cp - global_hdr_e); + if (pb == NULL) + return NULL; + + + /* + * insert the actual length of the entire packet + */ + pb = asn_build_sequence(packet, out_length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), + length + (cp - pb0e)); + if (pb == NULL) + return NULL; + + return cp; + +} /* end snmpv3_header_build() */ + +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + +int +snmpv3_header_realloc_rbuild(u_char ** pkt, size_t * pkt_len, + size_t * offset, netsnmp_session * session, + netsnmp_pdu *pdu) +{ + size_t start_offset = *offset; + u_char msg_flags; + long max_size, sec_model; + int rc = 0; + + /* + * msgSecurityModel. + */ + sec_model = pdu->securityModel; + DEBUGDUMPHEADER("send", "msgSecurityModel"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &sec_model, + sizeof(sec_model)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * msgFlags. + */ + snmpv3_calc_msg_flags(pdu->securityLevel, pdu->command, &msg_flags); + DEBUGDUMPHEADER("send", "msgFlags"); + rc = asn_realloc_rbuild_string(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_OCTET_STR), &msg_flags, + sizeof(msg_flags)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * msgMaxSize. + */ + max_size = netsnmp_max_send_msg_size(); + if (session->rcvMsgMaxSize < max_size) + max_size = session->rcvMsgMaxSize; + DEBUGDUMPHEADER("send:msgMaxSize2", "msgMaxSize"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &max_size, + sizeof(max_size)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * msgID. + */ + DEBUGDUMPHEADER("send", "msgID"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &pdu->msgid, + sizeof(pdu->msgid)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * Global data sequence. + */ + rc = asn_realloc_rbuild_sequence(pkt, pkt_len, offset, 1, + (u_char) (ASN_SEQUENCE | + ASN_CONSTRUCTOR), + *offset - start_offset); + if (rc == 0) { + return 0; + } + + /* + * Store the version field - msgVersion. + */ + DEBUGDUMPHEADER("send", "SNMP Version Number"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), + (long *) &pdu->version, + sizeof(pdu->version)); + DEBUGINDENTLESS(); + return rc; +} /* end snmpv3_header_realloc_rbuild() */ +#endif /* NETSNMP_USE_REVERSE_ASNENCODING */ + +static u_char * +snmpv3_scopedPDU_header_build(netsnmp_pdu *pdu, + u_char * packet, size_t * out_length, + u_char ** spdu_e) +{ + u_char *scopedPdu, *pb; + + pb = scopedPdu = packet; + pb = asn_build_sequence(pb, out_length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), 0); + if (pb == NULL) + return NULL; + if (spdu_e) + *spdu_e = pb; + + DEBUGDUMPHEADER("send", "contextEngineID"); + pb = asn_build_string(pb, out_length, + (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), + pdu->contextEngineID, pdu->contextEngineIDLen); + DEBUGINDENTLESS(); + if (pb == NULL) + return NULL; + + DEBUGDUMPHEADER("send", "contextName"); + pb = asn_build_string(pb, out_length, + (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), + (u_char *) pdu->contextName, + pdu->contextNameLen); + DEBUGINDENTLESS(); + if (pb == NULL) + return NULL; + + return pb; + +} /* end snmpv3_scopedPDU_header_build() */ + + +#ifdef NETSNMP_USE_REVERSE_ASNENCODING +int +snmpv3_scopedPDU_header_realloc_rbuild(u_char ** pkt, size_t * pkt_len, + size_t * offset, netsnmp_pdu *pdu, + size_t body_len) +{ + size_t start_offset = *offset; + int rc = 0; + + /* + * contextName. + */ + DEBUGDUMPHEADER("send", "contextName"); + rc = asn_realloc_rbuild_string(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_OCTET_STR), + (u_char *) pdu->contextName, + pdu->contextNameLen); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * contextEngineID. + */ + DEBUGDUMPHEADER("send", "contextEngineID"); + rc = asn_realloc_rbuild_string(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_OCTET_STR), + pdu->contextEngineID, + pdu->contextEngineIDLen); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + rc = asn_realloc_rbuild_sequence(pkt, pkt_len, offset, 1, + (u_char) (ASN_SEQUENCE | + ASN_CONSTRUCTOR), + *offset - start_offset + body_len); + + return rc; +} /* end snmpv3_scopedPDU_header_realloc_rbuild() */ +#endif /* NETSNMP_USE_REVERSE_ASNENCODING */ + +#ifdef NETSNMP_USE_REVERSE_ASNENCODING +/* + * returns 0 if success, -1 if fail, not 0 if SM build failure + */ +int +snmpv3_packet_realloc_rbuild(u_char ** pkt, size_t * pkt_len, + size_t * offset, netsnmp_session * session, + netsnmp_pdu *pdu, u_char * pdu_data, + size_t pdu_data_len) +{ + u_char *scoped_pdu, *hdrbuf = NULL, *hdr = NULL; + size_t hdrbuf_len = SNMP_MAX_MSG_V3_HDRS, hdr_offset = + 0, spdu_offset = 0; + size_t body_end_offset = *offset, body_len = 0; + struct snmp_secmod_def *sptr = NULL; + int rc = 0; + + /* + * Build a scopedPDU structure into the packet buffer. + */ + DEBUGPRINTPDUTYPE("send", pdu->command); + if (pdu_data) { + while ((*pkt_len - *offset) < pdu_data_len) { + if (!asn_realloc(pkt, pkt_len)) { + return -1; + } + } + + *offset += pdu_data_len; + memcpy(*pkt + *pkt_len - *offset, pdu_data, pdu_data_len); + } else { + rc = snmp_pdu_realloc_rbuild(pkt, pkt_len, offset, pdu); + if (rc == 0) { + return -1; + } + } + body_len = *offset - body_end_offset; + + DEBUGDUMPSECTION("send", "ScopedPdu"); + rc = snmpv3_scopedPDU_header_realloc_rbuild(pkt, pkt_len, offset, + pdu, body_len); + if (rc == 0) { + return -1; + } + spdu_offset = *offset; + DEBUGINDENTADD(-4); /* Return from Scoped PDU. */ + + if ((hdrbuf = (u_char *) malloc(hdrbuf_len)) == NULL) { + return -1; + } + + rc = snmpv3_header_realloc_rbuild(&hdrbuf, &hdrbuf_len, &hdr_offset, + session, pdu); + if (rc == 0) { + SNMP_FREE(hdrbuf); + return -1; + } + hdr = hdrbuf + hdrbuf_len - hdr_offset; + scoped_pdu = *pkt + *pkt_len - spdu_offset; + + /* + * Call the security module to possibly encrypt and authenticate the + * message---the entire message to transmitted on the wire is returned. + */ + + sptr = find_sec_mod(pdu->securityModel); + DEBUGDUMPSECTION("send", "SM msgSecurityParameters"); + if (sptr && sptr->encode_reverse) { + struct snmp_secmod_outgoing_params parms; + + parms.msgProcModel = pdu->msgParseModel; + parms.globalData = hdr; + parms.globalDataLen = hdr_offset; + parms.maxMsgSize = SNMP_MAX_MSG_SIZE; + parms.secModel = pdu->securityModel; + parms.secEngineID = pdu->securityEngineID; + parms.secEngineIDLen = pdu->securityEngineIDLen; + parms.secName = pdu->securityName; + parms.secNameLen = pdu->securityNameLen; + parms.secLevel = pdu->securityLevel; + parms.scopedPdu = scoped_pdu; + parms.scopedPduLen = spdu_offset; + parms.secStateRef = pdu->securityStateRef; + parms.wholeMsg = pkt; + parms.wholeMsgLen = pkt_len; + parms.wholeMsgOffset = offset; + parms.session = session; + parms.pdu = pdu; + + rc = (*sptr->encode_reverse) (&parms); + } else { + if (!sptr) { + snmp_log(LOG_ERR, + "no such security service available: %d\n", + pdu->securityModel); + } else if (!sptr->encode_reverse) { + snmp_log(LOG_ERR, + "security service %d doesn't support reverse encoding.\n", + pdu->securityModel); + } + rc = -1; + } + + DEBUGINDENTLESS(); + SNMP_FREE(hdrbuf); + return rc; +} /* end snmpv3_packet_realloc_rbuild() */ +#endif /* NETSNMP_USE_REVERSE_ASNENCODING */ + +/* + * returns 0 if success, -1 if fail, not 0 if SM build failure + */ +int +snmpv3_packet_build(netsnmp_session * session, netsnmp_pdu *pdu, + u_char * packet, size_t * out_length, + u_char * pdu_data, size_t pdu_data_len) +{ + u_char *global_data, *sec_params, *spdu_hdr_e; + size_t global_data_len, sec_params_len; + u_char spdu_buf[SNMP_MAX_MSG_SIZE]; + size_t spdu_buf_len, spdu_len; + u_char *cp; + int result; + struct snmp_secmod_def *sptr; + + global_data = packet; + + /* + * build the headers for the packet, returned addr = start of secParams + */ + sec_params = snmpv3_header_build(session, pdu, global_data, + out_length, 0, NULL); + if (sec_params == NULL) + return -1; + global_data_len = sec_params - global_data; + sec_params_len = *out_length; /* length left in packet buf for sec_params */ + + + /* + * build a scopedPDU structure into spdu_buf + */ + spdu_buf_len = SNMP_MAX_MSG_SIZE; + DEBUGDUMPSECTION("send", "ScopedPdu"); + cp = snmpv3_scopedPDU_header_build(pdu, spdu_buf, &spdu_buf_len, + &spdu_hdr_e); + if (cp == NULL) + return -1; + + /* + * build the PDU structure onto the end of spdu_buf + */ + DEBUGPRINTPDUTYPE("send", ((pdu_data) ? *pdu_data : 0x00)); + if (pdu_data) { + memcpy(cp, pdu_data, pdu_data_len); + cp += pdu_data_len; + } else { + cp = snmp_pdu_build(pdu, cp, &spdu_buf_len); + if (cp == NULL) + return -1; + } + DEBUGINDENTADD(-4); /* return from Scoped PDU */ + + /* + * re-encode the actual ASN.1 length of the scopedPdu + */ + spdu_len = cp - spdu_hdr_e; /* length of scopedPdu minus ASN.1 headers */ + spdu_buf_len = SNMP_MAX_MSG_SIZE; + if (asn_build_sequence(spdu_buf, &spdu_buf_len, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), + spdu_len) == NULL) + return -1; + spdu_len = cp - spdu_buf; /* the length of the entire scopedPdu */ + + + /* + * call the security module to possibly encrypt and authenticate the + * message - the entire message to transmitted on the wire is returned + */ + cp = NULL; + *out_length = SNMP_MAX_MSG_SIZE; + DEBUGDUMPSECTION("send", "SM msgSecurityParameters"); + sptr = find_sec_mod(pdu->securityModel); + if (sptr && sptr->encode_forward) { + struct snmp_secmod_outgoing_params parms; + parms.msgProcModel = pdu->msgParseModel; + parms.globalData = global_data; + parms.globalDataLen = global_data_len; + parms.maxMsgSize = SNMP_MAX_MSG_SIZE; + parms.secModel = pdu->securityModel; + parms.secEngineID = pdu->securityEngineID; + parms.secEngineIDLen = pdu->securityEngineIDLen; + parms.secName = pdu->securityName; + parms.secNameLen = pdu->securityNameLen; + parms.secLevel = pdu->securityLevel; + parms.scopedPdu = spdu_buf; + parms.scopedPduLen = spdu_len; + parms.secStateRef = pdu->securityStateRef; + parms.secParams = sec_params; + parms.secParamsLen = &sec_params_len; + parms.wholeMsg = &cp; + parms.wholeMsgLen = out_length; + parms.session = session; + parms.pdu = pdu; + result = (*sptr->encode_forward) (&parms); + } else { + if (!sptr) { + snmp_log(LOG_ERR, "no such security service available: %d\n", + pdu->securityModel); + } else if (!sptr->encode_forward) { + snmp_log(LOG_ERR, + "security service %d doesn't support forward out encoding.\n", + pdu->securityModel); + } + result = -1; + } + DEBUGINDENTLESS(); + return result; + +} /* end snmpv3_packet_build() */ + + +/* + * Takes a session and a pdu and serializes the ASN PDU into the area + * pointed to by *pkt. *pkt_len is the size of the data area available. + * Returns the length of the completed packet in *offset. If any errors + * occur, -1 is returned. If all goes well, 0 is returned. + */ + +static int +_snmp_build(u_char ** pkt, size_t * pkt_len, size_t * offset, + netsnmp_session * session, netsnmp_pdu *pdu) +{ +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) + u_char *h0e = NULL; + size_t start_offset = *offset; + long version; + int rc = 0; + size_t length; +#endif /* support for community based SNMP */ + + u_char *cp; + + if (NETSNMP_RUNTIME_PROTOCOL_SKIP(pdu->version)) { + DEBUGMSGTL(("snmp_send", "build packet (version 0x%02x disabled)\n", + (u_int)pdu->version)); + session->s_snmp_errno = SNMPERR_BAD_VERSION; + return -1; + } + + session->s_snmp_errno = 0; + session->s_errno = 0; + +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + if ((pdu->flags & UCD_MSG_FLAG_BULK_TOOBIG) || + (0 == netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_REVERSE_ENCODE))) { + pdu->flags |= UCD_MSG_FLAG_FORWARD_ENCODE; + } +#endif /* NETSNMP_USE_REVERSE_ASNENCODING */ + + if (pdu->version == SNMP_VERSION_3) { + return snmpv3_build(pkt, pkt_len, offset, session, pdu); + } + + switch (pdu->command) { + case SNMP_MSG_RESPONSE: + netsnmp_assert(0 == (pdu->flags & UCD_MSG_FLAG_EXPECT_RESPONSE)); +#ifndef NETSNMP_NOTIFY_ONLY + /* FALL THROUGH */ + case SNMP_MSG_GET: + case SNMP_MSG_GETNEXT: + /* FALL THROUGH */ +#endif /* ! NETSNMP_NOTIFY_ONLY */ +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: +#endif /* !NETSNMP_NO_WRITE_SUPPORT */ + /* + * all versions support these PDU types + */ + /* + * initialize defaulted PDU fields + */ + + if (pdu->errstat == SNMP_DEFAULT_ERRSTAT) + pdu->errstat = 0; + if (pdu->errindex == SNMP_DEFAULT_ERRINDEX) + pdu->errindex = 0; + break; + + case SNMP_MSG_TRAP2: + netsnmp_assert(0 == (pdu->flags & UCD_MSG_FLAG_EXPECT_RESPONSE)); + /* FALL THROUGH */ + case SNMP_MSG_INFORM: +#ifndef NETSNMP_DISABLE_SNMPV1 + /* + * not supported in SNMPv1 and SNMPsec + */ + if (pdu->version == SNMP_VERSION_1) { + session->s_snmp_errno = SNMPERR_V2_IN_V1; + return -1; + } +#endif + if (pdu->errstat == SNMP_DEFAULT_ERRSTAT) + pdu->errstat = 0; + if (pdu->errindex == SNMP_DEFAULT_ERRINDEX) + pdu->errindex = 0; + break; + +#ifndef NETSNMP_NOTIFY_ONLY + case SNMP_MSG_GETBULK: + /* + * not supported in SNMPv1 and SNMPsec + */ +#ifndef NETSNMP_DISABLE_SNMPV1 + if (pdu->version == SNMP_VERSION_1) { + session->s_snmp_errno = SNMPERR_V2_IN_V1; + return -1; + } +#endif + if (pdu->max_repetitions < 0) { + session->s_snmp_errno = SNMPERR_BAD_REPETITIONS; + return -1; + } + if (pdu->non_repeaters < 0) { + session->s_snmp_errno = SNMPERR_BAD_REPEATERS; + return -1; + } + break; +#endif /* ! NETSNMP_NOTIFY_ONLY */ + + case SNMP_MSG_TRAP: + /* + * *only* supported in SNMPv1 and SNMPsec + */ +#ifndef NETSNMP_DISABLE_SNMPV1 + if (pdu->version != SNMP_VERSION_1) { + session->s_snmp_errno = SNMPERR_V1_IN_V2; + return -1; + } +#endif + /* + * initialize defaulted Trap PDU fields + */ + pdu->reqid = 1; /* give a bogus non-error reqid for traps */ + if (pdu->enterprise_length == SNMP_DEFAULT_ENTERPRISE_LENGTH) { + pdu->enterprise = (oid *) malloc(sizeof(DEFAULT_ENTERPRISE)); + if (pdu->enterprise == NULL) { + session->s_snmp_errno = SNMPERR_MALLOC; + return -1; + } + memmove(pdu->enterprise, DEFAULT_ENTERPRISE, + sizeof(DEFAULT_ENTERPRISE)); + pdu->enterprise_length = + sizeof(DEFAULT_ENTERPRISE) / sizeof(oid); + } + if (pdu->time == SNMP_DEFAULT_TIME) + pdu->time = DEFAULT_TIME; + /* + * don't expect a response + */ + pdu->flags &= (~UCD_MSG_FLAG_EXPECT_RESPONSE); + break; + + case SNMP_MSG_REPORT: /* SNMPv3 only */ + default: + session->s_snmp_errno = SNMPERR_UNKNOWN_PDU; + return -1; + } + + /* + * save length + */ +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) + length = *pkt_len; +#endif + + /* + * setup administrative fields based on version + */ + /* + * build the message wrapper and all the administrative fields + * upto the PDU sequence + * (note that actual length of message will be inserted later) + */ + switch (pdu->version) { +#ifndef NETSNMP_DISABLE_SNMPV1 + case SNMP_VERSION_1: +#endif +#ifndef NETSNMP_DISABLE_SNMPV2C + case SNMP_VERSION_2c: +#endif +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +#ifdef NETSNMP_NO_ZEROLENGTH_COMMUNITY + if (pdu->community_len == 0) { + if (session->community_len == 0) { + session->s_snmp_errno = SNMPERR_BAD_COMMUNITY; + return -1; + } + pdu->community = (u_char *) malloc(session->community_len); + if (pdu->community == NULL) { + session->s_snmp_errno = SNMPERR_MALLOC; + return -1; + } + memmove(pdu->community, + session->community, session->community_len); + pdu->community_len = session->community_len; + } +#else /* !NETSNMP_NO_ZEROLENGTH_COMMUNITY */ + if (pdu->community_len == 0 && pdu->command != SNMP_MSG_RESPONSE) { + /* + * copy session community exactly to pdu community + */ + if (0 == session->community_len) { + SNMP_FREE(pdu->community); + } else if (pdu->community_len == session->community_len) { + memmove(pdu->community, + session->community, session->community_len); + } else { + SNMP_FREE(pdu->community); + pdu->community = (u_char *) malloc(session->community_len); + if (pdu->community == NULL) { + session->s_snmp_errno = SNMPERR_MALLOC; + return -1; + } + memmove(pdu->community, + session->community, session->community_len); + } + pdu->community_len = session->community_len; + } +#endif /* !NETSNMP_NO_ZEROLENGTH_COMMUNITY */ + + DEBUGMSGTL(("snmp_send", "Building SNMPv%ld message...\n", + (1 + pdu->version))); +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + if (!(pdu->flags & UCD_MSG_FLAG_FORWARD_ENCODE)) { + DEBUGPRINTPDUTYPE("send", pdu->command); + rc = snmp_pdu_realloc_rbuild(pkt, pkt_len, offset, pdu); + if (rc == 0) { + return -1; + } + + DEBUGDUMPHEADER("send", "Community String"); + rc = asn_realloc_rbuild_string(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | + ASN_PRIMITIVE | + ASN_OCTET_STR), + pdu->community, + pdu->community_len); + DEBUGINDENTLESS(); + if (rc == 0) { + return -1; + } + + + /* + * Store the version field. + */ + DEBUGDUMPHEADER("send", "SNMP Version Number"); + + version = pdu->version; + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | + ASN_PRIMITIVE | + ASN_INTEGER), + (long *) &version, + sizeof(version)); + DEBUGINDENTLESS(); + if (rc == 0) { + return -1; + } + + /* + * Build the final sequence. + */ +#ifndef NETSNMP_DISABLE_SNMPV1 + if (pdu->version == SNMP_VERSION_1) { + DEBUGDUMPSECTION("send", "SNMPv1 Message"); + } else { +#endif + DEBUGDUMPSECTION("send", "SNMPv2c Message"); +#ifndef NETSNMP_DISABLE_SNMPV1 + } +#endif + rc = asn_realloc_rbuild_sequence(pkt, pkt_len, offset, 1, + (u_char) (ASN_SEQUENCE | + ASN_CONSTRUCTOR), + *offset - start_offset); + DEBUGINDENTLESS(); + + if (rc == 0) { + return -1; + } + return 0; + } else { + +#endif /* NETSNMP_USE_REVERSE_ASNENCODING */ + /* + * Save current location and build SEQUENCE tag and length + * placeholder for SNMP message sequence + * (actual length will be inserted later) + */ + cp = asn_build_sequence(*pkt, pkt_len, + (u_char) (ASN_SEQUENCE | + ASN_CONSTRUCTOR), 0); + if (cp == NULL) { + return -1; + } + h0e = cp; + +#ifndef NETSNMP_DISABLE_SNMPV1 + if (pdu->version == SNMP_VERSION_1) { + DEBUGDUMPSECTION("send", "SNMPv1 Message"); + } else { +#endif + DEBUGDUMPSECTION("send", "SNMPv2c Message"); +#ifndef NETSNMP_DISABLE_SNMPV1 + } +#endif + + /* + * store the version field + */ + DEBUGDUMPHEADER("send", "SNMP Version Number"); + + version = pdu->version; + cp = asn_build_int(cp, pkt_len, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), (long *) &version, + sizeof(version)); + DEBUGINDENTLESS(); + if (cp == NULL) + return -1; + + /* + * store the community string + */ + DEBUGDUMPHEADER("send", "Community String"); + cp = asn_build_string(cp, pkt_len, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_OCTET_STR), pdu->community, + pdu->community_len); + DEBUGINDENTLESS(); + if (cp == NULL) + return -1; + break; + +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + } +#endif /* NETSNMP_USE_REVERSE_ASNENCODING */ + break; +#endif /* support for community based SNMP */ + case SNMP_VERSION_2p: + case SNMP_VERSION_sec: + case SNMP_VERSION_2u: + case SNMP_VERSION_2star: + default: + session->s_snmp_errno = SNMPERR_BAD_VERSION; + return -1; + } + + DEBUGPRINTPDUTYPE("send", pdu->command); + cp = snmp_pdu_build(pdu, cp, pkt_len); + DEBUGINDENTADD(-4); /* return from entire v1/v2c message */ + if (cp == NULL) + return -1; + + /* + * insert the actual length of the message sequence + */ + switch (pdu->version) { +#ifndef NETSNMP_DISABLE_SNMPV1 + case SNMP_VERSION_1: +#endif +#ifndef NETSNMP_DISABLE_SNMPV2C + case SNMP_VERSION_2c: +#endif +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) + asn_build_sequence(*pkt, &length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), + cp - h0e); + break; +#endif /* support for community based SNMP */ + + case SNMP_VERSION_2p: + case SNMP_VERSION_sec: + case SNMP_VERSION_2u: + case SNMP_VERSION_2star: + default: + session->s_snmp_errno = SNMPERR_BAD_VERSION; + return -1; + } + *pkt_len = cp - *pkt; + return 0; +} + +int +snmp_build(u_char ** pkt, size_t * pkt_len, size_t * offset, + netsnmp_session * pss, netsnmp_pdu *pdu) +{ + int rc; + rc = _snmp_build(pkt, pkt_len, offset, pss, pdu); + if (rc) { + if (!pss->s_snmp_errno) { + snmp_log(LOG_ERR, "snmp_build: unknown failure\n"); + pss->s_snmp_errno = SNMPERR_BAD_ASN1_BUILD; + } + SET_SNMP_ERROR(pss->s_snmp_errno); + rc = -1; + } + return rc; +} + +/* + * on error, returns NULL (likely an encoding problem). + */ +u_char * +snmp_pdu_build(netsnmp_pdu *pdu, u_char * cp, size_t * out_length) +{ + u_char *h1, *h1e, *h2, *h2e, *save_ptr; + netsnmp_variable_list *vp, *save_vp = NULL; + size_t length, save_length; + + length = *out_length; + /* + * Save current location and build PDU tag and length placeholder + * (actual length will be inserted later) + */ + h1 = cp; + cp = asn_build_sequence(cp, out_length, (u_char) pdu->command, 0); + if (cp == NULL) + return NULL; + h1e = cp; + + /* + * store fields in the PDU preceding the variable-bindings sequence + */ + if (pdu->command != SNMP_MSG_TRAP) { + /* + * PDU is not an SNMPv1 trap + */ + + DEBUGDUMPHEADER("send", "request_id"); + /* + * request id + */ + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &pdu->reqid, + sizeof(pdu->reqid)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * error status (getbulk non-repeaters) + */ + DEBUGDUMPHEADER("send", "error status"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &pdu->errstat, + sizeof(pdu->errstat)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * error index (getbulk max-repetitions) + */ + DEBUGDUMPHEADER("send", "error index"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), &pdu->errindex, + sizeof(pdu->errindex)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + } else { + /* + * an SNMPv1 trap PDU + */ + + /* + * enterprise + */ + DEBUGDUMPHEADER("send", "enterprise OBJID"); + cp = asn_build_objid(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_OBJECT_ID), + (oid *) pdu->enterprise, + pdu->enterprise_length); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * agent-addr + */ + DEBUGDUMPHEADER("send", "agent Address"); + cp = asn_build_string(cp, out_length, + (u_char) (ASN_IPADDRESS | ASN_PRIMITIVE), + (u_char *) pdu->agent_addr, 4); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * generic trap + */ + DEBUGDUMPHEADER("send", "generic trap number"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), + (long *) &pdu->trap_type, + sizeof(pdu->trap_type)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * specific trap + */ + DEBUGDUMPHEADER("send", "specific trap number"); + cp = asn_build_int(cp, out_length, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | + ASN_INTEGER), + (long *) &pdu->specific_type, + sizeof(pdu->specific_type)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + + /* + * timestamp + */ + DEBUGDUMPHEADER("send", "timestamp"); + cp = asn_build_unsigned_int(cp, out_length, + (u_char) (ASN_TIMETICKS | + ASN_PRIMITIVE), &pdu->time, + sizeof(pdu->time)); + DEBUGINDENTLESS(); + if (cp == NULL) + return NULL; + } + + /* + * Save current location and build SEQUENCE tag and length placeholder + * for variable-bindings sequence + * (actual length will be inserted later) + */ + h2 = cp; + cp = asn_build_sequence(cp, out_length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), 0); + if (cp == NULL) + return NULL; + h2e = cp; + + /* + * Store variable-bindings + */ + DEBUGDUMPSECTION("send", "VarBindList"); + for (vp = pdu->variables; vp; vp = vp->next_variable) { + /* + * if estimated getbulk response size exceeded packet max size, + * processing was stopped before bulk cache was filled and type + * was set to ASN_PRIV_STOP, indicating that the rest of the varbinds + * in the cache are empty and we can stop encoding them. + */ + if (ASN_PRIV_STOP == vp->type) + break; + + /* + * save current ptr and length so that if we exceed the packet length + * encoding this varbind and this is a bulk response, we can drop + * the failed varbind (and any that follow it) and continue encoding + * the (shorter) bulk response. + */ + save_ptr = cp; + save_length = *out_length; + + DEBUGDUMPSECTION("send", "VarBind"); + cp = snmp_build_var_op(cp, vp->name, &vp->name_length, vp->type, + vp->val_len, (u_char *) vp->val.string, + out_length); + DEBUGINDENTLESS(); + if (cp == NULL) { + if (save_vp && (pdu->flags & UCD_MSG_FLAG_BULK_TOOBIG)) { + DEBUGDUMPSECTION("send", + "VarBind would exceed packet size; dropped"); + cp = save_ptr; + *out_length = save_length; + break; + } else + return NULL; + } + save_vp = vp; + } + DEBUGINDENTLESS(); + + /** did we run out of room? (should only happen for bulk reponses) */ + if (vp && save_vp) { + save_vp->next_variable = NULL; /* truncate variable list */ + /** count remaining varbinds in list, then free them */ + save_vp = vp; + for(save_length = 0; save_vp; save_vp = save_vp->next_variable) + ++save_length; + DEBUGMSGTL(("send", "trimmed %" NETSNMP_PRIz "d variables\n", save_length)); + snmp_free_varbind(vp); + } + + /* + * insert actual length of variable-bindings sequence + */ + asn_build_sequence(h2, &length, + (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), + cp - h2e); + + /* + * insert actual length of PDU sequence + */ + asn_build_sequence(h1, &length, (u_char) pdu->command, cp - h1e); + + return cp; +} + +#ifdef NETSNMP_USE_REVERSE_ASNENCODING +/* + * On error, returns 0 (likely an encoding problem). + */ +int +snmp_pdu_realloc_rbuild(u_char ** pkt, size_t * pkt_len, size_t * offset, + netsnmp_pdu *pdu) +{ +#ifndef VPCACHE_SIZE +#define VPCACHE_SIZE 50 +#endif + netsnmp_variable_list *vpcache[VPCACHE_SIZE]; + netsnmp_variable_list *vp, *tmpvp; + size_t start_offset = *offset; + int i, wrapped = 0, notdone, final, rc = 0; + + DEBUGMSGTL(("snmp_pdu_realloc_rbuild", "starting\n")); + for (vp = pdu->variables, i = VPCACHE_SIZE - 1; vp; + vp = vp->next_variable, i--) { + /* + * if estimated getbulk response size exceeded packet max size, + * processing was stopped before bulk cache was filled and type + * was set to ASN_PRIV_STOP, indicating that the rest of the varbinds + * in the cache are empty and we can stop encoding them. + */ + if (ASN_PRIV_STOP == vp->type) + break; + if (i < 0) { + wrapped = notdone = 1; + i = VPCACHE_SIZE - 1; + DEBUGMSGTL(("snmp_pdu_realloc_rbuild", "wrapped\n")); + } + vpcache[i] = vp; + } + final = i + 1; + + do { + for (i = final; i < VPCACHE_SIZE; i++) { + vp = vpcache[i]; + DEBUGDUMPSECTION("send", "VarBind"); + rc = snmp_realloc_rbuild_var_op(pkt, pkt_len, offset, 1, + vp->name, &vp->name_length, + vp->type, + (u_char *) vp->val.string, + vp->val_len); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + } + + DEBUGINDENTLESS(); + if (wrapped) { + notdone = 1; + for (i = 0; i < final; i++) { + vp = vpcache[i]; + DEBUGDUMPSECTION("send", "VarBind"); + rc = snmp_realloc_rbuild_var_op(pkt, pkt_len, offset, 1, + vp->name, &vp->name_length, + vp->type, + (u_char *) vp->val.string, + vp->val_len); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + } + + if (final == 0) { + tmpvp = vpcache[VPCACHE_SIZE - 1]; + } else { + tmpvp = vpcache[final - 1]; + } + wrapped = 0; + + for (vp = pdu->variables, i = VPCACHE_SIZE - 1; + vp && vp != tmpvp; vp = vp->next_variable, i--) { + if (i < 0) { + wrapped = 1; + i = VPCACHE_SIZE - 1; + DEBUGMSGTL(("snmp_pdu_realloc_rbuild", "wrapped\n")); + } + vpcache[i] = vp; + } + final = i + 1; + } else { + notdone = 0; + } + } while (notdone); + + /* + * Save current location and build SEQUENCE tag and length placeholder for + * variable-bindings sequence (actual length will be inserted later). + */ + + rc = asn_realloc_rbuild_sequence(pkt, pkt_len, offset, 1, + (u_char) (ASN_SEQUENCE | + ASN_CONSTRUCTOR), + *offset - start_offset); + + /* + * Store fields in the PDU preceding the variable-bindings sequence. + */ + if (pdu->command != SNMP_MSG_TRAP) { + /* + * Error index (getbulk max-repetitions). + */ + DEBUGDUMPHEADER("send", "error index"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_INTEGER), + &pdu->errindex, sizeof(pdu->errindex)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * Error status (getbulk non-repeaters). + */ + DEBUGDUMPHEADER("send", "error status"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_INTEGER), + &pdu->errstat, sizeof(pdu->errstat)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * Request ID. + */ + DEBUGDUMPHEADER("send", "request_id"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_INTEGER), &pdu->reqid, + sizeof(pdu->reqid)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + } else { + /* + * An SNMPv1 trap PDU. + */ + + /* + * Timestamp. + */ + DEBUGDUMPHEADER("send", "timestamp"); + rc = asn_realloc_rbuild_unsigned_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_TIMETICKS | + ASN_PRIMITIVE), + &pdu->time, + sizeof(pdu->time)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * Specific trap. + */ + DEBUGDUMPHEADER("send", "specific trap number"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_INTEGER), + (long *) &pdu->specific_type, + sizeof(pdu->specific_type)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * Generic trap. + */ + DEBUGDUMPHEADER("send", "generic trap number"); + rc = asn_realloc_rbuild_int(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE + | ASN_INTEGER), + (long *) &pdu->trap_type, + sizeof(pdu->trap_type)); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * Agent-addr. + */ + DEBUGDUMPHEADER("send", "agent Address"); + rc = asn_realloc_rbuild_string(pkt, pkt_len, offset, 1, + (u_char) (ASN_IPADDRESS | + ASN_PRIMITIVE), + (u_char *) pdu->agent_addr, 4); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + + /* + * Enterprise. + */ + DEBUGDUMPHEADER("send", "enterprise OBJID"); + rc = asn_realloc_rbuild_objid(pkt, pkt_len, offset, 1, + (u_char) (ASN_UNIVERSAL | + ASN_PRIMITIVE | + ASN_OBJECT_ID), + (oid *) pdu->enterprise, + pdu->enterprise_length); + DEBUGINDENTLESS(); + if (rc == 0) { + return 0; + } + } + + /* + * Build the PDU sequence. + */ + rc = asn_realloc_rbuild_sequence(pkt, pkt_len, offset, 1, + (u_char) pdu->command, + *offset - start_offset); + return rc; +} +#endif /* NETSNMP_USE_REVERSE_ASNENCODING */ + +/* + * Parses the packet received to determine version, either directly + * from packets version field or inferred from ASN.1 construct. + */ +static int +snmp_parse_version(u_char * data, size_t length) +{ + u_char type; + long version = SNMPERR_BAD_VERSION; + + data = asn_parse_sequence(data, &length, &type, + (ASN_SEQUENCE | ASN_CONSTRUCTOR), "version"); + if (data) { + DEBUGDUMPHEADER("recv", "SNMP Version"); + data = + asn_parse_int(data, &length, &type, &version, sizeof(version)); + DEBUGINDENTLESS(); + if (!data || type != ASN_INTEGER) { + return SNMPERR_BAD_VERSION; + } + } + return version; +} + + +int +snmpv3_parse(netsnmp_pdu *pdu, + u_char * data, + size_t * length, + u_char ** after_header, netsnmp_session * sess) +{ + u_char type, msg_flags; + long ver, msg_sec_model; + size_t max_size_response; + u_char tmp_buf[SNMP_MAX_MSG_SIZE]; + size_t tmp_buf_len; + u_char pdu_buf[SNMP_MAX_MSG_SIZE]; + u_char *mallocbuf = NULL; + size_t pdu_buf_len = SNMP_MAX_MSG_SIZE; + u_char *sec_params; + u_char *msg_data; + u_char *cp; + size_t asn_len, msg_len; + int ret, ret_val; + struct snmp_secmod_def *sptr; + + + msg_data = data; + msg_len = *length; + + + /* + * message is an ASN.1 SEQUENCE + */ + DEBUGDUMPSECTION("recv", "SNMPv3 Message"); + data = asn_parse_sequence(data, length, &type, + (ASN_SEQUENCE | ASN_CONSTRUCTOR), "message"); + if (data == NULL) { + /* + * error msg detail is set + */ + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTLESS(); + return SNMPERR_ASN_PARSE_ERR; + } + + /* + * parse msgVersion + */ + DEBUGDUMPHEADER("recv", "SNMP Version Number"); + data = asn_parse_int(data, length, &type, &ver, sizeof(ver)); + DEBUGINDENTLESS(); + if (data == NULL) { + ERROR_MSG("bad parse of version"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTLESS(); + return SNMPERR_ASN_PARSE_ERR; + } + pdu->version = ver; + + /* + * parse msgGlobalData sequence + */ + cp = data; + asn_len = *length; + DEBUGDUMPSECTION("recv", "msgGlobalData"); + data = asn_parse_sequence(data, &asn_len, &type, + (ASN_SEQUENCE | ASN_CONSTRUCTOR), + "msgGlobalData"); + if (data == NULL) { + /* + * error msg detail is set + */ + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTADD(-4); + return SNMPERR_ASN_PARSE_ERR; + } + *length -= data - cp; /* subtract off the length of the header */ + + /* + * msgID + */ + DEBUGDUMPHEADER("recv", "msgID"); + data = + asn_parse_int(data, length, &type, &pdu->msgid, + sizeof(pdu->msgid)); + DEBUGINDENTLESS(); + if (data == NULL || type != ASN_INTEGER) { + ERROR_MSG("error parsing msgID"); + DEBUGINDENTADD(-4); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + return SNMPERR_ASN_PARSE_ERR; + } + + /* + * Check the msgID we received is a legal value. If not, then increment + * snmpInASNParseErrs and return the appropriate error (see RFC 2572, + * para. 7.2, section 2 -- note that a bad msgID means that the received + * message is NOT a serialiization of an SNMPv3Message, since the msgID + * field is out of bounds). + */ + + if (pdu->msgid < 0 || pdu->msgid > SNMP_MAX_PACKET_LEN) { + snmp_log(LOG_ERR, "Received bad msgID (%ld %s %s).\n", pdu->msgid, + (pdu->msgid < 0) ? "<" : ">", + (pdu->msgid < 0) ? "0" : "2^31 - 1"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTADD(-4); + return SNMPERR_ASN_PARSE_ERR; + } + + /* + * msgMaxSize + */ + DEBUGDUMPHEADER("recv:msgMaxSize", "msgMaxSize"); + data = asn_parse_int(data, length, &type, &pdu->msgMaxSize, + sizeof(pdu->msgMaxSize)); + DEBUGINDENTLESS(); + if (data == NULL || type != ASN_INTEGER) { + ERROR_MSG("error parsing msgMaxSize"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTADD(-4); + return SNMPERR_ASN_PARSE_ERR; + } + + /* + * Check the msgMaxSize we received is a legal value. If not, then + * increment snmpInASNParseErrs and return the appropriate error (see RFC + * 2572, para. 7.2, section 2 -- note that a bad msgMaxSize means that the + * received message is NOT a serialiization of an SNMPv3Message, since the + * msgMaxSize field is out of bounds). + */ + + if (pdu->msgMaxSize < SNMP_MIN_MAX_LEN) { + snmp_log(LOG_ERR, "Received bad msgMaxSize (%lu < 484).\n", + pdu->msgMaxSize); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTADD(-4); + return SNMPERR_ASN_PARSE_ERR; + } else if (pdu->msgMaxSize > SNMP_MAX_PACKET_LEN) { + snmp_log(LOG_ERR, "Received bad msgMaxSize (%lu > 2^31 - 1).\n", + pdu->msgMaxSize); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTADD(-4); + return SNMPERR_ASN_PARSE_ERR; + } else { + DEBUGMSGTL(("snmpv3_parse:msgMaxSize", "msgMaxSize %lu received\n", + pdu->msgMaxSize)); + /** don't increase max msg size if we've already got one */ + if (sess->sndMsgMaxSize < pdu->msgMaxSize) { + DEBUGMSGTL(("snmpv3_parse:msgMaxSize", + "msgMaxSize greater than session max; reducing\n")); + pdu->msgMaxSize = sess->sndMsgMaxSize; + } + } + + /* + * msgFlags + */ + tmp_buf_len = SNMP_MAX_MSG_SIZE; + DEBUGDUMPHEADER("recv", "msgFlags"); + data = asn_parse_string(data, length, &type, tmp_buf, &tmp_buf_len); + DEBUGINDENTLESS(); + if (data == NULL || type != ASN_OCTET_STR || tmp_buf_len != 1) { + ERROR_MSG("error parsing msgFlags"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTADD(-4); + return SNMPERR_ASN_PARSE_ERR; + } + msg_flags = *tmp_buf; + if (msg_flags & SNMP_MSG_FLAG_RPRT_BIT) + pdu->flags |= SNMP_MSG_FLAG_RPRT_BIT; + else + pdu->flags &= (~SNMP_MSG_FLAG_RPRT_BIT); + + /* + * msgSecurityModel + */ + DEBUGDUMPHEADER("recv", "msgSecurityModel"); + data = asn_parse_int(data, length, &type, &msg_sec_model, + sizeof(msg_sec_model)); + DEBUGINDENTADD(-4); /* return from global data indent */ + if (data == NULL || type != ASN_INTEGER || + msg_sec_model < 1 || msg_sec_model > 0x7fffffff) { + ERROR_MSG("error parsing msgSecurityModel"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTLESS(); + return SNMPERR_ASN_PARSE_ERR; + } + sptr = find_sec_mod(msg_sec_model); + if (!sptr) { + snmp_log(LOG_WARNING, "unknown security model: %ld\n", + msg_sec_model); + snmp_increment_statistic(STAT_SNMPUNKNOWNSECURITYMODELS); + DEBUGINDENTLESS(); + return SNMPERR_UNKNOWN_SEC_MODEL; + } + pdu->securityModel = msg_sec_model; + + if (msg_flags & SNMP_MSG_FLAG_PRIV_BIT && + !(msg_flags & SNMP_MSG_FLAG_AUTH_BIT)) { + ERROR_MSG("invalid message, illegal msgFlags"); + snmp_increment_statistic(STAT_SNMPINVALIDMSGS); + DEBUGINDENTLESS(); + return SNMPERR_INVALID_MSG; + } + pdu->securityLevel = ((msg_flags & SNMP_MSG_FLAG_AUTH_BIT) + ? ((msg_flags & SNMP_MSG_FLAG_PRIV_BIT) + ? SNMP_SEC_LEVEL_AUTHPRIV + : SNMP_SEC_LEVEL_AUTHNOPRIV) + : SNMP_SEC_LEVEL_NOAUTH); + /* + * end of msgGlobalData + */ + + /* + * securtityParameters OCTET STRING begins after msgGlobalData + */ + sec_params = data; + pdu->contextEngineID = (u_char *) calloc(1, SNMP_MAX_ENG_SIZE); + pdu->contextEngineIDLen = SNMP_MAX_ENG_SIZE; + + /* + * Note: there is no length limit on the msgAuthoritativeEngineID field, + * although we would EXPECT it to be limited to 32 (the SnmpEngineID TC + * limit). We'll use double that here to be on the safe side. + */ + + pdu->securityEngineID = (u_char *) calloc(1, SNMP_MAX_ENG_SIZE * 2); + pdu->securityEngineIDLen = SNMP_MAX_ENG_SIZE * 2; + pdu->securityName = (char *) calloc(1, SNMP_MAX_SEC_NAME_SIZE); + pdu->securityNameLen = SNMP_MAX_SEC_NAME_SIZE; + + if ((pdu->securityName == NULL) || + (pdu->securityEngineID == NULL) || + (pdu->contextEngineID == NULL)) { + return SNMPERR_MALLOC; + } + + if (pdu_buf_len < msg_len + && pdu->securityLevel == SNMP_SEC_LEVEL_AUTHPRIV) { + /* + * space needed is larger than we have in the default buffer + */ + mallocbuf = (u_char *) calloc(1, msg_len); + pdu_buf_len = msg_len; + cp = mallocbuf; + } else { + memset(pdu_buf, 0, pdu_buf_len); + cp = pdu_buf; + } + + DEBUGDUMPSECTION("recv", "SM msgSecurityParameters"); + if (sptr->decode) { + struct snmp_secmod_incoming_params parms; + parms.msgProcModel = pdu->msgParseModel; + parms.maxMsgSize = pdu->msgMaxSize; + parms.secParams = sec_params; + parms.secModel = msg_sec_model; + parms.secLevel = pdu->securityLevel; + parms.wholeMsg = msg_data; + parms.wholeMsgLen = msg_len; + parms.secEngineID = pdu->securityEngineID; + parms.secEngineIDLen = &pdu->securityEngineIDLen; + parms.secName = pdu->securityName; + parms.secNameLen = &pdu->securityNameLen; + parms.scopedPdu = &cp; + parms.scopedPduLen = &pdu_buf_len; + parms.maxSizeResponse = &max_size_response; + parms.secStateRef = &pdu->securityStateRef; + parms.sess = sess; + parms.pdu = pdu; + parms.msg_flags = msg_flags; + ret_val = (*sptr->decode) (&parms); + } else { + SNMP_FREE(mallocbuf); + DEBUGINDENTLESS(); + snmp_log(LOG_WARNING, "security service %ld can't decode packets\n", + msg_sec_model); + return (-1); + } + + if (ret_val != SNMPERR_SUCCESS) { + DEBUGDUMPSECTION("recv", "ScopedPDU"); + /* + * Parse as much as possible -- though I don't see the point? [jbpn]. + */ + if (cp) { + cp = snmpv3_scopedPDU_parse(pdu, cp, &pdu_buf_len); + } + if (cp) { + DEBUGPRINTPDUTYPE("recv", *cp); + snmp_pdu_parse(pdu, cp, &pdu_buf_len); + DEBUGINDENTADD(-8); + } else { + DEBUGINDENTADD(-4); + } + + SNMP_FREE(mallocbuf); + return ret_val; + } + + /* + * parse plaintext ScopedPDU sequence + */ + *length = pdu_buf_len; + DEBUGDUMPSECTION("recv", "ScopedPDU"); + data = snmpv3_scopedPDU_parse(pdu, cp, length); + if (data == NULL) { + snmp_log(LOG_WARNING, "security service %ld error parsing ScopedPDU\n", + msg_sec_model); + ERROR_MSG("error parsing PDU"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + DEBUGINDENTADD(-4); + SNMP_FREE(mallocbuf); + return SNMPERR_ASN_PARSE_ERR; + } + + /* + * parse the PDU. + */ + if (after_header != NULL) { + *after_header = data; + tmp_buf_len = *length; + } + + DEBUGPRINTPDUTYPE("recv", *data); + ret = snmp_pdu_parse(pdu, data, length); + DEBUGINDENTADD(-8); + + if (after_header != NULL) { + *length = tmp_buf_len; + } + + if (ret != SNMPERR_SUCCESS) { + snmp_log(LOG_WARNING, "security service %ld error parsing ScopedPDU\n", + msg_sec_model); + ERROR_MSG("error parsing PDU"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + SNMP_FREE(mallocbuf); + return SNMPERR_ASN_PARSE_ERR; + } + + SNMP_FREE(mallocbuf); + return SNMPERR_SUCCESS; +} /* end snmpv3_parse() */ + +static void +free_securityStateRef(netsnmp_pdu* pdu) +{ + struct snmp_secmod_def *sptr = find_sec_mod(pdu->securityModel); + if (sptr) { + if (sptr->pdu_free_state_ref) { + (*sptr->pdu_free_state_ref) (pdu->securityStateRef); + } else { + snmp_log(LOG_ERR, + "Security Model %d can't free state references\n", + pdu->securityModel); + } + } else { + snmp_log(LOG_ERR, + "Can't find security model to free ptr: %d\n", + pdu->securityModel); + } + pdu->securityStateRef = NULL; +} + +#define ERROR_STAT_LENGTH 11 + +int +snmpv3_make_report(netsnmp_pdu *pdu, int error) +{ + + long ltmp; + static oid unknownSecurityLevel[] = + { 1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0 }; + static oid notInTimeWindow[] = + { 1, 3, 6, 1, 6, 3, 15, 1, 1, 2, 0 }; + static oid unknownUserName[] = + { 1, 3, 6, 1, 6, 3, 15, 1, 1, 3, 0 }; + static oid unknownEngineID[] = + { 1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0 }; + static oid wrongDigest[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0 }; + static oid decryptionError[] = + { 1, 3, 6, 1, 6, 3, 15, 1, 1, 6, 0 }; + oid *err_var; + int err_var_len; +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + int stat_ind; +#endif + + switch (error) { + case SNMPERR_USM_UNKNOWNENGINEID: +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + stat_ind = STAT_USMSTATSUNKNOWNENGINEIDS; +#endif /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + err_var = unknownEngineID; + err_var_len = ERROR_STAT_LENGTH; + break; + case SNMPERR_USM_UNKNOWNSECURITYNAME: +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + stat_ind = STAT_USMSTATSUNKNOWNUSERNAMES; +#endif /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + err_var = unknownUserName; + err_var_len = ERROR_STAT_LENGTH; + break; + case SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL: +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + stat_ind = STAT_USMSTATSUNSUPPORTEDSECLEVELS; +#endif /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + err_var = unknownSecurityLevel; + err_var_len = ERROR_STAT_LENGTH; + break; + case SNMPERR_USM_AUTHENTICATIONFAILURE: +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + stat_ind = STAT_USMSTATSWRONGDIGESTS; +#endif /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + err_var = wrongDigest; + err_var_len = ERROR_STAT_LENGTH; + break; + case SNMPERR_USM_NOTINTIMEWINDOW: +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + stat_ind = STAT_USMSTATSNOTINTIMEWINDOWS; +#endif /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + err_var = notInTimeWindow; + err_var_len = ERROR_STAT_LENGTH; + break; + case SNMPERR_USM_DECRYPTIONERROR: +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + stat_ind = STAT_USMSTATSDECRYPTIONERRORS; +#endif /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + err_var = decryptionError; + err_var_len = ERROR_STAT_LENGTH; + break; + default: + return SNMPERR_GENERR; + } + + snmp_free_varbind(pdu->variables); /* free the current varbind */ + + pdu->variables = NULL; + SNMP_FREE(pdu->securityEngineID); + pdu->securityEngineID = + snmpv3_generate_engineID(&pdu->securityEngineIDLen); + SNMP_FREE(pdu->contextEngineID); + pdu->contextEngineID = + snmpv3_generate_engineID(&pdu->contextEngineIDLen); + pdu->command = SNMP_MSG_REPORT; + pdu->errstat = 0; + pdu->errindex = 0; + SNMP_FREE(pdu->contextName); + pdu->contextName = strdup(""); + pdu->contextNameLen = strlen(pdu->contextName); + + /* + * reports shouldn't cache previous data. + */ + /* + * FIX - yes they should but USM needs to follow new EoP to determine + * which cached values to use + */ + if (pdu->securityStateRef) { + free_securityStateRef(pdu); + } + + if (error == SNMPERR_USM_NOTINTIMEWINDOW) { + pdu->securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV; + } else { + pdu->securityLevel = SNMP_SEC_LEVEL_NOAUTH; + } + + /* + * find the appropriate error counter + */ +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS + ltmp = snmp_get_statistic(stat_ind); +#else /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + ltmp = 1; +#endif /* !NETSNMP_FEATURE_REMOVE_STATISTICS */ + + /* + * return the appropriate error counter + */ + snmp_pdu_add_variable(pdu, err_var, err_var_len, + ASN_COUNTER, & ltmp, sizeof(ltmp)); + + return SNMPERR_SUCCESS; +} /* end snmpv3_make_report() */ + + +int +snmpv3_get_report_type(netsnmp_pdu *pdu) +{ + static oid snmpMPDStats[] = { 1, 3, 6, 1, 6, 3, 11, 2, 1 }; + static oid targetStats[] = { 1, 3, 6, 1, 6, 3, 12, 1 }; + static oid usmStats[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1 }; + netsnmp_variable_list *vp; + int rpt_type = SNMPERR_UNKNOWN_REPORT; + + if (pdu == NULL || pdu->variables == NULL) + return rpt_type; + vp = pdu->variables; + /* MPD or USM based report statistics objects have the same length prefix + * so the actual statistics OID will have this length, + * plus one subidentifier for the scalar MIB object itself, + * and one for the instance subidentifier + */ + if (vp->name_length == REPORT_STATS_LEN + 2) { + if (memcmp(snmpMPDStats, vp->name, REPORT_STATS_LEN * sizeof(oid)) == 0) { + switch (vp->name[REPORT_STATS_LEN]) { + case REPORT_snmpUnknownSecurityModels_NUM: + rpt_type = SNMPERR_UNKNOWN_SEC_MODEL; + break; + case REPORT_snmpInvalidMsgs_NUM: + rpt_type = SNMPERR_INVALID_MSG; + break; + case REPORT_snmpUnknownPDUHandlers_NUM: + rpt_type = SNMPERR_BAD_VERSION; + break; + } + } else if (memcmp(usmStats, vp->name, REPORT_STATS_LEN * sizeof(oid)) == 0) { + switch (vp->name[REPORT_STATS_LEN]) { + case REPORT_usmStatsUnsupportedSecLevels_NUM: + rpt_type = SNMPERR_UNSUPPORTED_SEC_LEVEL; + break; + case REPORT_usmStatsNotInTimeWindows_NUM: + rpt_type = SNMPERR_NOT_IN_TIME_WINDOW; + break; + case REPORT_usmStatsUnknownUserNames_NUM: + rpt_type = SNMPERR_UNKNOWN_USER_NAME; + break; + case REPORT_usmStatsUnknownEngineIDs_NUM: + rpt_type = SNMPERR_UNKNOWN_ENG_ID; + break; + case REPORT_usmStatsWrongDigests_NUM: + rpt_type = SNMPERR_AUTHENTICATION_FAILURE; + break; + case REPORT_usmStatsDecryptionErrors_NUM: + rpt_type = SNMPERR_DECRYPTION_ERR; + break; + } + } + } + /* Context-based report statistics from the Target MIB are similar + * but the OID prefix has a different length + */ + if (vp->name_length == REPORT_STATS_LEN2 + 2) { + if (memcmp(targetStats, vp->name, REPORT_STATS_LEN2 * sizeof(oid)) == 0) { + switch (vp->name[REPORT_STATS_LEN2]) { + case REPORT_snmpUnavailableContexts_NUM: + rpt_type = SNMPERR_BAD_CONTEXT; + break; + case REPORT_snmpUnknownContexts_NUM: + rpt_type = SNMPERR_BAD_CONTEXT; + break; + } + } + } + DEBUGMSGTL(("report", "Report type: %d\n", rpt_type)); + return rpt_type; +} + +/* + * Parses the packet received on the input session, and places the data into + * the input pdu. length is the length of the input packet. + * If any errors are encountered, -1 or USM error is returned. + * Otherwise, a 0 is returned. + */ +static int +_snmp_parse(void *sessp, + netsnmp_session * session, + netsnmp_pdu *pdu, u_char * data, size_t length) +{ +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) + u_char community[COMMUNITY_MAX_LEN]; + size_t community_length = COMMUNITY_MAX_LEN; +#endif + int result = -1; + + static oid snmpEngineIDoid[] = { 1,3,6,1,6,3,10,2,1,1,0}; + static size_t snmpEngineIDoid_len = 11; + + static char ourEngineID[SNMP_SEC_PARAM_BUF_SIZE]; + static size_t ourEngineID_len = sizeof(ourEngineID); + + netsnmp_pdu *pdu2 = NULL; + + session->s_snmp_errno = 0; + session->s_errno = 0; + + /* + * Ensure all incoming PDUs have a unique means of identification + * (This is not restricted to AgentX handling, + * though that is where the need becomes visible) + */ + pdu->transid = snmp_get_next_transid(); + + if (session->version != SNMP_DEFAULT_VERSION) { + pdu->version = session->version; + } else { + pdu->version = snmp_parse_version(data, length); + } + + switch (pdu->version) { +#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) +#ifndef NETSNMP_DISABLE_SNMPV1 + case SNMP_VERSION_1: +#endif +#ifndef NETSNMP_DISABLE_SNMPV2C + case SNMP_VERSION_2c: +#endif + NETSNMP_RUNTIME_PROTOCOL_CHECK_V1V2(pdu->version,unsupported_version); + DEBUGMSGTL(("snmp_api", "Parsing SNMPv%ld message...\n", + (1 + pdu->version))); + + /* + * authenticates message and returns length if valid + */ +#ifndef NETSNMP_DISABLE_SNMPV1 + if (pdu->version == SNMP_VERSION_1) { + DEBUGDUMPSECTION("recv", "SNMPv1 message\n"); + } else { +#endif + DEBUGDUMPSECTION("recv", "SNMPv2c message\n"); +#ifndef NETSNMP_DISABLE_SNMPV1 + } +#endif + data = snmp_comstr_parse(data, &length, + community, &community_length, + &pdu->version); + if (data == NULL) + return -1; + + if (pdu->version != session->version && + session->version != SNMP_DEFAULT_VERSION) { + session->s_snmp_errno = SNMPERR_BAD_VERSION; + return -1; + } + + /* + * maybe get the community string. + */ + pdu->securityLevel = SNMP_SEC_LEVEL_NOAUTH; + pdu->securityModel = +#ifndef NETSNMP_DISABLE_SNMPV1 + (pdu->version == SNMP_VERSION_1) ? SNMP_SEC_MODEL_SNMPv1 : +#endif + SNMP_SEC_MODEL_SNMPv2c; + SNMP_FREE(pdu->community); + pdu->community_len = 0; + pdu->community = (u_char *) 0; + if (community_length) { + pdu->community_len = community_length; + pdu->community = (u_char *) malloc(community_length); + if (pdu->community == NULL) { + session->s_snmp_errno = SNMPERR_MALLOC; + return -1; + } + memmove(pdu->community, community, community_length); + } + if (session->authenticator) { + data = session->authenticator(data, &length, + community, community_length); + if (data == NULL) { + session->s_snmp_errno = SNMPERR_AUTHENTICATION_FAILURE; + return -1; + } + } + + DEBUGDUMPSECTION("recv", "PDU"); + result = snmp_pdu_parse(pdu, data, &length); + if (result < 0) { + /* + * This indicates a parse error. + */ + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + } + DEBUGINDENTADD(-6); + break; +#endif /* support for community based SNMP */ + + case SNMP_VERSION_3: + NETSNMP_RUNTIME_PROTOCOL_CHECK_V3(SNMP_VERSION_3,unsupported_version); + result = snmpv3_parse(pdu, data, &length, NULL, session); + DEBUGMSGTL(("snmp_parse", + "Parsed SNMPv3 message (secName:%s, secLevel:%s): %s\n", + pdu->securityName, secLevelName[pdu->securityLevel], + snmp_api_errstring(result))); + + if (result) { + struct snmp_secmod_def *secmod = + find_sec_mod(pdu->securityModel); + if (!sessp) { + session->s_snmp_errno = result; + } else { + /* + * Call the security model to special handle any errors + */ + + if (secmod && secmod->handle_report) { + struct session_list *slp = (struct session_list *) sessp; + (*secmod->handle_report)(sessp, slp->transport, session, + result, pdu); + } + } + if (pdu->securityStateRef != NULL) { + if (secmod && secmod->pdu_free_state_ref) { + secmod->pdu_free_state_ref(pdu->securityStateRef); + pdu->securityStateRef = NULL; + } + } + } + + /* Implement RFC5343 here for two reasons: + 1) From a security perspective it handles this otherwise + always approved request earlier. It bypasses the need + for authorization to the snmpEngineID scalar, which is + what is what RFC3415 appendix A species as ok. Note + that we haven't bypassed authentication since if there + was an authentication eror it would have been handled + above in the if(result) part at the lastet. + 2) From an application point of view if we let this request + get all the way to the application, it'd require that + all application types supporting discovery also fire up + a minimal agent in order to handle just this request + which seems like overkill. Though there is no other + application types that currently need discovery (NRs + accept notifications from contextEngineIDs that derive + from the NO not the NR). Also a lame excuse for doing + it here. + 3) Less important technically, but the net-snmp agent + doesn't currently handle registrations of different + engineIDs either and it would have been a lot more work + to implement there since we'd need to support that + first. :-/ Supporting multiple context engineIDs should + be done anyway, so it's not a valid excuse here. + 4) There is a lot less to do if we trump the agent at this + point; IE, the agent does a lot more unnecessary + processing when the only thing that should ever be in + this context by definition is the single scalar. + */ + + /* special RFC5343 engineID discovery engineID check */ + if (!netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_NO_DISCOVERY) && + SNMP_MSG_RESPONSE != pdu->command && + NULL != pdu->contextEngineID && + pdu->contextEngineIDLen == 5 && + pdu->contextEngineID[0] == 0x80 && + pdu->contextEngineID[1] == 0x00 && + pdu->contextEngineID[2] == 0x00 && + pdu->contextEngineID[3] == 0x00 && + pdu->contextEngineID[4] == 0x06) { + + /* define a result so it doesn't get past us at this point + and gets dropped by future parts of the stack */ + result = SNMPERR_JUST_A_CONTEXT_PROBE; + + DEBUGMSGTL(("snmpv3_contextid", "starting context ID discovery\n")); + /* ensure exactly one variable */ + if (NULL != pdu->variables && + NULL == pdu->variables->next_variable && + + /* if it's a GET, match it exactly */ + ((SNMP_MSG_GET == pdu->command && + snmp_oid_compare(snmpEngineIDoid, + snmpEngineIDoid_len, + pdu->variables->name, + pdu->variables->name_length) == 0) + /* if it's a GETNEXT ensure it's less than the engineID oid */ + || + (SNMP_MSG_GETNEXT == pdu->command && + snmp_oid_compare(snmpEngineIDoid, + snmpEngineIDoid_len, + pdu->variables->name, + pdu->variables->name_length) > 0) + )) { + + DEBUGMSGTL(("snmpv3_contextid", + " One correct variable found\n")); + + /* Note: we're explictly not handling a GETBULK. Deal. */ + + /* set up the response */ + pdu2 = snmp_clone_pdu(pdu); + + /* free the current varbind */ + snmp_free_varbind(pdu2->variables); + + /* set the variables */ + pdu2->variables = NULL; + pdu2->command = SNMP_MSG_RESPONSE; + pdu2->errstat = 0; + pdu2->errindex = 0; + + ourEngineID_len = + snmpv3_get_engineID((u_char*)ourEngineID, ourEngineID_len); + if (0 != ourEngineID_len) { + + DEBUGMSGTL(("snmpv3_contextid", + " responding with our engineID\n")); + + snmp_pdu_add_variable(pdu2, + snmpEngineIDoid, snmpEngineIDoid_len, + ASN_OCTET_STR, + ourEngineID, ourEngineID_len); + + /* send the response */ + if (0 == snmp_sess_send(sessp, pdu2)) { + + DEBUGMSGTL(("snmpv3_contextid", + " sent it off!\n")); + + snmp_free_pdu(pdu2); + + snmp_log(LOG_ERR, "sending a response to the context engineID probe failed\n"); + } + } else { + snmp_log(LOG_ERR, "failed to get our own engineID!\n"); + } + } else { + snmp_log(LOG_WARNING, + "received an odd context engineID probe\n"); + } + } + + break; + case SNMPERR_BAD_VERSION: + ERROR_MSG("error parsing snmp message version"); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + session->s_snmp_errno = SNMPERR_BAD_VERSION; + break; + + unsupported_version: /* goto label */ + case SNMP_VERSION_sec: + case SNMP_VERSION_2u: + case SNMP_VERSION_2star: + case SNMP_VERSION_2p: + default: + ERROR_MSG("unsupported snmp message version"); + snmp_increment_statistic(STAT_SNMPINBADVERSIONS); + + /* + * need better way to determine OS independent + * INT32_MAX value, for now hardcode + */ + if (pdu->version < 0 || pdu->version > 2147483647) { + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + } + session->s_snmp_errno = SNMPERR_BAD_VERSION; + break; + } + + return result; +} + +static int +snmp_parse(void *sessp, + netsnmp_session * pss, + netsnmp_pdu *pdu, u_char * data, size_t length) +{ + int rc; + + rc = _snmp_parse(sessp, pss, pdu, data, length); + if (rc) { + if (!pss->s_snmp_errno) { + pss->s_snmp_errno = SNMPERR_BAD_PARSE; + } + SET_SNMP_ERROR(pss->s_snmp_errno); + } + + return rc; +} + +int +snmp_pdu_parse(netsnmp_pdu *pdu, u_char * data, size_t * length) +{ + u_char type; + u_char msg_type; + u_char *var_val; + size_t len; + size_t four; + netsnmp_variable_list *vp = NULL, *vplast = NULL; + oid objid[MAX_OID_LEN]; + u_char *p; + + /* + * Get the PDU type + */ + data = asn_parse_header(data, length, &msg_type); + if (data == NULL) + return -1; + DEBUGMSGTL(("dumpv_recv"," Command %s\n", snmp_pdu_type(msg_type))); + pdu->command = msg_type; + pdu->flags &= (~UCD_MSG_FLAG_RESPONSE_PDU); + + /* + * get the fields in the PDU preceding the variable-bindings sequence + */ + switch (pdu->command) { + case SNMP_MSG_TRAP: + /* + * enterprise + */ + pdu->enterprise_length = MAX_OID_LEN; + data = asn_parse_objid(data, length, &type, objid, + &pdu->enterprise_length); + if (data == NULL) + return -1; + pdu->enterprise = + (oid *) malloc(pdu->enterprise_length * sizeof(oid)); + if (pdu->enterprise == NULL) { + return -1; + } + memmove(pdu->enterprise, objid, + pdu->enterprise_length * sizeof(oid)); + + /* + * agent-addr + */ + four = 4; + data = asn_parse_string(data, length, &type, + (u_char *) pdu->agent_addr, &four); + if (data == NULL) + return -1; + + /* + * generic trap + */ + data = asn_parse_int(data, length, &type, (long *) &pdu->trap_type, + sizeof(pdu->trap_type)); + if (data == NULL) + return -1; + /* + * specific trap + */ + data = + asn_parse_int(data, length, &type, + (long *) &pdu->specific_type, + sizeof(pdu->specific_type)); + if (data == NULL) + return -1; + + /* + * timestamp + */ + data = asn_parse_unsigned_int(data, length, &type, &pdu->time, + sizeof(pdu->time)); + if (data == NULL) + return -1; + + break; + + case SNMP_MSG_RESPONSE: + case SNMP_MSG_REPORT: + pdu->flags |= UCD_MSG_FLAG_RESPONSE_PDU; + /* FALL THROUGH */ + + case SNMP_MSG_TRAP2: + case SNMP_MSG_INFORM: +#ifndef NETSNMP_NOTIFY_ONLY + case SNMP_MSG_GET: + case SNMP_MSG_GETNEXT: + case SNMP_MSG_GETBULK: +#endif /* ! NETSNMP_NOTIFY_ONLY */ +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: +#endif /* !NETSNMP_NO_WRITE_SUPPORT */ + /* + * PDU is not an SNMPv1 TRAP + */ + + /* + * request id + */ + DEBUGDUMPHEADER("recv", "request_id"); + data = asn_parse_int(data, length, &type, &pdu->reqid, + sizeof(pdu->reqid)); + DEBUGINDENTLESS(); + if (data == NULL) { + return -1; + } + + /* + * error status (getbulk non-repeaters) + */ + DEBUGDUMPHEADER("recv", "error status"); + data = asn_parse_int(data, length, &type, &pdu->errstat, + sizeof(pdu->errstat)); + DEBUGINDENTLESS(); + if (data == NULL) { + return -1; + } + + /* + * error index (getbulk max-repetitions) + */ + DEBUGDUMPHEADER("recv", "error index"); + data = asn_parse_int(data, length, &type, &pdu->errindex, + sizeof(pdu->errindex)); + DEBUGINDENTLESS(); + if (data == NULL) { + return -1; + } + break; + + default: + snmp_log(LOG_ERR, "Bad PDU type received: 0x%.2x\n", pdu->command); + snmp_increment_statistic(STAT_SNMPINASNPARSEERRS); + return -1; + } + + /* + * get header for variable-bindings sequence + */ + DEBUGDUMPSECTION("recv", "VarBindList"); + data = asn_parse_sequence(data, length, &type, + (ASN_SEQUENCE | ASN_CONSTRUCTOR), + "varbinds"); + if (data == NULL) + goto fail; + + /* + * get each varBind sequence + */ + while ((int) *length > 0) { + vp = SNMP_MALLOC_TYPEDEF(netsnmp_variable_list); + if (NULL == vp) + goto fail; + + vp->name_length = MAX_OID_LEN; + DEBUGDUMPSECTION("recv", "VarBind"); + data = snmp_parse_var_op(data, objid, &vp->name_length, &vp->type, + &vp->val_len, &var_val, length); + if (data == NULL) + goto fail; + if (snmp_set_var_objid(vp, objid, vp->name_length)) + goto fail; + + len = SNMP_MAX_PACKET_LEN; + DEBUGDUMPHEADER("recv", "Value"); + switch ((short) vp->type) { + case ASN_INTEGER: + vp->val.integer = (long *) vp->buf; + vp->val_len = sizeof(long); + p = asn_parse_int(var_val, &len, &vp->type, + (long *) vp->val.integer, + sizeof(*vp->val.integer)); + if (!p) + goto fail; + break; + case ASN_COUNTER: + case ASN_GAUGE: + case ASN_TIMETICKS: + case ASN_UINTEGER: + vp->val.integer = (long *) vp->buf; + vp->val_len = sizeof(u_long); + p = asn_parse_unsigned_int(var_val, &len, &vp->type, + (u_long *) vp->val.integer, + vp->val_len); + if (!p) + goto fail; + break; +#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES + case ASN_OPAQUE_COUNTER64: + case ASN_OPAQUE_U64: +#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */ + case ASN_COUNTER64: + vp->val.counter64 = (struct counter64 *) vp->buf; + vp->val_len = sizeof(struct counter64); + p = asn_parse_unsigned_int64(var_val, &len, &vp->type, + (struct counter64 *) vp->val. + counter64, vp->val_len); + if (!p) + goto fail; + break; +#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES + case ASN_OPAQUE_FLOAT: + vp->val.floatVal = (float *) vp->buf; + vp->val_len = sizeof(float); + p = asn_parse_float(var_val, &len, &vp->type, + vp->val.floatVal, vp->val_len); + if (!p) + goto fail; + break; + case ASN_OPAQUE_DOUBLE: + vp->val.doubleVal = (double *) vp->buf; + vp->val_len = sizeof(double); + p = asn_parse_double(var_val, &len, &vp->type, + vp->val.doubleVal, vp->val_len); + if (!p) + goto fail; + break; + case ASN_OPAQUE_I64: + vp->val.counter64 = (struct counter64 *) vp->buf; + vp->val_len = sizeof(struct counter64); + p = asn_parse_signed_int64(var_val, &len, &vp->type, + (struct counter64 *) vp->val.counter64, + sizeof(*vp->val.counter64)); + + if (!p) + goto fail; + break; +#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */ + case ASN_IPADDRESS: + if (vp->val_len != 4) + goto fail; + /* fallthrough */ + case ASN_OCTET_STR: + case ASN_OPAQUE: + case ASN_NSAP: + if (vp->val_len < sizeof(vp->buf)) { + vp->val.string = (u_char *) vp->buf; + } else { + vp->val.string = (u_char *) malloc(vp->val_len); + } + if (vp->val.string == NULL) { + goto fail; + } + p = asn_parse_string(var_val, &len, &vp->type, vp->val.string, + &vp->val_len); + if (!p) + goto fail; + break; + case ASN_OBJECT_ID: + vp->val_len = MAX_OID_LEN; + p = asn_parse_objid(var_val, &len, &vp->type, objid, &vp->val_len); + if (!p) + goto fail; + vp->val_len *= sizeof(oid); + vp->val.objid = (oid *) malloc(vp->val_len); + if (vp->val.objid == NULL) { + goto fail; + } + memmove(vp->val.objid, objid, vp->val_len); + break; + case SNMP_NOSUCHOBJECT: + case SNMP_NOSUCHINSTANCE: + case SNMP_ENDOFMIBVIEW: + case ASN_NULL: + break; + case ASN_BIT_STR: + vp->val.bitstring = (u_char *) malloc(vp->val_len); + if (vp->val.bitstring == NULL) { + goto fail; + } + p = asn_parse_bitstring(var_val, &len, &vp->type, + vp->val.bitstring, &vp->val_len); + if (!p) + goto fail; + break; + default: + snmp_log(LOG_ERR, "bad type returned (%x)\n", vp->type); + goto fail; + break; + } + DEBUGINDENTADD(-4); + + if (NULL == vplast) { + pdu->variables = vp; + } else { + vplast->next_variable = vp; + } + vplast = vp; + vp = NULL; + } + return 0; + + fail: + { + const char *errstr = snmp_api_errstring(SNMPERR_SUCCESS); + DEBUGMSGTL(("recv", "error while parsing VarBindList:%s\n", errstr)); + } + /** if we were parsing a var, remove it from the pdu and free it */ + if (vp) + snmp_free_var(vp); + + return -1; +} + +/* + * snmp v3 utility function to parse into the scopedPdu. stores contextName + * and contextEngineID in pdu struct. Also stores pdu->command (handy for + * Report generation). + * + * returns pointer to begining of PDU or NULL on error. + */ +u_char * +snmpv3_scopedPDU_parse(netsnmp_pdu *pdu, u_char * cp, size_t * length) +{ + u_char tmp_buf[SNMP_MAX_MSG_SIZE]; + size_t tmp_buf_len; + u_char type; + size_t asn_len; + u_char *data; + + pdu->command = 0; /* initialize so we know if it got parsed */ + asn_len = *length; + data = asn_parse_sequence(cp, &asn_len, &type, + (ASN_SEQUENCE | ASN_CONSTRUCTOR), + "plaintext scopedPDU"); + if (data == NULL) { + return NULL; + } + *length -= data - cp; + + /* + * contextEngineID from scopedPdu + */ + DEBUGDUMPHEADER("recv", "contextEngineID"); + data = asn_parse_string(data, length, &type, pdu->contextEngineID, + &pdu->contextEngineIDLen); + DEBUGINDENTLESS(); + if (data == NULL) { + ERROR_MSG("error parsing contextEngineID from scopedPdu"); + return NULL; + } + + /* + * parse contextName from scopedPdu + */ + tmp_buf_len = SNMP_MAX_CONTEXT_SIZE; + DEBUGDUMPHEADER("recv", "contextName"); + data = asn_parse_string(data, length, &type, tmp_buf, &tmp_buf_len); + DEBUGINDENTLESS(); + if (data == NULL) { + ERROR_MSG("error parsing contextName from scopedPdu"); + return NULL; + } + + if (tmp_buf_len) { + pdu->contextName = (char *) malloc(tmp_buf_len); + memmove(pdu->contextName, tmp_buf, tmp_buf_len); + pdu->contextNameLen = tmp_buf_len; + } else { + pdu->contextName = strdup(""); + pdu->contextNameLen = 0; + } + if (pdu->contextName == NULL) { + ERROR_MSG("error copying contextName from scopedPdu"); + return NULL; + } + + /* + * Get the PDU type + */ + asn_len = *length; + cp = asn_parse_header(data, &asn_len, &type); + if (cp == NULL) + return NULL; + + pdu->command = type; + + return data; +} + + +/* =========================================================================== + * + * build pdu packet + */ +int +netsnmp_build_packet(struct snmp_internal_session *isp, netsnmp_session *sp, + netsnmp_pdu *pdu, u_char **pktbuf_p, + size_t *pktbuf_len_p, u_char **pkt_p, size_t *len_p) +{ + size_t offset = 0; + int result; + + if (isp && isp->hook_realloc_build) { + result = isp->hook_realloc_build(sp, pdu, pktbuf_p, pktbuf_len_p, + &offset); + + *pkt_p = *pktbuf_p; + *len_p = offset; + } else if (isp && isp->hook_build) { + *pkt_p = *pktbuf_p; + *len_p = *pktbuf_len_p; + result = isp->hook_build(sp, pdu, *pktbuf_p, len_p); + } else { +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + if (!(pdu->flags & UCD_MSG_FLAG_FORWARD_ENCODE)) { + result = snmp_build(pktbuf_p, pktbuf_len_p, &offset, sp, pdu); + *pkt_p = *pktbuf_p + *pktbuf_len_p - offset; + *len_p = offset; + } else { +#endif + *pkt_p = *pktbuf_p; + *len_p = *pktbuf_len_p; + result = snmp_build(pktbuf_p, len_p, &offset, sp, pdu); +#ifdef NETSNMP_USE_REVERSE_ASNENCODING + } +#endif + } + + return result; +} + +int +_build_initial_pdu_packet(struct session_list *slp, netsnmp_pdu *pdu, int bulk) +{ + netsnmp_session *session; + struct snmp_internal_session *isp; + netsnmp_transport *transport = NULL; + u_char *pktbuf = NULL, *packet = NULL; + size_t pktbuf_len = 0, offset = 0, length = 0, orig_length = 0; + int result, orig_count = 0, curr_count = 0; + + if (slp == NULL) { + return SNMPERR_GENERR; + } + session = slp->session; + + isp = slp->internal; + transport = slp->transport; + if (!session || !isp || !transport) { + DEBUGMSGTL(("sess_async_send", "send fail: closing...\n")); + return SNMPERR_GENERR; + } + + if (pdu == NULL) { + session->s_snmp_errno = SNMPERR_NULL_PDU; + return SNMPERR_GENERR; + } + + SNMP_FREE(isp->obuf); /* should already be NULL */ + + session->s_snmp_errno = 0; + session->s_errno = 0; + + /* + * Check/setup the version. + */ + if (pdu->version == SNMP_DEFAULT_VERSION) { + if (session->version == SNMP_DEFAULT_VERSION) { + session->s_snmp_errno = SNMPERR_BAD_VERSION; + return SNMPERR_GENERR; + } + pdu->version = session->version; + } else if (session->version == SNMP_DEFAULT_VERSION) { + /* + * It's OK + */ + } else if (pdu->version != session->version) { + /* + * ENHANCE: we should support multi-lingual sessions + */ + session->s_snmp_errno = SNMPERR_BAD_VERSION; + return SNMPERR_GENERR; + } + if (NETSNMP_RUNTIME_PROTOCOL_SKIP(pdu->version)) { + DEBUGMSGTL(("sess_async_send", "version disabled at runtime\n")); + session->s_snmp_errno = SNMPERR_BAD_VERSION; + return SNMPERR_GENERR; + } + + /* + * do we expect a response? + */ + switch (pdu->command) { + + case SNMP_MSG_RESPONSE: + case SNMP_MSG_TRAP: + case SNMP_MSG_TRAP2: + case SNMP_MSG_REPORT: + case AGENTX_MSG_CLEANUPSET: + case AGENTX_MSG_RESPONSE: + pdu->flags &= ~UCD_MSG_FLAG_EXPECT_RESPONSE; + break; + + default: + pdu->flags |= UCD_MSG_FLAG_EXPECT_RESPONSE; + break; + } + + /* + * check to see if we need a v3 engineID probe + */ + if ((pdu->version == SNMP_VERSION_3) && + (pdu->flags & UCD_MSG_FLAG_EXPECT_RESPONSE) && + (session->securityEngineIDLen == 0) && + (0 == (session->flags & SNMP_FLAGS_DONT_PROBE))) { + int rc; + DEBUGMSGTL(("snmpv3_build", "delayed probe for engineID\n")); + rc = snmpv3_engineID_probe(slp, session); + if (rc == 0) + return 0; /* s_snmp_errno already set */ + } + + /* + * determine max packet size + */ + if (pdu->msgMaxSize == 0) { + pdu->msgMaxSize = netsnmp_max_send_msg_size(); + if (pdu->msgMaxSize > transport->msgMaxSize) + pdu->msgMaxSize = transport->msgMaxSize; + if (pdu->msgMaxSize > session->sndMsgMaxSize) + pdu->msgMaxSize = session->sndMsgMaxSize; + } + netsnmp_assert(pdu->msgMaxSize > 0); + + /* + * allocate initial packet buffer. Buffer will be grown as needed + * while building the packet. + */ + pktbuf_len = SNMP_MIN_MAX_LEN; + if ((pktbuf = (u_char *)malloc(pktbuf_len)) == NULL) { + DEBUGMSGTL(("sess_async_send", + "couldn't malloc initial packet buffer\n")); + session->s_snmp_errno = SNMPERR_MALLOC; + return SNMPERR_MALLOC; + } + +#if TEMPORARILY_DISABLED + /* + * NULL variable are allowed in certain PDU types. + * In particular, SNMPv3 engineID probes are of this form. + * There is an internal PDU flag to indicate that this + * is acceptable, but until the construction of engineID + * probes can be amended to set this flag, we'll simply + * skip this test altogether. + */ + if (pdu->variables == NULL) { + switch (pdu->command) { +#ifndef NETSNMP_NO_WRITE_SUPPORT + case SNMP_MSG_SET: +#endif /* !NETSNMP_NO_WRITE_SUPPORT */ + case SNMP_MSG_GET: + case SNMP_MSG_GETNEXT: + case SNMP_MSG_GETBULK: + case SNMP_MSG_RESPONSE: + case SNMP_MSG_TRAP2: + case SNMP_MSG_REPORT: + case SNMP_MSG_INFORM: + session->s_snmp_errno = snmp_errno = SNMPERR_NO_VARS; + return SNMPERR_NO_VARS; + case SNMP_MSG_TRAP: + break; + } + } +#endif + + + /* + * Build the message to send. If a bulk response is too big, switch to + * forward encoding and set a flag to drop varbinds to make it fit. + */ + do { + packet = pktbuf; + length = offset = 0; + result = netsnmp_build_packet(isp, session, pdu, &pktbuf, &pktbuf_len, + &packet, &length); + if (0 != result) + break; + + if (orig_count) { /* 2nd pass, see how many varbinds remain */ + curr_count = count_varbinds(pdu->variables); + DEBUGMSGTL(("sess_async_send", " vb count: %d -> %d\n", orig_count, + curr_count)); + DEBUGMSGTL(("sess_async_send", " pdu_len: %" NETSNMP_PRIz "d -> %" NETSNMP_PRIz "d (max %ld)\n", + orig_length, length, pdu->msgMaxSize)); + } + + /** if length is less than max size, we're done (success). */ + if (length <= pdu->msgMaxSize) + break; + + /** packet too big. if this is not a bulk request, we're done (err). */ + if (!bulk) { + session->s_snmp_errno = SNMPERR_TOO_LONG; + break; + } + + /** rebuild bulk response with truncation and fixed size */ + pdu->flags |= UCD_MSG_FLAG_FORWARD_ENCODE | UCD_MSG_FLAG_BULK_TOOBIG; + pktbuf_len = pdu->msgMaxSize; + + /** save original number of vabinds & length */ + if (0 == orig_count) { + curr_count = orig_count = count_varbinds(pdu->variables); + orig_length = length; + } + + } while(1); + + DEBUGMSGTL(("sess_async_send", + "final pktbuf_len after building packet %" NETSNMP_PRIz "u\n", + pktbuf_len)); + if (curr_count != orig_count) + DEBUGMSGTL(("sess_async_send", + "sending %d of %d varbinds (-%d) from bulk response\n", + curr_count, orig_count, orig_count - curr_count)); + + if (length > pdu->msgMaxSize) { + DEBUGMSGTL(("sess_async_send", + "length of packet (%" NETSNMP_PRIz "u) exceeded pdu maximum (%lu)\n", + length, pdu->msgMaxSize)); + netsnmp_assert(SNMPERR_TOO_LONG == session->s_snmp_errno); + } + + if ((SNMPERR_TOO_LONG == session->s_snmp_errno) || (result < 0)) { + DEBUGMSGTL(("sess_async_send", "encoding failure\n")); + SNMP_FREE(pktbuf); + return SNMPERR_GENERR; + } + + isp->obuf = pktbuf; + isp->obuf_size = pktbuf_len; + isp->opacket = packet; + isp->opacket_len = length; + + return SNMPERR_SUCCESS; +} + +/* + * These functions send PDUs using an active session: + * snmp_send - traditional API, no callback + * snmp_async_send - traditional API, with callback + * snmp_sess_send - single session API, no callback + * snmp_sess_async_send - single session API, with callback + * + * Call snmp_build to create a serialized packet (the pdu). + * If necessary, set some of the pdu data from the + * session defaults. + * If there is an expected response for this PDU, + * queue a corresponding request on the list + * of outstanding requests for this session, + * and store the callback vectors in the request. + * + * Send the pdu to the target identified by this session. + * Return on success: + * The request id of the pdu is returned, and the pdu is freed. + * Return on failure: + * Zero (0) is returned. + * The caller must call snmp_free_pdu if 0 is returned. + */ +int +snmp_send(netsnmp_session * session, netsnmp_pdu *pdu) +{ + return snmp_async_send(session, pdu, NULL, NULL); +} + +int +snmp_sess_send(void *sessp, netsnmp_pdu *pdu) +{ + return snmp_sess_async_send(sessp, pdu, NULL, NULL); +} + +int +snmp_async_send(netsnmp_session * session, + netsnmp_pdu *pdu, snmp_callback callback, void *cb_data) +{ + void *sessp = snmp_sess_pointer(session); + return snmp_sess_async_send(sessp, pdu, callback, cb_data); +} + +static int +_sess_async_send(void *sessp, + netsnmp_pdu *pdu, snmp_callback callback, void *cb_data) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_session *session; + struct snmp_internal_session *isp; + netsnmp_transport *transport = NULL; + int result; + long reqid; + + if (slp == NULL || NULL == slp->session || NULL ==slp->internal || + NULL == slp->transport) { + return 0; + } + + session = slp->session; + isp = slp->internal; + transport = slp->transport; + + if (NULL == isp->opacket) { + result = _build_initial_pdu_packet(slp, pdu, 0); + if ((SNMPERR_SUCCESS != result) || (NULL == isp->opacket)) { + if (callback) { + switch (session->s_snmp_errno) { + /* + * some of these probably don't make sense here, but + * it's a rough first cut. + */ + case SNMPERR_BAD_ENG_ID: + case SNMPERR_BAD_SEC_LEVEL: + case SNMPERR_UNKNOWN_SEC_MODEL: + case SNMPERR_UNKNOWN_ENG_ID: + case SNMPERR_UNKNOWN_USER_NAME: + case SNMPERR_UNSUPPORTED_SEC_LEVEL: + case SNMPERR_AUTHENTICATION_FAILURE: + case SNMPERR_NOT_IN_TIME_WINDOW: + case SNMPERR_USM_GENERICERROR: + case SNMPERR_USM_UNKNOWNSECURITYNAME: + case SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL: + case SNMPERR_USM_ENCRYPTIONERROR: + case SNMPERR_USM_AUTHENTICATIONFAILURE: + case SNMPERR_USM_PARSEERROR: + case SNMPERR_USM_UNKNOWNENGINEID: + case SNMPERR_USM_NOTINTIMEWINDOW: + callback(NETSNMP_CALLBACK_OP_SEC_ERROR, session, + pdu->reqid, pdu, cb_data); + break; + case SNMPERR_TIMEOUT: /* engineID probe timed out */ + callback(NETSNMP_CALLBACK_OP_TIMED_OUT, session, + pdu->reqid, pdu, cb_data); + break; + default: + callback(NETSNMP_CALLBACK_OP_SEND_FAILED, session, + pdu->reqid, pdu, cb_data); + break; + } + } + /** no packet to send?? */ + return 0; + } + } + + /* + * Send the message. + */ + + DEBUGMSGTL(("sess_process_packet", "sending message id#%ld reqid#%ld len %" + NETSNMP_PRIz "u\n", pdu->msgid, pdu->reqid, isp->opacket_len)); + result = netsnmp_transport_send(transport, isp->opacket, isp->opacket_len, + &(pdu->transport_data), + &(pdu->transport_data_length)); + + SNMP_FREE(isp->obuf); + isp->opacket = NULL; /* opacket was in obuf, so no free needed */ + isp->opacket_len = 0; + + if (result < 0) { + session->s_snmp_errno = SNMPERR_BAD_SENDTO; + session->s_errno = errno; + if (callback) + callback(NETSNMP_CALLBACK_OP_SEND_FAILED, session, + pdu->reqid, pdu, cb_data); + return 0; + } + + reqid = pdu->reqid; + + /* + * Bug 2387: 0 is a valid request id, so since reqid is used as a return + * code with 0 meaning an error, set reqid to 1 if there is no error. This + * does not affect the request id in the packet and fixes a memory leak + * for incoming PDUs with a request id of 0. This could cause some + * confusion if the caller is expecting the request id to match the + * return code, as the documentation states it will. Most example code + * just checks for non-zero, so hopefully this wont be an issue. + */ + if (0 == reqid && (SNMPERR_SUCCESS == session->s_snmp_errno)) + ++reqid; + + /* + * Add to pending requests list if we expect a response. + */ + if (pdu->flags & UCD_MSG_FLAG_EXPECT_RESPONSE) { + netsnmp_request_list *rp; + struct timeval tv; + + rp = (netsnmp_request_list *) calloc(1, + sizeof(netsnmp_request_list)); + if (rp == NULL) { + session->s_snmp_errno = SNMPERR_GENERR; + return 0; + } + + netsnmp_get_monotonic_clock(&tv); + rp->pdu = pdu; + rp->request_id = pdu->reqid; + rp->message_id = pdu->msgid; + rp->callback = callback; + rp->cb_data = cb_data; + rp->retries = 0; + if (pdu->flags & UCD_MSG_FLAG_PDU_TIMEOUT) { + rp->timeout = pdu->time * 1000000L; + } else { + rp->timeout = session->timeout; + } + rp->timeM = tv; + tv.tv_usec += rp->timeout; + tv.tv_sec += tv.tv_usec / 1000000L; + tv.tv_usec %= 1000000L; + rp->expireM = tv; + + /* + * XX lock should be per session ! + */ + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + if (isp->requestsEnd) { + rp->next_request = isp->requestsEnd->next_request; + isp->requestsEnd->next_request = rp; + isp->requestsEnd = rp; + } else { + rp->next_request = isp->requests; + isp->requests = rp; + isp->requestsEnd = rp; + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); + } else { + /* + * No response expected... + */ + if (reqid) { + /* + * Free v1 or v2 TRAP PDU iff no error + */ + snmp_free_pdu(pdu); + } + } + + return reqid; +} + +int +snmp_sess_async_send(void *sessp, + netsnmp_pdu *pdu, + snmp_callback callback, void *cb_data) +{ + int rc; + + if (sessp == NULL) { + snmp_errno = SNMPERR_BAD_SESSION; /*MTCRITICAL_RESOURCE */ + return (0); + } + /* + * send pdu + */ + rc = _sess_async_send(sessp, pdu, callback, cb_data); + if (rc == 0) { + struct session_list *psl; + netsnmp_session *pss; + psl = (struct session_list *) sessp; + pss = psl->session; + SET_SNMP_ERROR(pss->s_snmp_errno); + } + return rc; +} + + +/* + * Frees the variable and any malloc'd data associated with it. + */ +void +snmp_free_var_internals(netsnmp_variable_list * var) +{ + if (!var) + return; + + if (var->name != var->name_loc) + SNMP_FREE(var->name); + if (var->val.string != var->buf) + SNMP_FREE(var->val.string); + if (var->data) { + if (var->dataFreeHook) { + var->dataFreeHook(var->data); + var->data = NULL; + } else { + SNMP_FREE(var->data); + } + } +} + +void +snmp_free_var(netsnmp_variable_list * var) +{ + snmp_free_var_internals(var); + free((char *) var); +} + +void +snmp_free_varbind(netsnmp_variable_list * var) +{ + netsnmp_variable_list *ptr; + while (var) { + ptr = var->next_variable; + snmp_free_var(var); + var = ptr; + } +} + +/* + * Frees the pdu and any malloc'd data associated with it. + */ +void +snmp_free_pdu(netsnmp_pdu *pdu) +{ + struct snmp_secmod_def *sptr; + + if (!pdu) + return; + + /* + * If the command field is empty, that probably indicates + * that this PDU structure has already been freed. + * Log a warning and return (rather than freeing things again) + * + * Note that this does not pick up dual-frees where the + * memory is set to random junk, which is probably more serious. + * + * rks: while this is a good idea, there are two problems. + * 1) agentx sets command to 0 in some cases + * 2) according to Wes, a bad decode of a v3 message could + * result in a 0 at this offset. + * so I'm commenting it out until a better solution is found. + * note that I'm leaving the memset, below.... + * + if (pdu->command == 0) { + snmp_log(LOG_WARNING, "snmp_free_pdu probably called twice\n"); + return; + } + */ + if ((sptr = find_sec_mod(pdu->securityModel)) != NULL && + sptr->pdu_free != NULL) { + (*sptr->pdu_free) (pdu); + } + snmp_free_varbind(pdu->variables); + SNMP_FREE(pdu->enterprise); + SNMP_FREE(pdu->community); + SNMP_FREE(pdu->contextEngineID); + SNMP_FREE(pdu->securityEngineID); + SNMP_FREE(pdu->contextName); + SNMP_FREE(pdu->securityName); + SNMP_FREE(pdu->transport_data); + memset(pdu, 0, sizeof(netsnmp_pdu)); + free((char *) pdu); +} + +netsnmp_pdu * +snmp_create_sess_pdu(netsnmp_transport *transport, void *opaque, + size_t olength) +{ + netsnmp_pdu *pdu = (netsnmp_pdu *)calloc(1, sizeof(netsnmp_pdu)); + if (pdu == NULL) { + DEBUGMSGTL(("sess_process_packet", "can't malloc space for PDU\n")); + return NULL; + } + + /* + * Save the transport-level data specific to this reception (e.g. UDP + * source address). + */ + + pdu->transport_data = opaque; + pdu->transport_data_length = olength; + pdu->tDomain = transport->domain; + pdu->tDomainLen = transport->domain_length; + return pdu; +} + + +/* + * This function parses a packet into a PDU + */ +static netsnmp_pdu * +_sess_process_packet_parse_pdu(void *sessp, netsnmp_session * sp, + struct snmp_internal_session *isp, + netsnmp_transport *transport, + void *opaque, int olength, + u_char * packetptr, int length) +{ + netsnmp_pdu *pdu; + int ret = 0; + int dump = 0, filter = 0; + + debug_indent_reset(); + + DEBUGMSGTL(("sess_process_packet", + "session %p fd %d pkt %p length %d\n", sessp, + transport->sock, packetptr, length)); + + dump = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DUMP_PACKET); +#ifndef NETSNMP_FEATURE_REMOVE_FILTER_SOURCE + filter = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_FILTER_TYPE); +#endif + if (dump || filter) { + int filtered = 0; + char *addrtxt = netsnmp_transport_peer_string(transport, opaque, olength); + snmp_log(LOG_DEBUG, "\nReceived %d byte packet from %s\n", + length, addrtxt); + + if (dump) + xdump(packetptr, length, ""); + +#ifndef NETSNMP_FEATURE_REMOVE_FILTER_SOURCE + if (filter) { + char *sourceaddr = NULL, *c = strchr(addrtxt, '['); + const char *dropstr = NULL; + if (c) { + sourceaddr = ++c; + c = strchr(sourceaddr, ']'); + if (c) + *c = 0; + filtered = netsnmp_transport_filter_check(sourceaddr); + } + if ((filter == -1) && filtered) + dropstr = "matched blacklist"; + else if ((filter == 1) && !filtered) + dropstr = "didn't match whitelist"; + if (dropstr) { + DEBUGMSGTL(("sess_process_packet:filter", + "packet from %s %s\n", + sourceaddr ? sourceaddr : "UNKNOWN", dropstr)); + SNMP_FREE(opaque); + SNMP_FREE(addrtxt); + return NULL; + } + } +#endif + + SNMP_FREE(addrtxt); + } + + /* + * Do transport-level filtering (e.g. IP-address based allow/deny). + */ + + if (isp->hook_pre) { + if (isp->hook_pre(sp, transport, opaque, olength) == 0) { + DEBUGMSGTL(("sess_process_packet", "pre-parse fail\n")); + SNMP_FREE(opaque); + return NULL; + } + } + + if (isp->hook_create_pdu) { + pdu = isp->hook_create_pdu(transport, opaque, olength); + } else { + pdu = snmp_create_sess_pdu(transport, opaque, olength); + } + + if (pdu == NULL) { + snmp_log(LOG_ERR, "pdu failed to be created\n"); + SNMP_FREE(opaque); + return NULL; + } + + /* if the transport was a magic tunnel, mark the PDU as having come + through one. */ + if (transport->flags & NETSNMP_TRANSPORT_FLAG_TUNNELED) { + pdu->flags |= UCD_MSG_FLAG_TUNNELED; + } + + if (isp->hook_parse) { + ret = isp->hook_parse(sp, pdu, packetptr, length); + } else { + ret = snmp_parse(sessp, sp, pdu, packetptr, length); + } + + DEBUGMSGTL(("sess_process_packet", "received message id#%ld reqid#%ld len " + "%u\n", pdu->msgid, pdu->reqid, length)); + + if (ret != SNMP_ERR_NOERROR) { + DEBUGMSGTL(("sess_process_packet", "parse fail\n")); + } + + if (isp->hook_post) { + if (isp->hook_post(sp, pdu, ret) == 0) { + DEBUGMSGTL(("sess_process_packet", "post-parse fail\n")); + ret = SNMPERR_ASN_PARSE_ERR; + } + } + + if (ret != SNMP_ERR_NOERROR) { + /* + * Call the security model to free any securityStateRef supplied w/ msg. + */ + if (pdu->securityStateRef != NULL) { + free_securityStateRef(pdu); + } + snmp_free_pdu(pdu); + return NULL; + } + + return pdu; +} + +/* + * This function processes a PDU and calls the relevant callbacks. + */ +static int +_sess_process_packet_handle_pdu(void *sessp, netsnmp_session * sp, + struct snmp_internal_session *isp, + netsnmp_transport *transport, netsnmp_pdu *pdu) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_request_list *rp, *orp = NULL; + int handled = 0; + + if (pdu->flags & UCD_MSG_FLAG_RESPONSE_PDU) { + /* + * Call USM to free any securityStateRef supplied with the message. + */ + if (pdu->securityStateRef) { + free_securityStateRef(pdu); + } + + for (rp = isp->requests; rp; orp = rp, rp = rp->next_request) { + snmp_callback callback; + void *magic; + + if (pdu->version == SNMP_VERSION_3) { + /* + * msgId must match for v3 messages. + */ + if (rp->message_id != pdu->msgid) { + DEBUGMSGTL(("sess_process_packet", "unmatched msg id: %ld != %ld\n", + rp->message_id, pdu->msgid)); + continue; + } + + /* + * Check that message fields match original, if not, no further + * processing. + */ + if (!snmpv3_verify_msg(rp, pdu)) { + break; + } + } else { + if (rp->request_id != pdu->reqid) { + continue; + } + } + + if (rp->callback) { + callback = rp->callback; + magic = rp->cb_data; + } else { + callback = sp->callback; + magic = sp->callback_magic; + } + handled = 1; + + /* + * MTR snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); ?* XX lock + * should be per session ! + */ + + if (callback == NULL + || callback(NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE, sp, + pdu->reqid, pdu, magic) == 1) { + if (pdu->command == SNMP_MSG_REPORT) { + if (sp->s_snmp_errno == SNMPERR_NOT_IN_TIME_WINDOW || + snmpv3_get_report_type(pdu) == + SNMPERR_NOT_IN_TIME_WINDOW) { + /* + * trigger immediate retry on recoverable Reports + * * (notInTimeWindow), incr_retries == TRUE to prevent + * * inifinite resend + */ + if (rp->retries <= sp->retries) { + snmp_resend_request(slp, rp, TRUE); + break; + } else { + /* We're done with retries, so no longer waiting for a response */ + if (callback) { + callback(NETSNMP_CALLBACK_OP_SEC_ERROR, sp, + pdu->reqid, pdu, magic); + } + } + } else { + if (SNMPV3_IGNORE_UNAUTH_REPORTS) { + break; + } else { /* We're done with retries */ + if (callback) { + callback(NETSNMP_CALLBACK_OP_SEC_ERROR, sp, + pdu->reqid, pdu, magic); + } + } + } + + /* + * Handle engineID discovery. + */ + if (!sp->securityEngineIDLen && pdu->securityEngineIDLen) { + sp->securityEngineID = + (u_char *) malloc(pdu->securityEngineIDLen); + if (sp->securityEngineID == NULL) { + /* + * TODO FIX: recover after message callback *? + */ + snmp_log(LOG_ERR, "malloc failed handling pdu\n"); + snmp_free_pdu(pdu); + return -1; + } + memcpy(sp->securityEngineID, pdu->securityEngineID, + pdu->securityEngineIDLen); + sp->securityEngineIDLen = pdu->securityEngineIDLen; + if (!sp->contextEngineIDLen) { + sp->contextEngineID = + (u_char *) malloc(pdu-> + securityEngineIDLen); + if (sp->contextEngineID == NULL) { + /* + * TODO FIX: recover after message callback *? + */ + snmp_log(LOG_ERR, "malloc failed handling pdu\n"); + snmp_free_pdu(pdu); + return -1; + } + memcpy(sp->contextEngineID, + pdu->securityEngineID, + pdu->securityEngineIDLen); + sp->contextEngineIDLen = + pdu->securityEngineIDLen; + } + } + } + + /* + * Successful, so delete request. + */ + if (orp) + orp->next_request = rp->next_request; + else + isp->requests = rp->next_request; + if (isp->requestsEnd == rp) + isp->requestsEnd = orp; + snmp_free_pdu(rp->pdu); + free(rp); + /* + * There shouldn't be any more requests with the same reqid. + */ + break; + } + /* + * MTR snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); ?* XX lock should be per session ! + */ + } + } else { + if (sp->callback) { + /* + * MTR snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + */ + handled = 1; + sp->callback(NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE, + sp, pdu->reqid, pdu, sp->callback_magic); + /* + * MTR snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); + */ + } + } + + /* + * Call USM to free any securityStateRef supplied with the message. + */ + if (pdu->securityStateRef && pdu->command == SNMP_MSG_TRAP2) + free_securityStateRef(pdu); + + if (!handled) { + if (sp->flags & SNMP_FLAGS_SHARED_SOCKET) + return -2; + snmp_increment_statistic(STAT_SNMPUNKNOWNPDUHANDLERS); + DEBUGMSGTL(("sess_process_packet", "unhandled PDU\n")); + } + + snmp_free_pdu(pdu); + return 0; +} + +/* + * This function processes a complete (according to asn_check_packet or the + * AgentX equivalent) packet, parsing it into a PDU and calling the relevant + * callbacks. On entry, packetptr points at the packet in the session's + * buffer and length is the length of the packet. Return codes: + * 0: pdu handled (pdu deleted) + * -1: parse error (pdu deleted) + * -2: pdu not found for shared session (pdu NOT deleted) + */ +static int +_sess_process_packet(void *sessp, netsnmp_session * sp, + struct snmp_internal_session *isp, + netsnmp_transport *transport, + void *opaque, int olength, + u_char * packetptr, int length) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_pdu *pdu; + int rc; + + pdu = _sess_process_packet_parse_pdu(sessp, sp, isp, transport, opaque, + olength, packetptr, length); + if (NULL == pdu) + return -1; + + /* + * find session to process pdu. usually that will be the current session, + * but with the introduction of shared transports, another session may + * have the same socket. + */ + do { + rc = _sess_process_packet_handle_pdu(sessp, sp, isp, transport, pdu); + if (-2 != rc || !(transport->flags & NETSNMP_TRANSPORT_FLAG_SHARED)) + break; + + /** -2 means pdu not in request list. check other sessions */ + do { + slp = slp->next; + } while (slp && slp->transport->sock != transport->sock); + if (!slp) + break; /* no more sessions with same socket */ + + sp = slp->session; + isp = slp->internal; + transport = slp->transport; + } while(slp); + + if (-2 == rc) { /* did not find session for pdu */ + snmp_increment_statistic(STAT_SNMPUNKNOWNPDUHANDLERS); + DEBUGMSGTL(("sess_process_packet", "unhandled PDU\n")); + snmp_free_pdu(pdu); + } + + return rc; +} + +/* + * Checks to see if any of the fd's set in the fdset belong to + * snmp. Each socket with it's fd set has a packet read from it + * and snmp_parse is called on the packet received. The resulting pdu + * is passed to the callback routine for that session. If the callback + * routine returns successfully, the pdu and it's request are deleted. + */ +void +snmp_read(fd_set * fdset) +{ + netsnmp_large_fd_set lfdset; + + netsnmp_large_fd_set_init(&lfdset, FD_SETSIZE); + netsnmp_copy_fd_set_to_large_fd_set(&lfdset, fdset); + snmp_read2(&lfdset); + netsnmp_large_fd_set_cleanup(&lfdset); +} + +void +snmp_read2(netsnmp_large_fd_set * fdset) +{ + struct session_list *slp; + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + for (slp = Sessions; slp; slp = slp->next) { + snmp_sess_read2((void *) slp, fdset); + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); +} + +/* + * accept new connections + * returns 0 if success, -1 if fail + */ +static int +_sess_read_accept(void *sessp) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_session *sp = slp ? slp->session : NULL; + struct snmp_internal_session *isp = slp ? slp->internal : NULL; + netsnmp_transport *transport = slp ? slp->transport : NULL; + netsnmp_transport *new_transport; + struct session_list *nslp; + int data_sock; + + if (NULL == sessp || NULL == sp || NULL == transport || NULL == isp || + !(transport->flags & NETSNMP_TRANSPORT_FLAG_LISTEN)) + return -1; + + data_sock = transport->f_accept(transport); + if (data_sock < 0) { + sp->s_snmp_errno = SNMPERR_BAD_RECVFROM; + sp->s_errno = errno; + snmp_set_detail(strerror(errno)); + return -1; + } + + /* + * We've successfully accepted a new stream-based connection. + * It's not too clear what should happen here if we are using the + * single-session API at this point. Basically a "session + * accepted" callback is probably needed to hand the new session + * over to the application. + * + * However, for now, as in th original snmp_api, we will ASSUME + * that we're using the traditional API, and simply add the new + * session to the list. Note we don't have to get the Session + * list lock here, because under that assumption we already hold + * it (this is also why we don't just use snmp_add). + * + * The moral of the story is: don't use listening stream-based + * transports in a multi-threaded environment because something + * will go HORRIBLY wrong (and also that SNMP/TCP is not trivial). + * + * Another open issue: what should happen to sockets that have + * been accept()ed from a listening socket when that original + * socket is closed? If they are left open, then attempting to + * re-open the listening socket will fail, which is semantically + * confusing. Perhaps there should be some kind of chaining in + * the transport structure so that they can all be closed. + * Discuss. ;-) + */ + new_transport=netsnmp_transport_copy(transport); + if (new_transport == NULL) { + sp->s_snmp_errno = SNMPERR_MALLOC; + sp->s_errno = errno; + snmp_set_detail(strerror(errno)); + return -1; + } + nslp = NULL; + + new_transport->sock = data_sock; + new_transport->flags &= ~NETSNMP_TRANSPORT_FLAG_LISTEN; + + nslp = (struct session_list *) + snmp_sess_add_ex(sp, new_transport, isp->hook_pre, isp->hook_parse, + isp->hook_post, isp->hook_build, + isp->hook_realloc_build, isp->check_packet, + isp->hook_create_pdu); + + if (nslp != NULL) { + snmp_session_insert(nslp); + /** Tell the new session about its existance if possible. */ + DEBUGMSGTL(("sess_read", + "perform callback with op=CONNECT\n")); + (void)nslp->session->callback(NETSNMP_CALLBACK_OP_CONNECT, + nslp->session, 0, NULL, + sp->callback_magic); + } + + return 0; +} + +/* + * Same as snmp_read, but works just one non-stream session. + * returns 0 if success, -1 if protocol err, -2 if no packet to process + * MTR: can't lock here and at snmp_read + * Beware recursive send maybe inside snmp_read callback function. + */ +static int +_sess_read_dgram_packet(void *sessp, netsnmp_large_fd_set * fdset, + snmp_rcv_packet *rcvp) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_session *sp = slp ? slp->session : NULL; + struct snmp_internal_session *isp = slp ? slp->internal : NULL; + netsnmp_transport *transport = slp ? slp->transport : NULL; + + if (!sp || !isp || !transport || !rcvp ) { + DEBUGMSGTL(("sess_read_packet", "missing arguments\n")); + return -2; + } + + if (transport->flags & NETSNMP_TRANSPORT_FLAG_STREAM) + return -2; + + if (NULL != rcvp->packet) { + snmp_log(LOG_WARNING, "overwriting existing saved packet; sess %p\n", + sp); + SNMP_FREE(rcvp->packet); + } + + if ((rcvp->packet = (u_char *) malloc(SNMP_MAX_RCV_MSG_SIZE)) == NULL) { + DEBUGMSGTL(("sess_read_packet", "can't malloc %u bytes for packet\n", + SNMP_MAX_RCV_MSG_SIZE)); + return -2; + } + + rcvp->packet_len = netsnmp_transport_recv(transport, rcvp->packet, + SNMP_MAX_RCV_MSG_SIZE, + &rcvp->opaque, &rcvp->olength); + if (rcvp->packet_len == -1) { + sp->s_snmp_errno = SNMPERR_BAD_RECVFROM; + sp->s_errno = errno; + snmp_set_detail(strerror(errno)); + SNMP_FREE(rcvp->packet); + SNMP_FREE(rcvp->opaque); + return -1; + } + + /** clear so any other sess sharing this socket won't try reading again */ + NETSNMP_LARGE_FD_CLR(transport->sock, fdset); + + if (0 == rcvp->packet_len && + transport->flags & NETSNMP_TRANSPORT_FLAG_EMPTY_PKT) { + /* this allows for a transport that needs to return from + * packet processing that doesn't necessarily have any + * consumable data in it. */ + + /* reset the flag since it's a per-message flag */ + transport->flags &= (~NETSNMP_TRANSPORT_FLAG_EMPTY_PKT); + + /** free packet */ + SNMP_FREE(rcvp->packet); + SNMP_FREE(rcvp->opaque); + + return -2; + } + + return 0; +} + +/* + * Same as snmp_read, but works just one session. + * returns 0 if success, -1 if fail + * MTR: can't lock here and at snmp_read + * Beware recursive send maybe inside snmp_read callback function. + */ +int +_sess_read(void *sessp, netsnmp_large_fd_set * fdset) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_session *sp = slp ? slp->session : NULL; + struct snmp_internal_session *isp = slp ? slp->internal : NULL; + netsnmp_transport *transport = slp ? slp->transport : NULL; + size_t pdulen = 0, rxbuf_len = SNMP_MAX_RCV_MSG_SIZE; + u_char *rxbuf = NULL; + int length = 0, olength = 0, rc = 0; + void *opaque = NULL; + + if (NULL == slp || NULL == sp || NULL == isp || NULL == transport) { + snmp_log(LOG_ERR, "bad parameters to _sess_read\n"); + return SNMPERR_GENERR; + } + + /* to avoid subagent crash */ + if (transport->sock < 0) { + snmp_log (LOG_INFO, "transport->sock got negative fd value %d\n", + transport->sock); + return 0; + } + + if (!fdset || !(NETSNMP_LARGE_FD_ISSET(transport->sock, fdset))) { + DEBUGMSGTL(("sess_read", "not reading %d (fdset %p set %d)\n", + transport->sock, fdset, + fdset ? NETSNMP_LARGE_FD_ISSET(transport->sock, fdset) + : -9)); + return 0; + } + + sp->s_snmp_errno = 0; + sp->s_errno = 0; + + if (transport->flags & NETSNMP_TRANSPORT_FLAG_LISTEN) + return _sess_read_accept(sessp); + + if (!(transport->flags & NETSNMP_TRANSPORT_FLAG_STREAM)) { + snmp_rcv_packet rcvp; + memset(&rcvp, 0x0, sizeof(rcvp)); + + /** read the packet */ + rc = _sess_read_dgram_packet(sessp, fdset, &rcvp); + if (-1 == rc) /* protocol error */ + return -1; + else if (-2 == rc) /* no packet to process */ + return 0; + + rc = _sess_process_packet(sessp, sp, isp, transport, + rcvp.opaque, rcvp.olength, + rcvp.packet, rcvp.packet_len); + SNMP_FREE(rcvp.packet); + /** opaque is freed in _sess_process_packet */ + return rc; + } + + /** stream transport */ + + if (isp->packet == NULL) { + /* + * We have no saved packet. Allocate one. + */ + if ((isp->packet = (u_char *) malloc(rxbuf_len)) == NULL) { + DEBUGMSGTL(("sess_read", "can't malloc %" NETSNMP_PRIz + "u bytes for rxbuf\n", rxbuf_len)); + return 0; + } else { + rxbuf = isp->packet; + isp->packet_size = rxbuf_len; + isp->packet_len = 0; + } + } else { + /* + * We have saved a partial packet from last time. Extend that, if + * necessary, and receive new data after the old data. + */ + u_char *newbuf; + + if (isp->packet_size < isp->packet_len + rxbuf_len) { + newbuf = + (u_char *) realloc(isp->packet, + isp->packet_len + rxbuf_len); + if (newbuf == NULL) { + DEBUGMSGTL(("sess_read", + "can't malloc %" NETSNMP_PRIz + "u more for rxbuf (%" NETSNMP_PRIz "u tot)\n", + rxbuf_len, isp->packet_len + rxbuf_len)); + return 0; + } else { + isp->packet = newbuf; + isp->packet_size = isp->packet_len + rxbuf_len; + rxbuf = isp->packet + isp->packet_len; + } + } else { + rxbuf = isp->packet + isp->packet_len; + rxbuf_len = isp->packet_size - isp->packet_len; + } + } + + length = netsnmp_transport_recv(transport, rxbuf, rxbuf_len, &opaque, + &olength); + + if (0 == length && transport->flags & NETSNMP_TRANSPORT_FLAG_EMPTY_PKT) { + /* this allows for a transport that needs to return from + * packet processing that doesn't necessarily have any + * consumable data in it. */ + + /* reset the flag since it's a per-message flag */ + transport->flags &= (~NETSNMP_TRANSPORT_FLAG_EMPTY_PKT); + + return 0; + } + + /* + * Remote end closed connection. + */ + if (length <= 0) { + /* + * Alert the application if possible. + */ + if (sp->callback != NULL) { + DEBUGMSGTL(("sess_read", "perform callback with op=DISCONNECT\n")); + (void) sp->callback(NETSNMP_CALLBACK_OP_DISCONNECT, sp, 0, + NULL, sp->callback_magic); + } + /* + * Close socket and mark session for deletion. + */ + DEBUGMSGTL(("sess_read", "fd %d closed\n", transport->sock)); + transport->f_close(transport); + SNMP_FREE(isp->packet); + SNMP_FREE(opaque); + return -1; + } + + { + u_char *pptr = isp->packet; + void *ocopy = NULL; + + isp->packet_len += length; + + while (isp->packet_len > 0) { + + /* + * Get the total data length we're expecting (and need to wait + * for). + */ + if (isp->check_packet) { + pdulen = isp->check_packet(pptr, isp->packet_len); + } else { + pdulen = asn_check_packet(pptr, isp->packet_len); + } + + DEBUGMSGTL(("sess_read", + " loop packet_len %" NETSNMP_PRIz "u, PDU length %" + NETSNMP_PRIz "u\n", isp->packet_len, pdulen)); + + if (pdulen > SNMP_MAX_PACKET_LEN) { + /* + * Illegal length, drop the connection. + */ + snmp_log(LOG_ERR, + "Received broken packet. Closing session.\n"); + if (sp->callback != NULL) { + DEBUGMSGTL(("sess_read", + "perform callback with op=DISCONNECT\n")); + (void)sp->callback(NETSNMP_CALLBACK_OP_DISCONNECT, + sp, 0, NULL, sp->callback_magic); + } + DEBUGMSGTL(("sess_read", "fd %d closed\n", transport->sock)); + transport->f_close(transport); + SNMP_FREE(opaque); + /** XXX-rks: why no SNMP_FREE(isp->packet); ?? */ + return -1; + } + + if (pdulen > isp->packet_len || pdulen == 0) { + /* + * We don't have a complete packet yet. If we've already + * processed a packet, break out so we'll shift this packet + * to the start of the buffer. If we're already at the + * start, simply return and wait for more data to arrive. + */ + DEBUGMSGTL(("sess_read", + "pkt not complete (need %" NETSNMP_PRIz "u got %" + NETSNMP_PRIz "u so far)\n", pdulen, + isp->packet_len)); + + if (pptr != isp->packet) + break; /* opaque freed for us outside of loop. */ + + SNMP_FREE(opaque); + return 0; + } + + /* We have *at least* one complete packet in the buffer now. If + we have possibly more than one packet, we must copy the opaque + pointer because we may need to reuse it for a later packet. */ + + if (pdulen < isp->packet_len) { + if (olength > 0 && opaque != NULL) { + ocopy = malloc(olength); + if (ocopy != NULL) { + memcpy(ocopy, opaque, olength); + } + } + } else if (pdulen == isp->packet_len) { + /* Common case -- exactly one packet. No need to copy the + opaque pointer. */ + ocopy = opaque; + opaque = NULL; + } + + if ((rc = _sess_process_packet(sessp, sp, isp, transport, + ocopy, ocopy?olength:0, pptr, + pdulen))) { + /* + * Something went wrong while processing this packet -- set the + * errno. + */ + if (sp->s_snmp_errno != 0) { + SET_SNMP_ERROR(sp->s_snmp_errno); + } + } + + /* ocopy has been free()d by _sess_process_packet by this point, + so set it to NULL. */ + + ocopy = NULL; + + /* Step past the packet we've just dealt with. */ + + pptr += pdulen; + isp->packet_len -= pdulen; + } + + /* If we had more than one packet, then we were working with copies + of the opaque pointer, so we still need to free() the opaque + pointer itself. */ + + SNMP_FREE(opaque); + + if (isp->packet_len >= SNMP_MAX_PACKET_LEN) { + /* + * Obviously this should never happen! + */ + snmp_log(LOG_ERR, + "too large packet_len = %" NETSNMP_PRIz + "u, dropping connection %d\n", + isp->packet_len, transport->sock); + transport->f_close(transport); + /** XXX-rks: why no SNMP_FREE(isp->packet); ?? */ + return -1; + } else if (isp->packet_len == 0) { + /* + * This is good: it means the packet buffer contained an integral + * number of PDUs, so we don't have to save any data for next + * time. We can free() the buffer now to keep the memory + * footprint down. + */ + SNMP_FREE(isp->packet); + isp->packet_size = 0; + isp->packet_len = 0; + return rc; + } + + /* + * If we get here, then there is a partial packet of length + * isp->packet_len bytes starting at pptr left over. Move that to the + * start of the buffer, and then realloc() the buffer down to size to + * reduce the memory footprint. + */ + + memmove(isp->packet, pptr, isp->packet_len); + DEBUGMSGTL(("sess_read", + "end: memmove(%p, %p, %" NETSNMP_PRIz "u); realloc(%p, %" + NETSNMP_PRIz "u)\n", + isp->packet, pptr, isp->packet_len, + isp->packet, isp->packet_len)); + + if ((rxbuf = (u_char *)realloc(isp->packet, isp->packet_len)) == NULL) { + /* + * I don't see why this should ever fail, but it's not a big deal. + */ + DEBUGMSGTL(("sess_read", "realloc() failed\n")); + } else { + DEBUGMSGTL(("sess_read", "realloc() okay, old buffer %p, new %p\n", + isp->packet, rxbuf)); + isp->packet = rxbuf; + isp->packet_size = isp->packet_len; + } + } + + return rc; +} + + + +/* + * returns 0 if success, -1 if fail + */ +int +snmp_sess_read(void *sessp, fd_set * fdset) +{ + int rc; + netsnmp_large_fd_set lfdset; + + netsnmp_large_fd_set_init(&lfdset, FD_SETSIZE); + netsnmp_copy_fd_set_to_large_fd_set(&lfdset, fdset); + rc = snmp_sess_read2(sessp, &lfdset); + netsnmp_large_fd_set_cleanup(&lfdset); + return rc; +} + +int +snmp_sess_read2(void *sessp, netsnmp_large_fd_set * fdset) +{ + struct session_list *psl; + netsnmp_session *pss; + int rc; + + rc = _sess_read(sessp, fdset); + psl = (struct session_list *) sessp; + pss = psl->session; + if (rc && pss->s_snmp_errno) { + SET_SNMP_ERROR(pss->s_snmp_errno); + } + return rc; +} + + +/** + * Returns info about what snmp requires from a select statement. + * numfds is the number of fds in the list that are significant. + * All file descriptors opened for SNMP are OR'd into the fdset. + * If activity occurs on any of these file descriptors, snmp_read + * should be called with that file descriptor set + * + * The timeout is the latest time that SNMP can wait for a timeout. The + * select should be done with the minimum time between timeout and any other + * timeouts necessary. This should be checked upon each invocation of select. + * If a timeout is received, snmp_timeout should be called to check if the + * timeout was for SNMP. (snmp_timeout is idempotent) + * + * The value of block indicates how the timeout value is interpreted. + * If block is true on input, the timeout value will be treated as undefined, + * but it must be available for setting in snmp_select_info. On return, + * block is set to true if the value returned for timeout is undefined; + * when block is set to false, timeout may be used as a parmeter to 'select'. + * + * snmp_select_info returns the number of open sockets. (i.e. The number of + * sessions open) + * + * @see See also snmp_sess_select_info2_flags(). + */ +int +snmp_select_info(int *numfds, fd_set *fdset, struct timeval *timeout, + int *block) +{ + return snmp_sess_select_info(NULL, numfds, fdset, timeout, block); +} + +/** + * @see See also snmp_sess_select_info2_flags(). + */ +int +snmp_select_info2(int *numfds, netsnmp_large_fd_set *fdset, + struct timeval *timeout, int *block) +{ + return snmp_sess_select_info2(NULL, numfds, fdset, timeout, block); +} + +/** + * @see See also snmp_sess_select_info2_flags(). + */ +int +snmp_sess_select_info(void *sessp, int *numfds, fd_set *fdset, + struct timeval *timeout, int *block) +{ + return snmp_sess_select_info_flags(sessp, numfds, fdset, timeout, block, + NETSNMP_SELECT_NOFLAGS); +} + +/** + * @see See also snmp_sess_select_info2_flags(). + */ +int +snmp_sess_select_info_flags(void *sessp, int *numfds, fd_set *fdset, + struct timeval *timeout, int *block, int flags) +{ + int rc; + netsnmp_large_fd_set lfdset; + + netsnmp_large_fd_set_init(&lfdset, FD_SETSIZE); + netsnmp_copy_fd_set_to_large_fd_set(&lfdset, fdset); + rc = snmp_sess_select_info2_flags(sessp, numfds, &lfdset, timeout, + block, flags); + if (netsnmp_copy_large_fd_set_to_fd_set(fdset, &lfdset) < 0) { + snmp_log(LOG_ERR, + "Use snmp_sess_select_info2() for processing" + " large file descriptors\n"); + } + netsnmp_large_fd_set_cleanup(&lfdset); + return rc; +} + +/** + * @see See also snmp_sess_select_info2_flags(). + */ +int +snmp_sess_select_info2(void *sessp, int *numfds, netsnmp_large_fd_set *fdset, + struct timeval *timeout, int *block) +{ + return snmp_sess_select_info2_flags(sessp, numfds, fdset, timeout, block, + NETSNMP_SELECT_NOFLAGS); +} + +/** + * Compute/update the arguments to be passed to select(). + * + * @param[in] sessp Which sessions to process: either a pointer to a + * specific session or NULL which means to process all sessions. + * @param[in,out] numfds On POSIX systems one more than the the largest file + * descriptor that is present in *fdset. On systems that use Winsock (MinGW + * and MSVC), do not use the value written into *numfds. + * @param[in,out] fdset A large file descriptor set to which all file + * descriptors will be added that are associated with one of the examined + * sessions. + * @param[in,out] timeout On input, if *block = 1, the maximum time the caller + * will block while waiting for Net-SNMP activity. On output, if this function + * has set *block to 0, the maximum time the caller is allowed to wait before + * invoking the Net-SNMP processing functions (snmp_read(), snmp_timeout() + * and run_alarms()). If this function has set *block to 1, *timeout won't + * have been modified and no alarms are active. + * @param[in,out] block On input, whether the caller prefers to block forever + * when no alarms are active. On output, 0 means that no alarms are active + * nor that there is a timeout pending for any of the processed sessions. + * @param[in] flags Either 0 or NETSNMP_SELECT_NOALARMS. + * + * @return Number of sessions processed by this function. + * + * @see See also agent_check_and_process() for an example of how to use this + * function. + */ +int +snmp_sess_select_info2_flags(void *sessp, int *numfds, + netsnmp_large_fd_set * fdset, + struct timeval *timeout, int *block, int flags) +{ + struct session_list *slp, *next = NULL; + netsnmp_request_list *rp; + struct timeval now, earliest, alarm_tm; + int active = 0, requests = 0; + int next_alarm = 0; + + timerclear(&earliest); + + /* + * For each session examined, add its socket to the fdset, + * and if it is the earliest timeout to expire, mark it as lowest. + * If a single session is specified, do just for that session. + */ + + DEBUGMSGTL(("sess_select", "for %s session%s: ", + sessp ? "single" : "all", sessp ? "" : "s")); + + for (slp = sessp ? sessp : Sessions; slp; slp = next) { + next = slp->next; + + if (slp->transport == NULL) { + /* + * Close in progress -- skip this one. + */ + DEBUGMSG(("sess_select", "skip ")); + continue; + } + + if (slp->transport->sock == -1) { + /* + * This session was marked for deletion. + */ + DEBUGMSG(("sess_select", "delete\n")); + if (sessp == NULL) { + snmp_close(slp->session); + } else { + snmp_sess_close(slp); + } + DEBUGMSGTL(("sess_select", "for %s session%s: ", + sessp ? "single" : "all", sessp ? "" : "s")); + continue; + } + + DEBUGMSG(("sess_select", "%d ", slp->transport->sock)); + if ((slp->transport->sock + 1) > *numfds) { + *numfds = (slp->transport->sock + 1); + } + + NETSNMP_LARGE_FD_SET(slp->transport->sock, fdset); + if (slp->internal != NULL && slp->internal->requests) { + /* + * Found another session with outstanding requests. + */ + requests++; + for (rp = slp->internal->requests; rp; rp = rp->next_request) { + if (!timerisset(&earliest) + || (timerisset(&rp->expireM) + && timercmp(&rp->expireM, &earliest, <))) { + earliest = rp->expireM; + DEBUGMSG(("verbose:sess_select","(to in %d.%06d sec) ", + (int)earliest.tv_sec, (int)earliest.tv_usec)); + } + } + } + + active++; + if (sessp) { + /* + * Single session processing. + */ + break; + } + } + DEBUGMSG(("sess_select", "\n")); + + netsnmp_get_monotonic_clock(&now); + + if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_ALARM_DONT_USE_SIG) && + !(flags & NETSNMP_SELECT_NOALARMS)) { + next_alarm = netsnmp_get_next_alarm_time(&alarm_tm, &now); + if (next_alarm) + DEBUGMSGT(("sess_select","next alarm at %ld.%06ld sec\n", + (long)alarm_tm.tv_sec, (long)alarm_tm.tv_usec)); + } + if (next_alarm == 0 && requests == 0) { + /* + * If none are active, skip arithmetic. + */ + DEBUGMSGT(("sess_select","blocking:no session requests or alarms.\n")); + *block = 1; /* can block - timeout value is undefined if no requests */ + return active; + } + + if (next_alarm && + (!timerisset(&earliest) || timercmp(&alarm_tm, &earliest, <))) + earliest = alarm_tm; + + NETSNMP_TIMERSUB(&earliest, &now, &earliest); + if (earliest.tv_sec < 0) { + time_t overdue_ms = -(earliest.tv_sec * 1000 + earliest.tv_usec / 1000); + if (overdue_ms >= 10) + DEBUGMSGT(("verbose:sess_select","timer overdue by %ld ms\n", + (long) overdue_ms)); + timerclear(&earliest); + } else { + DEBUGMSGT(("verbose:sess_select","timer due in %d.%06d sec\n", + (int)earliest.tv_sec, (int)earliest.tv_usec)); + } + + /* + * if it was blocking before or our delta time is less, reset timeout + */ + if ((*block || (timercmp(&earliest, timeout, <)))) { + DEBUGMSGT(("verbose:sess_select", + "setting timer to %d.%06d sec, clear block (was %d)\n", + (int)earliest.tv_sec, (int)earliest.tv_usec, *block)); + *timeout = earliest; + *block = 0; + } + return active; +} + +/* + * snmp_timeout should be called whenever the timeout from snmp_select_info + * expires, but it is idempotent, so snmp_timeout can be polled (probably a + * cpu expensive proposition). snmp_timeout checks to see if any of the + * sessions have an outstanding request that has timed out. If it finds one + * (or more), and that pdu has more retries available, a new packet is formed + * from the pdu and is resent. If there are no more retries available, the + * callback for the session is used to alert the user of the timeout. + */ +void +snmp_timeout(void) +{ + struct session_list *slp; + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + for (slp = Sessions; slp; slp = slp->next) { + snmp_sess_timeout((void *) slp); + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); +} + +static int +snmp_resend_request(struct session_list *slp, netsnmp_request_list *rp, + int incr_retries) +{ + struct snmp_internal_session *isp; + netsnmp_session *sp; + netsnmp_transport *transport; + u_char *pktbuf = NULL, *packet = NULL; + size_t pktbuf_len = 0, length = 0; + struct timeval tv, now; + int result = 0; + + sp = slp->session; + isp = slp->internal; + transport = slp->transport; + if (!sp || !isp || !transport) { + DEBUGMSGTL(("sess_read", "resend fail: closing...\n")); + return 0; + } + + if ((pktbuf = (u_char *)malloc(2048)) == NULL) { + DEBUGMSGTL(("sess_resend", + "couldn't malloc initial packet buffer\n")); + return 0; + } else { + pktbuf_len = 2048; + } + + if (incr_retries) { + rp->retries++; + } + + /* + * Always increment msgId for resent messages. + */ + rp->pdu->msgid = rp->message_id = snmp_get_next_msgid(); + + result = netsnmp_build_packet(isp, sp, rp->pdu, &pktbuf, &pktbuf_len, + &packet, &length); + if (result < 0) { + /* + * This should never happen. + */ + DEBUGMSGTL(("sess_resend", "encoding failure\n")); + SNMP_FREE(pktbuf); + return -1; + } + + DEBUGMSGTL(("sess_process_packet", "resending message id#%ld reqid#%ld " + "rp_reqid#%ld rp_msgid#%ld len %" NETSNMP_PRIz "u\n", + rp->pdu->msgid, rp->pdu->reqid, rp->request_id, rp->message_id, length)); + result = netsnmp_transport_send(transport, packet, length, + &(rp->pdu->transport_data), + &(rp->pdu->transport_data_length)); + + /* + * We are finished with the local packet buffer, if we allocated one (due + * to there being no saved packet). + */ + + if (pktbuf != NULL) { + SNMP_FREE(pktbuf); + packet = NULL; + } + + if (result < 0) { + sp->s_snmp_errno = SNMPERR_BAD_SENDTO; + sp->s_errno = errno; + snmp_set_detail(strerror(errno)); + if (rp->callback) + rp->callback(NETSNMP_CALLBACK_OP_SEND_FAILED, sp, + rp->pdu->reqid, rp->pdu, rp->cb_data); + return -1; + } else { + netsnmp_get_monotonic_clock(&now); + tv = now; + rp->timeM = tv; + tv.tv_usec += rp->timeout; + tv.tv_sec += tv.tv_usec / 1000000L; + tv.tv_usec %= 1000000L; + rp->expireM = tv; + if (rp->callback) + rp->callback(NETSNMP_CALLBACK_OP_RESEND, sp, + rp->pdu->reqid, rp->pdu, rp->cb_data); + } + return 0; +} + + + +void +snmp_sess_timeout(void *sessp) +{ + struct session_list *slp = (struct session_list *) sessp; + netsnmp_session *sp; + struct snmp_internal_session *isp; + netsnmp_request_list *rp, *orp = NULL, *freeme = NULL; + struct timeval now; + snmp_callback callback; + void *magic; + struct snmp_secmod_def *sptr; + + sp = slp->session; + isp = slp->internal; + if (!sp || !isp) { + DEBUGMSGTL(("sess_read", "timeout fail: closing...\n")); + return; + } + + netsnmp_get_monotonic_clock(&now); + + /* + * For each request outstanding, check to see if it has expired. + */ + for (rp = isp->requests; rp; rp = rp->next_request) { + if (freeme != NULL) { + /* + * frees rp's after the for loop goes on to the next_request + */ + free((char *) freeme); + freeme = NULL; + } + + if ((timercmp(&rp->expireM, &now, <))) { + if ((sptr = find_sec_mod(rp->pdu->securityModel)) != NULL && + sptr->pdu_timeout != NULL) { + /* + * call security model if it needs to know about this + */ + (*sptr->pdu_timeout) (rp->pdu); + } + + /* + * this timer has expired + */ + if (rp->retries >= sp->retries) { + if (rp->callback) { + callback = rp->callback; + magic = rp->cb_data; + } else { + callback = sp->callback; + magic = sp->callback_magic; + } + + /* + * No more chances, delete this entry + */ + if (callback) { + callback(NETSNMP_CALLBACK_OP_TIMED_OUT, sp, + rp->pdu->reqid, rp->pdu, magic); + } + if (orp) + orp->next_request = rp->next_request; + else + isp->requests = rp->next_request; + if (isp->requestsEnd == rp) + isp->requestsEnd = orp; + snmp_free_pdu(rp->pdu); + freeme = rp; + continue; /* don't update orp below */ + } else { + if (snmp_resend_request(slp, rp, TRUE)) { + break; + } + } + } + orp = rp; + } + + if (freeme != NULL) { + free((char *) freeme); + freeme = NULL; + } +} + +/* + * lexicographical compare two object identifiers. + * * Returns -1 if name1 < name2, + * * 0 if name1 = name2, + * * 1 if name1 > name2 + * * + * * Caution: this method is called often by + * * command responder applications (ie, agent). + */ +int +snmp_oid_ncompare(const oid * in_name1, + size_t len1, + const oid * in_name2, size_t len2, size_t max_len) +{ + register int len; + register const oid *name1 = in_name1; + register const oid *name2 = in_name2; + size_t min_len; + + /* + * len = minimum of len1 and len2 + */ + if (len1 < len2) + min_len = len1; + else + min_len = len2; + + if (min_len > max_len) + min_len = max_len; + + len = min_len; + + /* + * find first non-matching OID + */ + while (len-- > 0) { + /* + * these must be done in seperate comparisons, since + * subtracting them and using that result has problems with + * subids > 2^31. + */ + if (*(name1) != *(name2)) { + if (*(name1) < *(name2)) + return -1; + return 1; + } + name1++; + name2++; + } + + if (min_len != max_len) { + /* + * both OIDs equal up to length of shorter OID + */ + if (len1 < len2) + return -1; + if (len2 < len1) + return 1; + } + + return 0; +} + +/** lexicographical compare two object identifiers. + * + * Caution: this method is called often by + * command responder applications (ie, agent). + * + * @return -1 if name1 < name2, 0 if name1 = name2, 1 if name1 > name2 + */ +int +snmp_oid_compare(const oid * in_name1, + size_t len1, const oid * in_name2, size_t len2) +{ + register int len; + register const oid *name1 = in_name1; + register const oid *name2 = in_name2; + + /* + * len = minimum of len1 and len2 + */ + if (len1 < len2) + len = len1; + else + len = len2; + /* + * find first non-matching OID + */ + while (len-- > 0) { + /* + * these must be done in seperate comparisons, since + * subtracting them and using that result has problems with + * subids > 2^31. + */ + if (*(name1) != *(name2)) { + if (*(name1) < *(name2)) + return -1; + return 1; + } + name1++; + name2++; + } + /* + * both OIDs equal up to length of shorter OID + */ + if (len1 < len2) + return -1; + if (len2 < len1) + return 1; + return 0; +} + +/** lexicographical compare two object identifiers and return the point where they differ + * + * Caution: this method is called often by + * command responder applications (ie, agent). + * + * @return -1 if name1 < name2, 0 if name1 = name2, 1 if name1 > name2 and offpt = len where name1 != name2 + */ +int +netsnmp_oid_compare_ll(const oid * in_name1, + size_t len1, const oid * in_name2, size_t len2, + size_t *offpt) +{ + register int len; + register const oid *name1 = in_name1; + register const oid *name2 = in_name2; + int initlen; + + /* + * len = minimum of len1 and len2 + */ + if (len1 < len2) + initlen = len = len1; + else + initlen = len = len2; + /* + * find first non-matching OID + */ + while (len-- > 0) { + /* + * these must be done in seperate comparisons, since + * subtracting them and using that result has problems with + * subids > 2^31. + */ + if (*(name1) != *(name2)) { + *offpt = initlen - len; + if (*(name1) < *(name2)) + return -1; + return 1; + } + name1++; + name2++; + } + /* + * both OIDs equal up to length of shorter OID + */ + *offpt = initlen - len; + if (len1 < len2) + return -1; + if (len2 < len1) + return 1; + return 0; +} + +/** Compares 2 OIDs to determine if they are equal up until the shortest length. + * @param in_name1 A pointer to the first oid. + * @param len1 length of the first OID (in segments, not bytes) + * @param in_name2 A pointer to the second oid. + * @param len2 length of the second OID (in segments, not bytes) + * @return 0 if they are equal, 1 if in_name1 is > in_name2, or -1 if <. + */ +int +snmp_oidtree_compare(const oid * in_name1, + size_t len1, const oid * in_name2, size_t len2) +{ + int len = ((len1 < len2) ? len1 : len2); + + return (snmp_oid_compare(in_name1, len, in_name2, len)); +} + +int +snmp_oidsubtree_compare(const oid * in_name1, + size_t len1, const oid * in_name2, size_t len2) +{ + int len = ((len1 < len2) ? len1 : len2); + + return (snmp_oid_compare(in_name1, len1, in_name2, len)); +} + +/** Compares 2 OIDs to determine if they are exactly equal. + * This should be faster than doing a snmp_oid_compare for different + * length OIDs, since the length is checked first and if != returns + * immediately. Might be very slighly faster if lengths are ==. + * @param in_name1 A pointer to the first oid. + * @param len1 length of the first OID (in segments, not bytes) + * @param in_name2 A pointer to the second oid. + * @param len2 length of the second OID (in segments, not bytes) + * @return 0 if they are equal, 1 if they are not. + */ +int +netsnmp_oid_equals(const oid * in_name1, + size_t len1, const oid * in_name2, size_t len2) +{ + register const oid *name1 = in_name1; + register const oid *name2 = in_name2; + register int len = len1; + + /* + * len = minimum of len1 and len2 + */ + if (len1 != len2) + return 1; + /* + * Handle 'null' OIDs + */ + if (len1 == 0) + return 0; /* Two null OIDs are (trivially) the same */ + if (!name1 || !name2) + return 1; /* Otherwise something's wrong, so report a non-match */ + /* + * find first non-matching OID + */ + while (len-- > 0) { + /* + * these must be done in seperate comparisons, since + * subtracting them and using that result has problems with + * subids > 2^31. + */ + if (*(name1++) != *(name2++)) + return 1; + } + return 0; +} + +#ifndef NETSNMP_FEATURE_REMOVE_OID_IS_SUBTREE +/** Identical to netsnmp_oid_equals, except only the length up to len1 is compared. + * Functionally, this determines if in_name2 is equal or a subtree of in_name1 + * @param in_name1 A pointer to the first oid. + * @param len1 length of the first OID (in segments, not bytes) + * @param in_name2 A pointer to the second oid. + * @param len2 length of the second OID (in segments, not bytes) + * @return 0 if one is a common prefix of the other. + */ +int +netsnmp_oid_is_subtree(const oid * in_name1, + size_t len1, const oid * in_name2, size_t len2) +{ + if (len1 > len2) + return 1; + + if (memcmp(in_name1, in_name2, len1 * sizeof(oid))) + return 1; + + return 0; +} +#endif /* NETSNMP_FEATURE_REMOVE_OID_IS_SUBTREE */ + +/** Given two OIDs, determine the common prefix to them both. + * @param in_name1 A pointer to the first oid. + * @param len1 Length of the first oid. + * @param in_name2 A pointer to the second oid. + * @param len2 Length of the second oid. + * @return length of common prefix + * 0 if no common prefix, -1 on error. + */ +int +netsnmp_oid_find_prefix(const oid * in_name1, size_t len1, + const oid * in_name2, size_t len2) +{ + int i; + size_t min_size; + + if (!in_name1 || !in_name2 || !len1 || !len2) + return -1; + + if (in_name1[0] != in_name2[0]) + return 0; /* No match */ + min_size = SNMP_MIN(len1, len2); + for(i = 0; i < (int)min_size; i++) { + if (in_name1[i] != in_name2[i]) + return i; /* '�' is the first differing subidentifier + So the common prefix is 0..(i-1), of length i */ + } + return min_size; /* The shorter OID is a prefix of the longer, and + hence is precisely the common prefix of the two. + Return its length. */ +} + +#ifndef NETSNMP_DISABLE_MIB_LOADING +static int _check_range(struct tree *tp, long ltmp, int *resptr, + const char *errmsg) +{ + char *cp = NULL; + char *temp = NULL; + int temp_len = 0; + int check = !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DONT_CHECK_RANGE); + + if (check && tp && tp->ranges) { + struct range_list *rp = tp->ranges; + while (rp) { + if (rp->low <= ltmp && ltmp <= rp->high) break; + /* Allow four digits per range value */ + temp_len += ((rp->low != rp->high) ? 27 : 15 ); + rp = rp->next; + } + if (!rp) { + *resptr = SNMPERR_RANGE; + temp = (char *)malloc( temp_len+strlen(errmsg)+7); + if ( temp ) { + /* Append the Display Hint range information to the error message */ + sprintf( temp, "%s :: {", errmsg ); + cp = temp+(strlen(temp)); + for ( rp = tp->ranges; rp; rp=rp->next ) { + if ( rp->low != rp->high ) + sprintf( cp, "(%d..%d), ", rp->low, rp->high ); + else + sprintf( cp, "(%d), ", rp->low ); + cp += strlen(cp); + } + *(cp-2) = '}'; /* Replace the final comma with a '}' */ + *(cp-1) = 0; + snmp_set_detail(temp); + free(temp); + } + return 0; + } + } + free(temp); + return 1; +} +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + +/* + * Add a variable with the requested name to the end of the list of + * variables for this pdu. + */ +netsnmp_variable_list * +snmp_pdu_add_variable(netsnmp_pdu *pdu, + const oid * name, + size_t name_length, + u_char type, const void * value, size_t len) +{ + return snmp_varlist_add_variable(&pdu->variables, name, name_length, + type, value, len); +} + +/* + * Add a variable with the requested name to the end of the list of + * variables for this pdu. + */ +netsnmp_variable_list * +snmp_varlist_add_variable(netsnmp_variable_list ** varlist, + const oid * name, + size_t name_length, + u_char type, const void * value, size_t len) +{ + netsnmp_variable_list *vars, *vtmp; + int rc; + + if (varlist == NULL) + return NULL; + + vars = SNMP_MALLOC_TYPEDEF(netsnmp_variable_list); + if (vars == NULL) + return NULL; + + vars->type = type; + + rc = snmp_set_var_value( vars, value, len ); + if (( 0 != rc ) || + (name != NULL && snmp_set_var_objid(vars, name, name_length))) { + snmp_free_var(vars); + return NULL; + } + + /* + * put only qualified variable onto varlist + */ + if (*varlist == NULL) { + *varlist = vars; + } else { + for (vtmp = *varlist; vtmp->next_variable; + vtmp = vtmp->next_variable); + + vtmp->next_variable = vars; + } + + return vars; +} + + + +/* + * Add a variable with the requested name to the end of the list of + * variables for this pdu. + * Returns: + * may set these error types : + * SNMPERR_RANGE - type, value, or length not found or out of range + * SNMPERR_VALUE - value is not correct + * SNMPERR_VAR_TYPE - type is not correct + * SNMPERR_BAD_NAME - name is not found + * + * returns 0 if success, error if failure. + */ +int +snmp_add_var(netsnmp_pdu *pdu, + const oid * name, size_t name_length, char type, const char *value) +{ + char *st; + const char *cp; + char *ecp, *vp; + int result = SNMPERR_SUCCESS; +#ifndef NETSNMP_DISABLE_MIB_LOADING + int check = !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DONT_CHECK_RANGE); + int do_hint = !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_NO_DISPLAY_HINT); + u_char *hintptr; + struct tree *tp; + struct enum_list *ep; + int itmp; +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + u_char *buf = NULL; + const u_char *buf_ptr = NULL; + size_t buf_len = 0, value_len = 0, tint; + in_addr_t atmp; + long ltmp; +#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES + double dtmp; + float ftmp; +#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */ + struct counter64 c64tmp; + +#ifndef NETSNMP_DISABLE_MIB_LOADING + tp = get_tree(name, name_length, get_tree_head()); + if (!tp || !tp->type || tp->type > TYPE_SIMPLE_LAST) { + check = 0; + } + if (!(tp && tp->hint)) + do_hint = 0; + + if (tp && type == '=') { + /* + * generic assignment - let the tree node decide value format + */ + switch (tp->type) { + case TYPE_INTEGER: + case TYPE_INTEGER32: + type = 'i'; + break; + case TYPE_GAUGE: + case TYPE_UNSIGNED32: + type = 'u'; + break; + case TYPE_UINTEGER: + type = '3'; + break; + case TYPE_COUNTER: + type = 'c'; + break; + case TYPE_COUNTER64: + type = 'C'; + break; + case TYPE_TIMETICKS: + type = 't'; + break; + case TYPE_OCTETSTR: + type = 's'; + break; + case TYPE_BITSTRING: + type = 'b'; + break; + case TYPE_IPADDR: + type = 'a'; + break; + case TYPE_OBJID: + type = 'o'; + break; + } + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + + switch (type) { + case 'i': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_INTEGER + && tp->type != TYPE_INTEGER32) { + value = "INTEGER"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + if (!*value) + goto fail; + ltmp = strtol(value, &ecp, 10); + if (*ecp) { +#ifndef NETSNMP_DISABLE_MIB_LOADING + ep = tp ? tp->enums : NULL; + while (ep) { + if (strcmp(value, ep->label) == 0) { + ltmp = ep->value; + break; + } + ep = ep->next; + } + if (!ep) { +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + result = SNMPERR_RANGE; /* ?? or SNMPERR_VALUE; */ + snmp_set_detail(value); + break; +#ifndef NETSNMP_DISABLE_MIB_LOADING + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + } + +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (!_check_range(tp, ltmp, &result, value)) + break; +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + snmp_pdu_add_variable(pdu, name, name_length, ASN_INTEGER, + <mp, sizeof(ltmp)); + break; + + case 'u': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_GAUGE && tp->type != TYPE_UNSIGNED32) { + value = "Unsigned32"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + ltmp = strtoul(value, &ecp, 10); + if (*value && !*ecp) + snmp_pdu_add_variable(pdu, name, name_length, ASN_UNSIGNED, + <mp, sizeof(ltmp)); + else + goto fail; + break; + + case '3': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_UINTEGER) { + value = "UInteger32"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + ltmp = strtoul(value, &ecp, 10); + if (*value && !*ecp) + snmp_pdu_add_variable(pdu, name, name_length, ASN_UINTEGER, + <mp, sizeof(ltmp)); + else + goto fail; + break; + + case 'c': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_COUNTER) { + value = "Counter32"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + ltmp = strtoul(value, &ecp, 10); + if (*value && !*ecp) + snmp_pdu_add_variable(pdu, name, name_length, ASN_COUNTER, + <mp, sizeof(ltmp)); + else + goto fail; + break; + + case 'C': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_COUNTER64) { + value = "Counter64"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + if (read64(&c64tmp, value)) + snmp_pdu_add_variable(pdu, name, name_length, ASN_COUNTER64, + &c64tmp, sizeof(c64tmp)); + else + goto fail; + break; + + case 't': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_TIMETICKS) { + value = "Timeticks"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + ltmp = strtoul(value, &ecp, 10); + if (*value && !*ecp) + snmp_pdu_add_variable(pdu, name, name_length, ASN_TIMETICKS, + <mp, sizeof(long)); + else + goto fail; + break; + + case 'a': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_IPADDR) { + value = "IpAddress"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + atmp = inet_addr(value); + if (atmp != (in_addr_t) -1 || !strcmp(value, "255.255.255.255")) + snmp_pdu_add_variable(pdu, name, name_length, ASN_IPADDRESS, + &atmp, sizeof(atmp)); + else + goto fail; + break; + + case 'o': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_OBJID) { + value = "OBJECT IDENTIFIER"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + if ((buf = (u_char *)malloc(sizeof(oid) * MAX_OID_LEN)) == NULL) { + result = SNMPERR_MALLOC; + } else { + tint = MAX_OID_LEN; + if (snmp_parse_oid(value, (oid *) buf, &tint)) { + snmp_pdu_add_variable(pdu, name, name_length, ASN_OBJECT_ID, + buf, sizeof(oid) * tint); + } else { + result = snmp_errno; /*MTCRITICAL_RESOURCE */ + } + } + break; + + case 's': + case 'x': + case 'd': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && tp->type != TYPE_OCTETSTR && tp->type != TYPE_BITSTRING) { + value = "OCTET STRING"; + result = SNMPERR_VALUE; + goto type_error; + } + if ('s' == type && do_hint && !parse_octet_hint(tp->hint, value, &hintptr, &itmp)) { + if (_check_range(tp, itmp, &result, "Value does not match DISPLAY-HINT")) { + snmp_pdu_add_variable(pdu, name, name_length, ASN_OCTET_STR, + hintptr, itmp); + } + SNMP_FREE(hintptr); + hintptr = buf; + break; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + if (type == 'd') { + if (!snmp_decimal_to_binary + (&buf, &buf_len, &value_len, 1, value)) { + result = SNMPERR_VALUE; + snmp_set_detail(value); + break; + } + buf_ptr = buf; + } else if (type == 'x') { + if (!snmp_hex_to_binary(&buf, &buf_len, &value_len, 1, value)) { + result = SNMPERR_VALUE; + snmp_set_detail(value); + break; + } + buf_ptr = buf; + } else if (type == 's') { + buf_ptr = (const u_char *)value; + value_len = strlen(value); + } +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (!_check_range(tp, value_len, &result, "Bad string length")) + break; +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + snmp_pdu_add_variable(pdu, name, name_length, ASN_OCTET_STR, + buf_ptr, value_len); + break; + + case 'n': + snmp_pdu_add_variable(pdu, name, name_length, ASN_NULL, NULL, 0); + break; + + case 'b': +#ifndef NETSNMP_DISABLE_MIB_LOADING + if (check && (tp->type != TYPE_BITSTRING || !tp->enums)) { + value = "BITS"; + result = SNMPERR_VALUE; + goto type_error; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + tint = 0; + if ((buf = (u_char *) malloc(256)) == NULL) { + result = SNMPERR_MALLOC; + break; + } else { + buf_len = 256; + memset(buf, 0, buf_len); + } + +#ifndef NETSNMP_DISABLE_MIB_LOADING + for (ep = tp ? tp->enums : NULL; ep; ep = ep->next) { + if (ep->value / 8 >= (int) tint) { + tint = ep->value / 8 + 1; + } + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + + vp = strdup(value); + if (!vp) { + SNMP_FREE(buf); + goto fail; + } + for (cp = strtok_r(vp, " ,\t", &st); cp; cp = strtok_r(NULL, " ,\t", &st)) { + int ix, bit; + + ltmp = strtoul(cp, &ecp, 0); + if (*ecp != 0) { +#ifndef NETSNMP_DISABLE_MIB_LOADING + for (ep = tp ? tp->enums : NULL; ep != NULL; ep = ep->next) { + if (strcmp(ep->label, cp) == 0) { + break; + } + } + if (ep != NULL) { + ltmp = ep->value; + } else { +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + result = SNMPERR_RANGE; /* ?? or SNMPERR_VALUE; */ + snmp_set_detail(cp); + SNMP_FREE(buf); + SNMP_FREE(vp); + goto out; +#ifndef NETSNMP_DISABLE_MIB_LOADING + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + } + + ix = ltmp / 8; + if (ix >= (int) tint) { + tint = ix + 1; + } + if (ix >= (int)buf_len && !snmp_realloc(&buf, &buf_len)) { + result = SNMPERR_MALLOC; + break; + } + bit = 0x80 >> ltmp % 8; + buf[ix] |= bit; + + } + SNMP_FREE(vp); + snmp_pdu_add_variable(pdu, name, name_length, ASN_OCTET_STR, + buf, tint); + break; + +#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES + case 'U': + if (read64(&c64tmp, value)) + snmp_pdu_add_variable(pdu, name, name_length, ASN_OPAQUE_U64, + &c64tmp, sizeof(c64tmp)); + else + goto fail; + break; + + case 'I': + if (read64(&c64tmp, value)) + snmp_pdu_add_variable(pdu, name, name_length, ASN_OPAQUE_I64, + &c64tmp, sizeof(c64tmp)); + else + goto fail; + break; + + case 'F': + if (sscanf(value, "%f", &ftmp) == 1) + snmp_pdu_add_variable(pdu, name, name_length, ASN_OPAQUE_FLOAT, + &ftmp, sizeof(ftmp)); + else + goto fail; + break; + + case 'D': + if (sscanf(value, "%lf", &dtmp) == 1) + snmp_pdu_add_variable(pdu, name, name_length, ASN_OPAQUE_DOUBLE, + &dtmp, sizeof(dtmp)); + else + goto fail; + break; +#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */ + + default: + result = SNMPERR_VAR_TYPE; + buf = (u_char *)calloc(1, 4); + if (buf != NULL) { + sprintf((char *)buf, "\"%c\"", type); + snmp_set_detail((char *)buf); + } + break; + } + + SNMP_FREE(buf); + SET_SNMP_ERROR(result); + return result; + +#ifndef NETSNMP_DISABLE_MIB_LOADING + type_error: + { + char error_msg[256]; + char undef_msg[32]; + const char *var_type; + switch (tp->type) { + case TYPE_OBJID: + var_type = "OBJECT IDENTIFIER"; + break; + case TYPE_OCTETSTR: + var_type = "OCTET STRING"; + break; + case TYPE_INTEGER: + var_type = "INTEGER"; + break; + case TYPE_NETADDR: + var_type = "NetworkAddress"; + break; + case TYPE_IPADDR: + var_type = "IpAddress"; + break; + case TYPE_COUNTER: + var_type = "Counter32"; + break; + case TYPE_GAUGE: + var_type = "Gauge32"; + break; + case TYPE_TIMETICKS: + var_type = "Timeticks"; + break; + case TYPE_OPAQUE: + var_type = "Opaque"; + break; + case TYPE_NULL: + var_type = "Null"; + break; + case TYPE_COUNTER64: + var_type = "Counter64"; + break; + case TYPE_BITSTRING: + var_type = "BITS"; + break; + case TYPE_NSAPADDRESS: + var_type = "NsapAddress"; + break; + case TYPE_UINTEGER: + var_type = "UInteger"; + break; + case TYPE_UNSIGNED32: + var_type = "Unsigned32"; + break; + case TYPE_INTEGER32: + var_type = "Integer32"; + break; + default: + sprintf(undef_msg, "TYPE_%d", tp->type); + var_type = undef_msg; + } + snprintf(error_msg, sizeof(error_msg), + "Type of attribute is %s, not %s", var_type, value); + error_msg[ sizeof(error_msg)-1 ] = 0; + result = SNMPERR_VAR_TYPE; + snmp_set_detail(error_msg); + goto out; + } +#endif /* NETSNMP_DISABLE_MIB_LOADING */ + fail: + result = SNMPERR_VALUE; + snmp_set_detail(value); + out: + SET_SNMP_ERROR(result); + return result; +} + +/* + * returns NULL or internal pointer to session + * use this pointer for the other snmp_sess* routines, + * which guarantee action will occur ONLY for this given session. + */ +void * +snmp_sess_pointer(netsnmp_session * session) +{ + struct session_list *slp; + + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + for (slp = Sessions; slp; slp = slp->next) { + if (slp->session == session) { + break; + } + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); + + if (slp == NULL) { + snmp_errno = SNMPERR_BAD_SESSION; /*MTCRITICAL_RESOURCE */ + return (NULL); + } + return ((void *) slp); +} + +/* + * Input : an opaque pointer, returned by snmp_sess_open. + * returns NULL or pointer to session. + */ +netsnmp_session * +snmp_sess_session(void *sessp) +{ + struct session_list *slp = (struct session_list *) sessp; + if (slp == NULL) + return (NULL); + return (slp->session); +} + +/** + * Look up a session that already may have been closed. + * + * @param sessp Opaque pointer, returned by snmp_sess_open. + * + * @return Pointer to session upon success or NULL upon failure. + * + * @see snmp_sess_session() + */ +netsnmp_session * +snmp_sess_session_lookup(void *sessp) +{ + struct session_list *slp; + + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + for (slp = Sessions; slp; slp = slp->next) { + if (slp == sessp) { + break; + } + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); + + return (netsnmp_session *)slp; +} + + +/* + * returns NULL or internal pointer to session + * use this pointer for the other snmp_sess* routines, + * which guarantee action will occur ONLY for this given session. + */ +netsnmp_session * +snmp_sess_lookup_by_name(const char *paramName) +{ + struct session_list *slp; + + snmp_res_lock(MT_LIBRARY_ID, MT_LIB_SESSION); + for (slp = Sessions; slp; slp = slp->next) { + if (NULL == slp->session->paramName) + continue; + if (strcmp(paramName, slp->session->paramName) == 0) + break; + } + snmp_res_unlock(MT_LIBRARY_ID, MT_LIB_SESSION); + + if (slp == NULL) + return NULL; + + return slp->session; +} + + +/* + * snmp_sess_transport: takes an opaque pointer (as returned by + * snmp_sess_open or snmp_sess_pointer) and returns the corresponding + * netsnmp_transport pointer (or NULL if the opaque pointer does not correspond + * to an active internal session). + */ + +netsnmp_transport * +snmp_sess_transport(void *sessp) +{ + struct session_list *slp = (struct session_list *) sessp; + if (slp == NULL) { + return NULL; + } else { + return slp->transport; + } +} + + + +/* + * snmp_sess_transport_set: set the transport pointer for the opaque + * session pointer sp. + */ + +void +snmp_sess_transport_set(void *sp, netsnmp_transport *t) +{ + struct session_list *slp = (struct session_list *) sp; + if (slp != NULL) { + slp->transport = t; + } +} + + +/* + * snmp_duplicate_objid: duplicates (mallocs) an objid based on the + * input objid + */ +oid * +snmp_duplicate_objid(const oid * objToCopy, size_t objToCopyLen) +{ + oid *returnOid; + if (objToCopy != NULL && objToCopyLen != 0) { + returnOid = (oid *) malloc(objToCopyLen * sizeof(oid)); + if (returnOid) { + memcpy(returnOid, objToCopy, objToCopyLen * sizeof(oid)); + } + } else + returnOid = NULL; + return returnOid; +} + +#ifndef NETSNMP_FEATURE_REMOVE_STATISTICS +/* + * generic statistics counter functions + */ +static u_int statistics[NETSNMP_STAT_MAX_STATS]; + +u_int +snmp_increment_statistic(int which) +{ + if (which >= 0 && which < NETSNMP_STAT_MAX_STATS) { + statistics[which]++; + return statistics[which]; + } + return 0; +} + +u_int +snmp_increment_statistic_by(int which, int count) +{ + if (which >= 0 && which < NETSNMP_STAT_MAX_STATS) { + statistics[which] += count; + return statistics[which]; + } + return 0; +} + +u_int +snmp_get_statistic(int which) +{ + if (which >= 0 && which < NETSNMP_STAT_MAX_STATS) + return statistics[which]; + return 0; +} + +void +snmp_init_statistics(void) +{ + memset(statistics, 0, sizeof(statistics)); +} +#endif /* NETSNMP_FEATURE_REMOVE_STATISTICS */ +/** @} */