blob: 0b6693a79d29b1daa8f21c48261d7b7b9e2209e9 [file] [log] [blame]
/**
* @file
* SNMP message processing (RFC1157).
*/
/*
* Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
* Copyright (c) 2016 Elias Oenal.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* Author: Christiaan Simons <christiaan.simons@axon.tv>
* Martin Hentschel <info@cl-soft.de>
* Elias Oenal <lwip@eliasoenal.com>
*/
#include "lwip/apps/snmp_opts.h"
#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
#include "snmp_msg.h"
#include "snmp_asn1.h"
#include "snmp_core_priv.h"
#include "lwip/ip_addr.h"
#include "lwip/stats.h"
#if LWIP_SNMP_V3
#include "lwip/apps/snmpv3.h"
#include "snmpv3_priv.h"
#ifdef LWIP_HOOK_FILENAME
#include LWIP_HOOK_FILENAME
#endif
#endif
#include <string.h>
#define SNMP_V3_AUTH_FLAG 0x01
#define SNMP_V3_PRIV_FLAG 0x02
/* Security levels */
#define SNMP_V3_NOAUTHNOPRIV 0x00
#define SNMP_V3_AUTHNOPRIV SNMP_V3_AUTH_FLAG
#define SNMP_V3_AUTHPRIV (SNMP_V3_AUTH_FLAG | SNMP_V3_PRIV_FLAG)
/* public (non-static) constants */
/** SNMP community string */
const char *snmp_community = SNMP_COMMUNITY;
/** SNMP community string for write access */
const char *snmp_community_write = SNMP_COMMUNITY_WRITE;
/** SNMP community string for sending traps */
const char *snmp_community_trap = SNMP_COMMUNITY_TRAP;
snmp_write_callback_fct snmp_write_callback = NULL;
void *snmp_write_callback_arg = NULL;
#if LWIP_SNMP_CONFIGURE_VERSIONS
static u8_t v1_enabled = 1;
static u8_t v2c_enabled = 1;
static u8_t v3_enabled = 1;
static u8_t
snmp_version_enabled(u8_t version)
{
if (version == SNMP_VERSION_1) {
return v1_enabled;
} else if (version == SNMP_VERSION_2c) {
return v2c_enabled;
}
#if LWIP_SNMP_V3
else if (version == SNMP_VERSION_3) {
return v3_enabled;
}
#endif
else {
LWIP_ASSERT("Invalid SNMP version", 0);
return 0;
}
}
u8_t
snmp_v1_enabled(void)
{
return snmp_version_enabled(SNMP_VERSION_1);
}
u8_t
snmp_v2c_enabled(void)
{
return snmp_version_enabled(SNMP_VERSION_2c);
}
u8_t
snmp_v3_enabled(void)
{
return snmp_version_enabled(SNMP_VERSION_3);
}
static void
snmp_version_enable(u8_t version, u8_t enable)
{
if (version == SNMP_VERSION_1) {
v1_enabled = enable;
} else if (version == SNMP_VERSION_2c) {
v2c_enabled = enable;
}
#if LWIP_SNMP_V3
else if (version == SNMP_VERSION_3) {
v3_enabled = enable;
}
#endif
else {
LWIP_ASSERT("Invalid SNMP version", 0);
}
}
void
snmp_v1_enable(u8_t enable)
{
snmp_version_enable(SNMP_VERSION_1, enable);
}
void
snmp_v2c_enable(u8_t enable)
{
snmp_version_enable(SNMP_VERSION_2c, enable);
}
void
snmp_v3_enable(u8_t enable)
{
snmp_version_enable(SNMP_VERSION_3, enable);
}
#endif
/**
* @ingroup snmp_core
* Returns current SNMP community string.
* @return current SNMP community string
*/
const char *
snmp_get_community(void)
{
return snmp_community;
}
/**
* @ingroup snmp_core
* Sets SNMP community string.
* The string itself (its storage) must be valid throughout the whole life of
* program (or until it is changed to sth else).
*
* @param community is a pointer to new community string
*/
void
snmp_set_community(const char *const community)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
snmp_community = community;
}
/**
* @ingroup snmp_core
* Returns current SNMP write-access community string.
* @return current SNMP write-access community string
*/
const char *
snmp_get_community_write(void)
{
return snmp_community_write;
}
/**
* @ingroup snmp_traps
* Returns current SNMP community string used for sending traps.
* @return current SNMP community string used for sending traps
*/
const char *
snmp_get_community_trap(void)
{
return snmp_community_trap;
}
/**
* @ingroup snmp_core
* Sets SNMP community string for write-access.
* The string itself (its storage) must be valid throughout the whole life of
* program (or until it is changed to sth else).
*
* @param community is a pointer to new write-access community string
*/
void
snmp_set_community_write(const char *const community)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ASSERT("community string must not be NULL", community != NULL);
LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
snmp_community_write = community;
}
/**
* @ingroup snmp_traps
* Sets SNMP community string used for sending traps.
* The string itself (its storage) must be valid throughout the whole life of
* program (or until it is changed to sth else).
*
* @param community is a pointer to new trap community string
*/
void
snmp_set_community_trap(const char *const community)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
snmp_community_trap = community;
}
/**
* @ingroup snmp_core
* Callback fired on every successful write access
*/
void
snmp_set_write_callback(snmp_write_callback_fct write_callback, void *callback_arg)
{
LWIP_ASSERT_CORE_LOCKED();
snmp_write_callback = write_callback;
snmp_write_callback_arg = callback_arg;
}
/* ----------------------------------------------------------------------- */
/* forward declarations */
/* ----------------------------------------------------------------------- */
static err_t snmp_process_get_request(struct snmp_request *request);
static err_t snmp_process_getnext_request(struct snmp_request *request);
static err_t snmp_process_getbulk_request(struct snmp_request *request);
static err_t snmp_process_set_request(struct snmp_request *request);
static err_t snmp_parse_inbound_frame(struct snmp_request *request);
static err_t snmp_prepare_outbound_frame(struct snmp_request *request);
static err_t snmp_complete_outbound_frame(struct snmp_request *request);
static void snmp_execute_write_callbacks(struct snmp_request *request);
/* ----------------------------------------------------------------------- */
/* implementation */
/* ----------------------------------------------------------------------- */
void
snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port)
{
err_t err;
struct snmp_request request;
memset(&request, 0, sizeof(request));
request.handle = handle;
request.source_ip = source_ip;
request.source_port = port;
request.inbound_pbuf = p;
snmp_stats.inpkts++;
err = snmp_parse_inbound_frame(&request);
if (err == ERR_OK) {
err = snmp_prepare_outbound_frame(&request);
if (err == ERR_OK) {
if (request.error_status == SNMP_ERR_NOERROR) {
/* only process frame if we do not already have an error to return (e.g. all readonly) */
if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_REQ) {
err = snmp_process_get_request(&request);
} else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ) {
err = snmp_process_getnext_request(&request);
} else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
err = snmp_process_getbulk_request(&request);
} else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
err = snmp_process_set_request(&request);
}
}
#if LWIP_SNMP_V3
else {
struct snmp_varbind vb;
vb.next = NULL;
vb.prev = NULL;
vb.type = SNMP_ASN1_TYPE_COUNTER32;
vb.value_len = sizeof(u32_t);
switch (request.error_status) {
case SNMP_ERR_AUTHORIZATIONERROR: {
static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0 };
snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
vb.value = &snmp_stats.wrongdigests;
}
break;
case SNMP_ERR_UNKNOWN_ENGINEID: {
static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0 };
snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
vb.value = &snmp_stats.unknownengineids;
}
break;
case SNMP_ERR_UNKNOWN_SECURITYNAME: {
static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 3, 0 };
snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
vb.value = &snmp_stats.unknownusernames;
}
break;
case SNMP_ERR_UNSUPPORTED_SECLEVEL: {
static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0 };
snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
vb.value = &snmp_stats.unsupportedseclevels;
}
break;
case SNMP_ERR_NOTINTIMEWINDOW: {
static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 2, 0 };
snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
vb.value = &snmp_stats.notintimewindows;
}
break;
case SNMP_ERR_DECRYIPTION_ERROR: {
static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 6, 0 };
snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
vb.value = &snmp_stats.decryptionerrors;
}
break;
default:
/* Unknown or unhandled error_status */
err = ERR_ARG;
}
if (err == ERR_OK) {
snmp_append_outbound_varbind(&(request.outbound_pbuf_stream), &vb);
request.error_status = SNMP_ERR_NOERROR;
}
request.request_out_type = (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_REPORT);
request.request_id = request.msg_id;
}
#endif
if (err == ERR_OK) {
err = snmp_complete_outbound_frame(&request);
if (err == ERR_OK) {
err = snmp_sendto(request.handle, request.outbound_pbuf, request.source_ip, request.source_port);
if ((request.request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ)
&& (request.error_status == SNMP_ERR_NOERROR)
&& (snmp_write_callback != NULL)) {
/* raise write notification for all written objects */
snmp_execute_write_callbacks(&request);
}
}
}
}
if (request.outbound_pbuf != NULL) {
pbuf_free(request.outbound_pbuf);
}
}
}
static u8_t
snmp_msg_getnext_validate_node_inst(struct snmp_node_instance *node_instance, void *validate_arg)
{
if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_READ) != SNMP_NODE_INSTANCE_ACCESS_READ) || (node_instance->get_value == NULL)) {
return SNMP_ERR_NOSUCHINSTANCE;
}
#if LWIP_HAVE_INT64
if ((node_instance->asn1_type == SNMP_ASN1_TYPE_COUNTER64) && (((struct snmp_request *)validate_arg)->version == SNMP_VERSION_1)) {
/* according to RFC 2089 skip Counter64 objects in GetNext requests from v1 clients */
return SNMP_ERR_NOSUCHINSTANCE;
}
#endif
return SNMP_ERR_NOERROR;
}
static void
snmp_process_varbind(struct snmp_request *request, struct snmp_varbind *vb, u8_t get_next)
{
err_t err;
struct snmp_node_instance node_instance;
memset(&node_instance, 0, sizeof(node_instance));
if (get_next) {
struct snmp_obj_id result_oid;
request->error_status = snmp_get_next_node_instance_from_oid(vb->oid.id, vb->oid.len, snmp_msg_getnext_validate_node_inst, request, &result_oid, &node_instance);
if (request->error_status == SNMP_ERR_NOERROR) {
snmp_oid_assign(&vb->oid, result_oid.id, result_oid.len);
}
} else {
request->error_status = snmp_get_node_instance_from_oid(vb->oid.id, vb->oid.len, &node_instance);
if (request->error_status == SNMP_ERR_NOERROR) {
/* use 'getnext_validate' method for validation to avoid code duplication (some checks have to be executed here) */
request->error_status = snmp_msg_getnext_validate_node_inst(&node_instance, request);
if (request->error_status != SNMP_ERR_NOERROR) {
if (node_instance.release_instance != NULL) {
node_instance.release_instance(&node_instance);
}
}
}
}
if (request->error_status != SNMP_ERR_NOERROR) {
if (request->error_status >= SNMP_VARBIND_EXCEPTION_OFFSET) {
if ((request->version == SNMP_VERSION_2c) || request->version == SNMP_VERSION_3) {
/* in SNMP v2c a varbind related exception is stored in varbind and not in frame header */
vb->type = (SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_CLASS_CONTEXT | (request->error_status & SNMP_VARBIND_EXCEPTION_MASK));
vb->value_len = 0;
err = snmp_append_outbound_varbind(&(request->outbound_pbuf_stream), vb);
if (err == ERR_OK) {
/* we stored the exception in varbind -> go on */
request->error_status = SNMP_ERR_NOERROR;
} else if (err == ERR_BUF) {
request->error_status = SNMP_ERR_TOOBIG;
} else {
request->error_status = SNMP_ERR_GENERROR;
}
}
} else {
/* according to RFC 1157/1905, all other errors only return genError */
request->error_status = SNMP_ERR_GENERROR;
}
} else {
s16_t len = node_instance.get_value(&node_instance, vb->value);
if (len >= 0) {
vb->value_len = (u16_t)len; /* cast is OK because we checked >= 0 above */
vb->type = node_instance.asn1_type;
LWIP_ASSERT("SNMP_MAX_VALUE_SIZE is configured too low", (vb->value_len & ~SNMP_GET_VALUE_RAW_DATA) <= SNMP_MAX_VALUE_SIZE);
err = snmp_append_outbound_varbind(&request->outbound_pbuf_stream, vb);
if (err == ERR_BUF) {
request->error_status = SNMP_ERR_TOOBIG;
} else if (err != ERR_OK) {
request->error_status = SNMP_ERR_GENERROR;
}
} else {
request->error_status = SNMP_ERR_GENERROR;
}
if (node_instance.release_instance != NULL) {
node_instance.release_instance(&node_instance);
}
}
}
/**
* Service an internal or external event for SNMP GET.
*
* @param request points to the associated message process state
*/
static err_t
snmp_process_get_request(struct snmp_request *request)
{
snmp_vb_enumerator_err_t err;
struct snmp_varbind vb;
vb.value = request->value_buffer;
LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get request\n"));
while (request->error_status == SNMP_ERR_NOERROR) {
err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
if ((vb.type == SNMP_ASN1_TYPE_NULL) && (vb.value_len == 0)) {
snmp_process_varbind(request, &vb, 0);
} else {
request->error_status = SNMP_ERR_GENERROR;
}
} else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
/* no more varbinds in request */
break;
} else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
/* malformed ASN.1, don't answer */
return ERR_ARG;
} else {
request->error_status = SNMP_ERR_GENERROR;
}
}
return ERR_OK;
}
/**
* Service an internal or external event for SNMP GET.
*
* @param request points to the associated message process state
*/
static err_t
snmp_process_getnext_request(struct snmp_request *request)
{
snmp_vb_enumerator_err_t err;
struct snmp_varbind vb;
vb.value = request->value_buffer;
LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get-next request\n"));
while (request->error_status == SNMP_ERR_NOERROR) {
err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
if ((vb.type == SNMP_ASN1_TYPE_NULL) && (vb.value_len == 0)) {
snmp_process_varbind(request, &vb, 1);
} else {
request->error_status = SNMP_ERR_GENERROR;
}
} else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
/* no more varbinds in request */
break;
} else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
/* malformed ASN.1, don't answer */
return ERR_ARG;
} else {
request->error_status = SNMP_ERR_GENERROR;
}
}
return ERR_OK;
}
/**
* Service an internal or external event for SNMP GETBULKT.
*
* @param request points to the associated message process state
*/
static err_t
snmp_process_getbulk_request(struct snmp_request *request)
{
snmp_vb_enumerator_err_t err;
s32_t non_repeaters = request->non_repeaters;
s32_t repetitions;
u16_t repetition_offset = 0;
struct snmp_varbind_enumerator repetition_varbind_enumerator;
struct snmp_varbind vb;
vb.value = request->value_buffer;
if (SNMP_LWIP_GETBULK_MAX_REPETITIONS > 0) {
repetitions = LWIP_MIN(request->max_repetitions, SNMP_LWIP_GETBULK_MAX_REPETITIONS);
} else {
repetitions = request->max_repetitions;
}
LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get-bulk request\n"));
/* process non repeaters and first repetition */
while (request->error_status == SNMP_ERR_NOERROR) {
if (non_repeaters == 0) {
repetition_offset = request->outbound_pbuf_stream.offset;
if (repetitions == 0) {
/* do not resolve repeaters when repetitions is set to 0 */
break;
}
repetitions--;
}
err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
/* no more varbinds in request */
break;
} else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
/* malformed ASN.1, don't answer */
return ERR_ARG;
} else if ((err != SNMP_VB_ENUMERATOR_ERR_OK) || (vb.type != SNMP_ASN1_TYPE_NULL) || (vb.value_len != 0)) {
request->error_status = SNMP_ERR_GENERROR;
} else {
snmp_process_varbind(request, &vb, 1);
non_repeaters--;
}
}
/* process repetitions > 1 */
while ((request->error_status == SNMP_ERR_NOERROR) && (repetitions > 0) && (request->outbound_pbuf_stream.offset != repetition_offset)) {
u8_t all_endofmibview = 1;
snmp_vb_enumerator_init(&repetition_varbind_enumerator, request->outbound_pbuf, repetition_offset, request->outbound_pbuf_stream.offset - repetition_offset);
repetition_offset = request->outbound_pbuf_stream.offset; /* for next loop */
while (request->error_status == SNMP_ERR_NOERROR) {
vb.value = NULL; /* do NOT decode value (we enumerate outbound buffer here, so all varbinds have values assigned) */
err = snmp_vb_enumerator_get_next(&repetition_varbind_enumerator, &vb);
if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
vb.value = request->value_buffer;
snmp_process_varbind(request, &vb, 1);
if (request->error_status != SNMP_ERR_NOERROR) {
/* already set correct error-index (here it cannot be taken from inbound varbind enumerator) */
request->error_index = request->non_repeaters + repetition_varbind_enumerator.varbind_count;
} else if (vb.type != (SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTEXT_VARBIND_END_OF_MIB_VIEW)) {
all_endofmibview = 0;
}
} else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
/* no more varbinds in request */
break;
} else {
LWIP_DEBUGF(SNMP_DEBUG, ("Very strange, we cannot parse the varbind output that we created just before!"));
request->error_status = SNMP_ERR_GENERROR;
request->error_index = request->non_repeaters + repetition_varbind_enumerator.varbind_count;
}
}
if ((request->error_status == SNMP_ERR_NOERROR) && all_endofmibview) {
/* stop when all varbinds in a loop return EndOfMibView */
break;
}
repetitions--;
}
if (request->error_status == SNMP_ERR_TOOBIG) {
/* for GetBulk it is ok, if not all requested variables fit into the response -> just return the varbinds added so far */
request->error_status = SNMP_ERR_NOERROR;
}
return ERR_OK;
}
/**
* Service an internal or external event for SNMP SET.
*
* @param request points to the associated message process state
*/
static err_t
snmp_process_set_request(struct snmp_request *request)
{
snmp_vb_enumerator_err_t err;
struct snmp_varbind vb;
vb.value = request->value_buffer;
LWIP_DEBUGF(SNMP_DEBUG, ("SNMP set request\n"));
/* perform set test on all objects */
while (request->error_status == SNMP_ERR_NOERROR) {
err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
struct snmp_node_instance node_instance;
memset(&node_instance, 0, sizeof(node_instance));
request->error_status = snmp_get_node_instance_from_oid(vb.oid.id, vb.oid.len, &node_instance);
if (request->error_status == SNMP_ERR_NOERROR) {
if (node_instance.asn1_type != vb.type) {
request->error_status = SNMP_ERR_WRONGTYPE;
} else if (((node_instance.access & SNMP_NODE_INSTANCE_ACCESS_WRITE) != SNMP_NODE_INSTANCE_ACCESS_WRITE) || (node_instance.set_value == NULL)) {
request->error_status = SNMP_ERR_NOTWRITABLE;
} else {
if (node_instance.set_test != NULL) {
request->error_status = node_instance.set_test(&node_instance, vb.value_len, vb.value);
}
}
if (node_instance.release_instance != NULL) {
node_instance.release_instance(&node_instance);
}
}
} else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
/* no more varbinds in request */
break;
} else if (err == SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH) {
request->error_status = SNMP_ERR_WRONGLENGTH;
} else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
/* malformed ASN.1, don't answer */
return ERR_ARG;
} else {
request->error_status = SNMP_ERR_GENERROR;
}
}
/* perform real set operation on all objects */
if (request->error_status == SNMP_ERR_NOERROR) {
snmp_vb_enumerator_init(&request->inbound_varbind_enumerator, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
while (request->error_status == SNMP_ERR_NOERROR) {
err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
struct snmp_node_instance node_instance;
memset(&node_instance, 0, sizeof(node_instance));
request->error_status = snmp_get_node_instance_from_oid(vb.oid.id, vb.oid.len, &node_instance);
if (request->error_status == SNMP_ERR_NOERROR) {
if (node_instance.set_value(&node_instance, vb.value_len, vb.value) != SNMP_ERR_NOERROR) {
if (request->inbound_varbind_enumerator.varbind_count == 1) {
request->error_status = SNMP_ERR_COMMITFAILED;
} else {
/* we cannot undo the set operations done so far */
request->error_status = SNMP_ERR_UNDOFAILED;
}
}
if (node_instance.release_instance != NULL) {
node_instance.release_instance(&node_instance);
}
}
} else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
/* no more varbinds in request */
break;
} else {
/* first time enumerating varbinds work but second time not, although nothing should have changed in between ??? */
request->error_status = SNMP_ERR_GENERROR;
}
}
}
return ERR_OK;
}
#define PARSE_EXEC(code, retValue) \
if ((code) != ERR_OK) { \
LWIP_DEBUGF(SNMP_DEBUG, ("Malformed ASN.1 detected.\n")); \
snmp_stats.inasnparseerrs++; \
return retValue; \
}
#define PARSE_ASSERT(cond, retValue) \
if (!(cond)) { \
LWIP_DEBUGF(SNMP_DEBUG, ("SNMP parse assertion failed!: " # cond)); \
snmp_stats.inasnparseerrs++; \
return retValue; \
}
#define BUILD_EXEC(code, retValue) \
if ((code) != ERR_OK) { \
LWIP_DEBUGF(SNMP_DEBUG, ("SNMP error during creation of outbound frame!: " # code)); \
return retValue; \
}
#define IF_PARSE_EXEC(code) PARSE_EXEC(code, ERR_ARG)
#define IF_PARSE_ASSERT(code) PARSE_ASSERT(code, ERR_ARG)
/**
* Checks and decodes incoming SNMP message header, logs header errors.
*
* @param request points to the current message request state return
* @return
* - ERR_OK SNMP header is sane and accepted
* - ERR_VAL SNMP header is either malformed or rejected
*/
static err_t
snmp_parse_inbound_frame(struct snmp_request *request)
{
struct snmp_pbuf_stream pbuf_stream;
struct snmp_asn1_tlv tlv;
s32_t parent_tlv_value_len;
s32_t s32_value;
err_t err;
#if LWIP_SNMP_V3
snmpv3_auth_algo_t auth;
snmpv3_priv_algo_t priv;
#endif
IF_PARSE_EXEC(snmp_pbuf_stream_init(&pbuf_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
/* decode main container consisting of version, community and PDU */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len == pbuf_stream.length));
parent_tlv_value_len = tlv.value_len;
/* decode version */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
if (((s32_value != SNMP_VERSION_1) &&
(s32_value != SNMP_VERSION_2c)
#if LWIP_SNMP_V3
&& (s32_value != SNMP_VERSION_3)
#endif
)
#if LWIP_SNMP_CONFIGURE_VERSIONS
|| (!snmp_version_enabled(s32_value))
#endif
) {
/* unsupported SNMP version */
snmp_stats.inbadversions++;
return ERR_ARG;
}
request->version = (u8_t)s32_value;
#if LWIP_SNMP_V3
if (request->version == SNMP_VERSION_3) {
u16_t u16_value;
u16_t inbound_msgAuthenticationParameters_offset;
/* SNMPv3 doesn't use communities */
/* @todo: Differentiate read/write access */
strncpy((char *)request->community, snmp_community, SNMP_MAX_COMMUNITY_STR_LEN);
request->community[SNMP_MAX_COMMUNITY_STR_LEN] = 0; /* ensure NULL termination (strncpy does NOT guarantee it!) */
request->community_strlen = (u16_t)strnlen((char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN);
/* RFC3414 globalData */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
/* decode msgID */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
request->msg_id = s32_value;
/* decode msgMaxSize */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
request->msg_max_size = s32_value;
/* decode msgFlags */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
request->msg_flags = (u8_t)s32_value;
/* decode msgSecurityModel */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
request->msg_security_model = s32_value;
/* RFC3414 msgSecurityParameters
* The User-based Security Model defines the contents of the OCTET
* STRING as a SEQUENCE.
*
* We skip the protective dummy OCTET STRING header
* to access the SEQUENCE header.
*/
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
/* msgSecurityParameters SEQUENCE header */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
/* decode msgAuthoritativeEngineID */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authoritative_engine_id,
&u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
request->msg_authoritative_engine_id_len = (u8_t)u16_value;
/* msgAuthoritativeEngineBoots */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->msg_authoritative_engine_boots));
/* msgAuthoritativeEngineTime */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->msg_authoritative_engine_time));
/* msgUserName */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_user_name,
&u16_value, SNMP_V3_MAX_USER_LENGTH));
request->msg_user_name_len = (u8_t)u16_value;
/* msgAuthenticationParameters */
memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
/* Remember position */
inbound_msgAuthenticationParameters_offset = pbuf_stream.offset;
LWIP_UNUSED_ARG(inbound_msgAuthenticationParameters_offset);
/* Read auth parameters */
/* IF_PARSE_ASSERT(tlv.value_len <= SNMP_V3_MAX_AUTH_PARAM_LENGTH); */
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authentication_parameters,
&u16_value, tlv.value_len));
request->msg_authentication_parameters_len = (u8_t)u16_value;
/* msgPrivacyParameters */
memset(request->msg_privacy_parameters, 0, SNMP_V3_MAX_PRIV_PARAM_LENGTH);
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_privacy_parameters,
&u16_value, SNMP_V3_MAX_PRIV_PARAM_LENGTH));
request->msg_privacy_parameters_len = (u8_t)u16_value;
/* validate securityParameters here (do this after decoding because we don't want to increase other counters for wrong frames)
* 1) securityParameters was correctly serialized if we reach here.
* 2) securityParameters are already cached.
* 3) if msgAuthoritativeEngineID is unknown, zero-length or too long:
b) https://tools.ietf.org/html/rfc3414#section-7
*/
{
const char *eid;
u8_t eid_len;
snmpv3_get_engine_id(&eid, &eid_len);
if ((request->msg_authoritative_engine_id_len == 0) ||
(request->msg_authoritative_engine_id_len != eid_len) ||
(memcmp(eid, request->msg_authoritative_engine_id, eid_len) != 0)) {
snmp_stats.unknownengineids++;
request->msg_flags = 0; /* noauthnopriv */
request->error_status = SNMP_ERR_UNKNOWN_ENGINEID;
return ERR_OK;
}
}
/* 4) verify username */
if (snmpv3_get_user((char *)request->msg_user_name, &auth, NULL, &priv, NULL)) {
snmp_stats.unknownusernames++;
request->msg_flags = 0; /* noauthnopriv */
request->error_status = SNMP_ERR_UNKNOWN_SECURITYNAME;
return ERR_OK;
}
/* 5) verify security level */
switch (request->msg_flags & (SNMP_V3_AUTH_FLAG | SNMP_V3_PRIV_FLAG)) {
case SNMP_V3_NOAUTHNOPRIV:
if ((auth != SNMP_V3_AUTH_ALGO_INVAL) || (priv != SNMP_V3_PRIV_ALGO_INVAL)) {
/* Invalid security level for user */
snmp_stats.unsupportedseclevels++;
request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
return ERR_OK;
}
break;
#if LWIP_SNMP_V3_CRYPTO
case SNMP_V3_AUTHNOPRIV:
if ((auth == SNMP_V3_AUTH_ALGO_INVAL) || (priv != SNMP_V3_PRIV_ALGO_INVAL)) {
/* Invalid security level for user */
snmp_stats.unsupportedseclevels++;
request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
return ERR_OK;
}
break;
case SNMP_V3_AUTHPRIV:
if ((auth == SNMP_V3_AUTH_ALGO_INVAL) || (priv == SNMP_V3_PRIV_ALGO_INVAL)) {
/* Invalid security level for user */
snmp_stats.unsupportedseclevels++;
request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
return ERR_OK;
}
break;
#endif
default:
snmp_stats.unsupportedseclevels++;
request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
return ERR_OK;
}
/* 6) if securitylevel specifies authentication, authenticate message. */
#if LWIP_SNMP_V3_CRYPTO
if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
const u8_t zero_arr[SNMP_V3_MAX_AUTH_PARAM_LENGTH] = { 0 };
u8_t key[20];
u8_t hmac[LWIP_MAX(SNMP_V3_SHA_LEN, SNMP_V3_MD5_LEN)];
struct snmp_pbuf_stream auth_stream;
if (request->msg_authentication_parameters_len > SNMP_V3_MAX_AUTH_PARAM_LENGTH) {
snmp_stats.wrongdigests++;
request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
return ERR_OK;
}
/* Rewind stream */
IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
IF_PARSE_EXEC(snmp_pbuf_stream_seek_abs(&auth_stream, inbound_msgAuthenticationParameters_offset));
/* Set auth parameters to zero for verification */
IF_PARSE_EXEC(snmp_asn1_enc_raw(&auth_stream, zero_arr, request->msg_authentication_parameters_len));
/* Verify authentication */
IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, &auth, key, NULL, NULL));
IF_PARSE_EXEC(snmpv3_auth(&auth_stream, request->inbound_pbuf->tot_len, key, auth, hmac));
if (memcmp(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH)) {
snmp_stats.wrongdigests++;
request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
return ERR_OK;
}
/* 7) if securitylevel specifies authentication, verify engineboots, enginetime and lastenginetime */
{
s32_t boots = snmpv3_get_engine_boots_internal();
if ((request->msg_authoritative_engine_boots != boots) || (boots == 2147483647UL)) {
snmp_stats.notintimewindows++;
request->msg_flags = SNMP_V3_AUTHNOPRIV;
request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
return ERR_OK;
}
}
{
s32_t time = snmpv3_get_engine_time_internal();
if (request->msg_authoritative_engine_time > (time + 150)) {
snmp_stats.notintimewindows++;
request->msg_flags = SNMP_V3_AUTHNOPRIV;
request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
return ERR_OK;
} else if (time > 150) {
if (request->msg_authoritative_engine_time < (time - 150)) {
snmp_stats.notintimewindows++;
request->msg_flags = SNMP_V3_AUTHNOPRIV;
request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
return ERR_OK;
}
}
}
}
#endif
/* 8) if securitylevel specifies privacy, decrypt message. */
#if LWIP_SNMP_V3_CRYPTO
if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
/* Decrypt message */
u8_t key[20];
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, NULL, NULL, &priv, key));
if (snmpv3_crypt(&pbuf_stream, tlv.value_len, key,
request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
request->msg_authoritative_engine_time, priv, SNMP_V3_PRIV_MODE_DECRYPT) != ERR_OK) {
snmp_stats.decryptionerrors++;
request->msg_flags = SNMP_V3_AUTHNOPRIV;
request->error_status = SNMP_ERR_DECRYIPTION_ERROR;
return ERR_OK;
}
}
#endif
/* 9) calculate max size of scoped pdu?
* 10) securityname for user is retrieved from usertable?
* 11) security data is cached?
* 12)
*/
/* Scoped PDU
* Encryption context
*/
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
/* contextEngineID */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->context_engine_id,
&u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
request->context_engine_id_len = (u8_t)u16_value;
/* TODO: do we need to verify this contextengineid too? */
/* contextName */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->context_name,
&u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
request->context_name_len = (u8_t)u16_value;
/* TODO: do we need to verify this contextname too? */
} else
#endif
{
/* decode community */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
err = snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->community, &request->community_strlen, SNMP_MAX_COMMUNITY_STR_LEN);
if (err == ERR_MEM) {
/* community string does not fit in our buffer -> its too long -> its invalid */
request->community_strlen = 0;
snmp_pbuf_stream_seek(&pbuf_stream, tlv.value_len);
} else {
IF_PARSE_ASSERT(err == ERR_OK);
}
/* add zero terminator */
request->community[request->community_strlen] = 0;
}
/* decode PDU type (next container level) */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.value_len <= pbuf_stream.length);
request->inbound_padding_len = pbuf_stream.length - tlv.value_len;
parent_tlv_value_len = tlv.value_len;
/* validate PDU type */
switch (tlv.type) {
case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_REQ):
/* GetRequest PDU */
snmp_stats.ingetrequests++;
break;
case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ):
/* GetNextRequest PDU */
snmp_stats.ingetnexts++;
break;
case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ):
/* GetBulkRequest PDU */
if (request->version < SNMP_VERSION_2c) {
/* RFC2089: invalid, drop packet */
return ERR_ARG;
}
break;
case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_SET_REQ):
/* SetRequest PDU */
snmp_stats.insetrequests++;
break;
default:
/* unsupported input PDU for this agent (no parse error) */
LWIP_DEBUGF(SNMP_DEBUG, ("Unknown/Invalid SNMP PDU type received: %d", tlv.type)); \
return ERR_ARG;
}
request->request_type = tlv.type & SNMP_ASN1_DATATYPE_MASK;
request->request_out_type = (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_RESP);
/* validate community (do this after decoding PDU type because we don't want to increase 'inbadcommunitynames' for wrong frame types */
if (request->community_strlen == 0) {
/* community string was too long or really empty*/
snmp_stats.inbadcommunitynames++;
snmp_authfail_trap();
return ERR_ARG;
} else if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
if (snmp_community_write[0] == 0) {
/* our write community is empty, that means all our objects are readonly */
request->error_status = SNMP_ERR_NOTWRITABLE;
request->error_index = 1;
} else if (strncmp(snmp_community_write, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
/* community name does not match */
snmp_stats.inbadcommunitynames++;
snmp_authfail_trap();
return ERR_ARG;
}
} else {
if (strncmp(snmp_community, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
/* community name does not match */
snmp_stats.inbadcommunitynames++;
snmp_authfail_trap();
return ERR_ARG;
}
}
/* decode request ID */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->request_id));
/* decode error status / non-repeaters */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->non_repeaters));
if (request->non_repeaters < 0) {
/* RFC 1905, 4.2.3 */
request->non_repeaters = 0;
}
} else {
/* only check valid value, don't touch 'request->error_status', maybe a response error status was already set to above; */
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
IF_PARSE_ASSERT(s32_value == SNMP_ERR_NOERROR);
}
/* decode error index / max-repetitions */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
IF_PARSE_ASSERT(parent_tlv_value_len > 0);
if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->max_repetitions));
if (request->max_repetitions < 0) {
/* RFC 1905, 4.2.3 */
request->max_repetitions = 0;
}
} else {
IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->error_index));
IF_PARSE_ASSERT(s32_value == 0);
}
/* decode varbind-list type (next container level) */
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len <= pbuf_stream.length));
request->inbound_varbind_offset = pbuf_stream.offset;
request->inbound_varbind_len = pbuf_stream.length - request->inbound_padding_len;
snmp_vb_enumerator_init(&(request->inbound_varbind_enumerator), request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
return ERR_OK;
}
#define OF_BUILD_EXEC(code) BUILD_EXEC(code, ERR_ARG)
static err_t
snmp_prepare_outbound_frame(struct snmp_request *request)
{
struct snmp_asn1_tlv tlv;
struct snmp_pbuf_stream *pbuf_stream = &(request->outbound_pbuf_stream);
/* try allocating pbuf(s) for maximum response size */
request->outbound_pbuf = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);
if (request->outbound_pbuf == NULL) {
return ERR_MEM;
}
snmp_pbuf_stream_init(pbuf_stream, request->outbound_pbuf, 0, request->outbound_pbuf->tot_len);
/* 'Message' sequence */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
/* version */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
snmp_asn1_enc_s32t_cnt(request->version, &tlv.value_len);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->version) );
#if LWIP_SNMP_V3
if (request->version < SNMP_VERSION_3) {
#endif
/* community */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->community_strlen);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
OF_BUILD_EXEC( snmp_asn1_enc_raw(pbuf_stream, request->community, request->community_strlen) );
#if LWIP_SNMP_V3
} else {
const char *id;
/* globalData */
request->outbound_msg_global_data_offset = pbuf_stream->offset;
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
/* msgID */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
snmp_asn1_enc_s32t_cnt(request->msg_id, &tlv.value_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_id));
/* msgMaxSize */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
snmp_asn1_enc_s32t_cnt(request->msg_max_size, &tlv.value_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_max_size));
/* msgFlags */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 1);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, &request->msg_flags, 1));
/* msgSecurityModel */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
snmp_asn1_enc_s32t_cnt(request->msg_security_model, &tlv.value_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_security_model));
/* end of msgGlobalData */
request->outbound_msg_global_data_end = pbuf_stream->offset;
/* msgSecurityParameters */
request->outbound_msg_security_parameters_str_offset = pbuf_stream->offset;
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, 0);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
request->outbound_msg_security_parameters_seq_offset = pbuf_stream->offset;
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
/* msgAuthoritativeEngineID */
snmpv3_get_engine_id(&id, &request->msg_authoritative_engine_id_len);
MEMCPY(request->msg_authoritative_engine_id, id, request->msg_authoritative_engine_id_len);
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_authoritative_engine_id_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authoritative_engine_id, request->msg_authoritative_engine_id_len));
request->msg_authoritative_engine_time = snmpv3_get_engine_time();
request->msg_authoritative_engine_boots = snmpv3_get_engine_boots();
/* msgAuthoritativeEngineBoots */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_boots, &tlv.value_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_boots));
/* msgAuthoritativeEngineTime */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_time, &tlv.value_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_time));
/* msgUserName */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_user_name_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_user_name, request->msg_user_name_len));
#if LWIP_SNMP_V3_CRYPTO
/* msgAuthenticationParameters */
if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
request->outbound_msg_authentication_parameters_offset = pbuf_stream->offset;
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
} else
#endif
{
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
}
#if LWIP_SNMP_V3_CRYPTO
/* msgPrivacyParameters */
if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
snmpv3_build_priv_param(request->msg_privacy_parameters);
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, SNMP_V3_MAX_PRIV_PARAM_LENGTH);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_privacy_parameters, SNMP_V3_MAX_PRIV_PARAM_LENGTH));
} else
#endif
{
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
}
/* End of msgSecurityParameters, so we can calculate the length of this sequence later */
request->outbound_msg_security_parameters_end = pbuf_stream->offset;
#if LWIP_SNMP_V3_CRYPTO
/* For encryption we have to encapsulate the payload in an octet string */
if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
request->outbound_scoped_pdu_string_offset = pbuf_stream->offset;
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, 0);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
}
#endif
/* Scoped PDU
* Encryption context
*/
request->outbound_scoped_pdu_seq_offset = pbuf_stream->offset;
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
/* contextEngineID */
snmpv3_get_engine_id(&id, &request->context_engine_id_len);
MEMCPY(request->context_engine_id, id, request->context_engine_id_len);
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_engine_id_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_engine_id, request->context_engine_id_len));
/* contextName */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_name_len);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_name, request->context_name_len));
}
#endif
/* 'PDU' sequence */
request->outbound_pdu_offset = pbuf_stream->offset;
SNMP_ASN1_SET_TLV_PARAMS(tlv, request->request_out_type, 3, 0);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
/* request ID */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
snmp_asn1_enc_s32t_cnt(request->request_id, &tlv.value_len);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->request_id) );
/* error status */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
request->outbound_error_status_offset = pbuf_stream->offset;
OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
/* error index */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
request->outbound_error_index_offset = pbuf_stream->offset;
OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
/* 'VarBindList' sequence */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
request->outbound_varbind_offset = pbuf_stream->offset;
return ERR_OK;
}
/** Calculate the length of a varbind list */
err_t
snmp_varbind_length(struct snmp_varbind *varbind, struct snmp_varbind_len *len)
{
/* calculate required lengths */
snmp_asn1_enc_oid_cnt(varbind->oid.id, varbind->oid.len, &len->oid_value_len);
snmp_asn1_enc_length_cnt(len->oid_value_len, &len->oid_len_len);
if (varbind->value_len == 0) {
len->value_value_len = 0;
} else if (varbind->value_len & SNMP_GET_VALUE_RAW_DATA) {
len->value_value_len = varbind->value_len & (~SNMP_GET_VALUE_RAW_DATA);
} else {
switch (varbind->type) {
case SNMP_ASN1_TYPE_INTEGER:
if (varbind->value_len != sizeof (s32_t)) {
return ERR_VAL;
}
snmp_asn1_enc_s32t_cnt(*((s32_t *) varbind->value), &len->value_value_len);
break;
case SNMP_ASN1_TYPE_COUNTER:
case SNMP_ASN1_TYPE_GAUGE:
case SNMP_ASN1_TYPE_TIMETICKS:
if (varbind->value_len != sizeof (u32_t)) {
return ERR_VAL;
}
snmp_asn1_enc_u32t_cnt(*((u32_t *) varbind->value), &len->value_value_len);
break;
case SNMP_ASN1_TYPE_OCTET_STRING:
case SNMP_ASN1_TYPE_IPADDR:
case SNMP_ASN1_TYPE_OPAQUE:
len->value_value_len = varbind->value_len;
break;
case SNMP_ASN1_TYPE_NULL:
if (varbind->value_len != 0) {
return ERR_VAL;
}
len->value_value_len = 0;
break;
case SNMP_ASN1_TYPE_OBJECT_ID:
if ((varbind->value_len & 0x03) != 0) {
return ERR_VAL;
}
snmp_asn1_enc_oid_cnt((u32_t *) varbind->value, varbind->value_len >> 2, &len->value_value_len);
break;
#if LWIP_HAVE_INT64
case SNMP_ASN1_TYPE_COUNTER64:
if (varbind->value_len != sizeof(u64_t)) {
return ERR_VAL;
}
snmp_asn1_enc_u64t_cnt(*(u64_t *)varbind->value, &len->value_value_len);
break;
#endif
default:
/* unsupported type */
return ERR_VAL;
}
}
snmp_asn1_enc_length_cnt(len->value_value_len, &len->value_len_len);
len->vb_value_len = 1 + len->oid_len_len + len->oid_value_len + 1 + len->value_len_len + len->value_value_len;
snmp_asn1_enc_length_cnt(len->vb_value_len, &len->vb_len_len);
return ERR_OK;
}
#define OVB_BUILD_EXEC(code) BUILD_EXEC(code, ERR_ARG)
err_t
snmp_append_outbound_varbind(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbind)
{
struct snmp_asn1_tlv tlv;
struct snmp_varbind_len len;
err_t err;
err = snmp_varbind_length(varbind, &len);
if (err != ERR_OK) {
return err;
}
/* check length already before adding first data because in case of GetBulk,
* data added so far is returned and therefore no partial data shall be added
*/
if ((1 + len.vb_len_len + len.vb_value_len) > pbuf_stream->length) {
return ERR_BUF;
}
/* 'VarBind' sequence */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, len.vb_len_len, len.vb_value_len);
OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
/* VarBind OID */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OBJECT_ID, len.oid_len_len, len.oid_value_len);
OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
OVB_BUILD_EXEC(snmp_asn1_enc_oid(pbuf_stream, varbind->oid.id, varbind->oid.len));
/* VarBind value */
SNMP_ASN1_SET_TLV_PARAMS(tlv, varbind->type, len.value_len_len, len.value_value_len);
OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
if (len.value_value_len > 0) {
if (varbind->value_len & SNMP_GET_VALUE_RAW_DATA) {
OVB_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, (u8_t *) varbind->value, len.value_value_len));
} else {
switch (varbind->type) {
case SNMP_ASN1_TYPE_INTEGER:
OVB_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, len.value_value_len, *((s32_t *) varbind->value)));
break;
case SNMP_ASN1_TYPE_COUNTER:
case SNMP_ASN1_TYPE_GAUGE:
case SNMP_ASN1_TYPE_TIMETICKS:
OVB_BUILD_EXEC(snmp_asn1_enc_u32t(pbuf_stream, len.value_value_len, *((u32_t *) varbind->value)));
break;
case SNMP_ASN1_TYPE_OCTET_STRING:
case SNMP_ASN1_TYPE_IPADDR:
case SNMP_ASN1_TYPE_OPAQUE:
OVB_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, (u8_t *) varbind->value, len.value_value_len));
len.value_value_len = varbind->value_len;
break;
case SNMP_ASN1_TYPE_OBJECT_ID:
OVB_BUILD_EXEC(snmp_asn1_enc_oid(pbuf_stream, (u32_t *) varbind->value, varbind->value_len / sizeof (u32_t)));
break;
#if LWIP_HAVE_INT64
case SNMP_ASN1_TYPE_COUNTER64:
OVB_BUILD_EXEC(snmp_asn1_enc_u64t(pbuf_stream, len.value_value_len, *(u64_t *) varbind->value));
break;
#endif
default:
LWIP_ASSERT("Unknown variable type", 0);
break;
}
}
}
return ERR_OK;
}
static err_t
snmp_complete_outbound_frame(struct snmp_request *request)
{
struct snmp_asn1_tlv tlv;
u16_t frame_size;
u8_t outbound_padding = 0;
if (request->version == SNMP_VERSION_1) {
if (request->error_status != SNMP_ERR_NOERROR) {
/* map v2c error codes to v1 compliant error code (according to RFC 2089) */
switch (request->error_status) {
/* mapping of implementation specific "virtual" error codes
* (during processing of frame we already stored them in error_status field,
* so no need to check all varbinds here for those exceptions as suggested by RFC) */
case SNMP_ERR_NOSUCHINSTANCE:
case SNMP_ERR_NOSUCHOBJECT:
case SNMP_ERR_ENDOFMIBVIEW:
request->error_status = SNMP_ERR_NOSUCHNAME;
break;
/* mapping according to RFC */
case SNMP_ERR_WRONGVALUE:
case SNMP_ERR_WRONGENCODING:
case SNMP_ERR_WRONGTYPE:
case SNMP_ERR_WRONGLENGTH:
case SNMP_ERR_INCONSISTENTVALUE:
request->error_status = SNMP_ERR_BADVALUE;
break;
case SNMP_ERR_NOACCESS:
case SNMP_ERR_NOTWRITABLE:
case SNMP_ERR_NOCREATION:
case SNMP_ERR_INCONSISTENTNAME:
case SNMP_ERR_AUTHORIZATIONERROR:
request->error_status = SNMP_ERR_NOSUCHNAME;
break;
case SNMP_ERR_RESOURCEUNAVAILABLE:
case SNMP_ERR_COMMITFAILED:
case SNMP_ERR_UNDOFAILED:
default:
request->error_status = SNMP_ERR_GENERROR;
break;
}
}
} else {
if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
/* map error codes to according to RFC 1905 (4.2.5. The SetRequest-PDU) return 'NotWritable' for unknown OIDs) */
switch (request->error_status) {
case SNMP_ERR_NOSUCHINSTANCE:
case SNMP_ERR_NOSUCHOBJECT:
case SNMP_ERR_ENDOFMIBVIEW:
request->error_status = SNMP_ERR_NOTWRITABLE;
break;
default:
break;
}
}
if (request->error_status >= SNMP_VARBIND_EXCEPTION_OFFSET) {
/* should never occur because v2 frames store exceptions directly inside varbinds and not as frame error_status */
LWIP_DEBUGF(SNMP_DEBUG, ("snmp_complete_outbound_frame() > Found v2 request with varbind exception code stored as error status!\n"));
return ERR_ARG;
}
}
if ((request->error_status != SNMP_ERR_NOERROR) || (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ)) {
/* all inbound vars are returned in response without any modification for error responses and successful set requests*/
struct snmp_pbuf_stream inbound_stream;
OF_BUILD_EXEC( snmp_pbuf_stream_init(&inbound_stream, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len) );
OF_BUILD_EXEC( snmp_pbuf_stream_init(&(request->outbound_pbuf_stream), request->outbound_pbuf, request->outbound_varbind_offset, request->outbound_pbuf->tot_len - request->outbound_varbind_offset) );
OF_BUILD_EXEC( snmp_pbuf_stream_writeto(&inbound_stream, &(request->outbound_pbuf_stream), 0) );
}
frame_size = request->outbound_pbuf_stream.offset;
#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
/* Calculate padding for encryption */
if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
u8_t i;
outbound_padding = (8 - (u8_t)((frame_size - request->outbound_scoped_pdu_seq_offset) & 0x07)) & 0x07;
for (i = 0; i < outbound_padding; i++) {
OF_BUILD_EXEC( snmp_pbuf_stream_write(&request->outbound_pbuf_stream, 0) );
}
}
#endif
/* complete missing length in 'Message' sequence ; 'Message' tlv is located at the beginning (offset 0) */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size + outbound_padding - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
OF_BUILD_EXEC( snmp_pbuf_stream_init(&(request->outbound_pbuf_stream), request->outbound_pbuf, 0, request->outbound_pbuf->tot_len) );
OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
#if LWIP_SNMP_V3
if (request->version == SNMP_VERSION_3) {
/* complete missing length in 'globalData' sequence */
/* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_global_data_end
- request->outbound_msg_global_data_offset - 1 - 1);
OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_global_data_offset));
OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
/* complete missing length in 'msgSecurityParameters' sequence */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, request->outbound_msg_security_parameters_end
- request->outbound_msg_security_parameters_str_offset - 1 - 1);
OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_security_parameters_str_offset));
OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_security_parameters_end
- request->outbound_msg_security_parameters_seq_offset - 1 - 1);
OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_security_parameters_seq_offset));
OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
/* complete missing length in scoped PDU sequence */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_scoped_pdu_seq_offset - 1 - 3);
OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_scoped_pdu_seq_offset));
OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
}
#endif
/* complete missing length in 'PDU' sequence */
SNMP_ASN1_SET_TLV_PARAMS(tlv, request->request_out_type, 3,
frame_size - request->outbound_pdu_offset - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_pdu_offset) );
OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
/* process and encode final error status */
if (request->error_status != 0) {
u16_t len;
snmp_asn1_enc_s32t_cnt(request->error_status, &len);
if (len != 1) {
/* error, we only reserved one byte for it */
return ERR_ARG;
}
OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_error_status_offset) );
OF_BUILD_EXEC( snmp_asn1_enc_s32t(&(request->outbound_pbuf_stream), len, request->error_status) );
/* for compatibility to v1, log statistics; in v2 (RFC 1907) these statistics are obsoleted */
switch (request->error_status) {
case SNMP_ERR_TOOBIG:
snmp_stats.outtoobigs++;
break;
case SNMP_ERR_NOSUCHNAME:
snmp_stats.outnosuchnames++;
break;
case SNMP_ERR_BADVALUE:
snmp_stats.outbadvalues++;
break;
case SNMP_ERR_GENERROR:
default:
snmp_stats.outgenerrs++;
break;
}
if (request->error_status == SNMP_ERR_TOOBIG) {
request->error_index = 0; /* defined by RFC 1157 */
} else if (request->error_index == 0) {
/* set index to varbind where error occured (if not already set before, e.g. during GetBulk processing) */
request->error_index = request->inbound_varbind_enumerator.varbind_count;
}
} else {
if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
snmp_stats.intotalsetvars += request->inbound_varbind_enumerator.varbind_count;
} else {
snmp_stats.intotalreqvars += request->inbound_varbind_enumerator.varbind_count;
}
}
/* encode final error index*/
if (request->error_index != 0) {
u16_t len;
snmp_asn1_enc_s32t_cnt(request->error_index, &len);
if (len != 1) {
/* error, we only reserved one byte for it */
return ERR_VAL;
}
OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_error_index_offset) );
OF_BUILD_EXEC( snmp_asn1_enc_s32t(&(request->outbound_pbuf_stream), len, request->error_index) );
}
/* complete missing length in 'VarBindList' sequence ; 'VarBindList' tlv is located directly before varbind offset */
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_varbind_offset);
OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_varbind_offset - 1 - 3) ); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
/* Authenticate response */
#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
/* Encrypt response */
if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
u8_t key[20];
snmpv3_priv_algo_t algo;
/* complete missing length in PDU sequence */
OF_BUILD_EXEC(snmp_pbuf_stream_init(&request->outbound_pbuf_stream, request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_scoped_pdu_string_offset));
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, frame_size + outbound_padding
- request->outbound_scoped_pdu_string_offset - 1 - 3);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
OF_BUILD_EXEC(snmpv3_get_user((char *)request->msg_user_name, NULL, NULL, &algo, key));
OF_BUILD_EXEC(snmpv3_crypt(&request->outbound_pbuf_stream, tlv.value_len, key,
request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
request->msg_authoritative_engine_time, algo, SNMP_V3_PRIV_MODE_ENCRYPT));
}
if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_AUTH_FLAG)) {
u8_t key[20];
snmpv3_auth_algo_t algo;
u8_t hmac[20];
OF_BUILD_EXEC(snmpv3_get_user((char *)request->msg_user_name, &algo, key, NULL, NULL));
OF_BUILD_EXEC(snmp_pbuf_stream_init(&(request->outbound_pbuf_stream),
request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
OF_BUILD_EXEC(snmpv3_auth(&request->outbound_pbuf_stream, frame_size + outbound_padding, key, algo, hmac));
MEMCPY(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
OF_BUILD_EXEC(snmp_pbuf_stream_init(&request->outbound_pbuf_stream,
request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&request->outbound_pbuf_stream,
request->outbound_msg_authentication_parameters_offset));
SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
OF_BUILD_EXEC(snmp_ans1_enc_tlv(&request->outbound_pbuf_stream, &tlv));
OF_BUILD_EXEC(snmp_asn1_enc_raw(&request->outbound_pbuf_stream,
request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
}
#endif
pbuf_realloc(request->outbound_pbuf, frame_size + outbound_padding);
snmp_stats.outgetresponses++;
snmp_stats.outpkts++;
return ERR_OK;
}
static void
snmp_execute_write_callbacks(struct snmp_request *request)
{
struct snmp_varbind_enumerator inbound_varbind_enumerator;
struct snmp_varbind vb;
snmp_vb_enumerator_init(&inbound_varbind_enumerator, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
vb.value = NULL; /* do NOT decode value (we enumerate outbound buffer here, so all varbinds have values assigned, which we don't need here) */
while (snmp_vb_enumerator_get_next(&inbound_varbind_enumerator, &vb) == SNMP_VB_ENUMERATOR_ERR_OK) {
snmp_write_callback(vb.oid.id, vb.oid.len, snmp_write_callback_arg);
}
}
/* ----------------------------------------------------------------------- */
/* VarBind enumerator methods */
/* ----------------------------------------------------------------------- */
void
snmp_vb_enumerator_init(struct snmp_varbind_enumerator *enumerator, struct pbuf *p, u16_t offset, u16_t length)
{
snmp_pbuf_stream_init(&(enumerator->pbuf_stream), p, offset, length);
enumerator->varbind_count = 0;
}
#define VB_PARSE_EXEC(code) PARSE_EXEC(code, SNMP_VB_ENUMERATOR_ERR_ASN1ERROR)
#define VB_PARSE_ASSERT(code) PARSE_ASSERT(code, SNMP_VB_ENUMERATOR_ERR_ASN1ERROR)
snmp_vb_enumerator_err_t
snmp_vb_enumerator_get_next(struct snmp_varbind_enumerator *enumerator, struct snmp_varbind *varbind)
{
struct snmp_asn1_tlv tlv;
u16_t varbind_len;
err_t err;
if (enumerator->pbuf_stream.length == 0) {
return SNMP_VB_ENUMERATOR_ERR_EOVB;
}
enumerator->varbind_count++;
/* decode varbind itself (parent container of a varbind) */
VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
VB_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len <= enumerator->pbuf_stream.length));
varbind_len = tlv.value_len;
/* decode varbind name (object id) */
VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
VB_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_OBJECT_ID) && (SNMP_ASN1_TLV_LENGTH(tlv) < varbind_len) && (tlv.value_len < enumerator->pbuf_stream.length));
VB_PARSE_EXEC(snmp_asn1_dec_oid(&(enumerator->pbuf_stream), tlv.value_len, varbind->oid.id, &(varbind->oid.len), SNMP_MAX_OBJ_ID_LEN));
varbind_len -= SNMP_ASN1_TLV_LENGTH(tlv);
/* decode varbind value (object id) */
VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
VB_PARSE_ASSERT((SNMP_ASN1_TLV_LENGTH(tlv) == varbind_len) && (tlv.value_len <= enumerator->pbuf_stream.length));
varbind->type = tlv.type;
/* shall the value be decoded ? */
if (varbind->value != NULL) {
switch (varbind->type) {
case SNMP_ASN1_TYPE_INTEGER:
VB_PARSE_EXEC(snmp_asn1_dec_s32t(&(enumerator->pbuf_stream), tlv.value_len, (s32_t *)varbind->value));
varbind->value_len = sizeof(s32_t);
break;
case SNMP_ASN1_TYPE_COUNTER:
case SNMP_ASN1_TYPE_GAUGE:
case SNMP_ASN1_TYPE_TIMETICKS:
VB_PARSE_EXEC(snmp_asn1_dec_u32t(&(enumerator->pbuf_stream), tlv.value_len, (u32_t *)varbind->value));
varbind->value_len = sizeof(u32_t);
break;
case SNMP_ASN1_TYPE_OCTET_STRING:
case SNMP_ASN1_TYPE_OPAQUE:
err = snmp_asn1_dec_raw(&(enumerator->pbuf_stream), tlv.value_len, (u8_t *)varbind->value, &varbind->value_len, SNMP_MAX_VALUE_SIZE);
if (err == ERR_MEM) {
return SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH;
}
VB_PARSE_ASSERT(err == ERR_OK);
break;
case SNMP_ASN1_TYPE_NULL:
varbind->value_len = 0;
break;
case SNMP_ASN1_TYPE_OBJECT_ID:
/* misuse tlv.length_len as OID_length transporter */
err = snmp_asn1_dec_oid(&(enumerator->pbuf_stream), tlv.value_len, (u32_t *)varbind->value, &tlv.length_len, SNMP_MAX_OBJ_ID_LEN);
if (err == ERR_MEM) {
return SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH;
}
VB_PARSE_ASSERT(err == ERR_OK);
varbind->value_len = tlv.length_len * sizeof(u32_t);
break;
case SNMP_ASN1_TYPE_IPADDR:
if (tlv.value_len == 4) {
/* must be exactly 4 octets! */
VB_PARSE_EXEC(snmp_asn1_dec_raw(&(enumerator->pbuf_stream), tlv.value_len, (u8_t *)varbind->value, &varbind->value_len, SNMP_MAX_VALUE_SIZE));
} else {
VB_PARSE_ASSERT(0);
}
break;
#if LWIP_HAVE_INT64
case SNMP_ASN1_TYPE_COUNTER64:
VB_PARSE_EXEC(snmp_asn1_dec_u64t(&(enumerator->pbuf_stream), tlv.value_len, (u64_t *)varbind->value));
varbind->value_len = sizeof(u64_t);
break;
#endif
default:
VB_PARSE_ASSERT(0);
break;
}
} else {
snmp_pbuf_stream_seek(&(enumerator->pbuf_stream), tlv.value_len);
varbind->value_len = tlv.value_len;
}
return SNMP_VB_ENUMERATOR_ERR_OK;
}
#endif /* LWIP_SNMP */