#include <sys/queue.h>
#include <sys/time.h>
#include <sys/tree.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <event.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "application.h"
#include "log.h"
#include "mib.h"
#include "smi.h"
#include "snmp.h"
#include "snmpd.h"
#include "snmpe.h"
#define OID(...) (struct ber_oid){ { __VA_ARGS__ }, \
(sizeof((uint32_t []) { __VA_ARGS__ }) / sizeof(uint32_t)) }
TAILQ_HEAD(, appl_context) contexts = TAILQ_HEAD_INITIALIZER(contexts);
struct appl_agentcap {
struct appl_backend *aa_backend;
struct appl_context *aa_context;
uint32_t aa_index;
struct ber_oid aa_oid;
char aa_descr[256];
uint32_t aa_uptime;
TAILQ_ENTRY(appl_agentcap) aa_entry;
};
struct appl_context {
char ac_name[APPL_CONTEXTNAME_MAX + 1];
RB_HEAD(appl_regions, appl_region) ac_regions;
TAILQ_HEAD(, appl_agentcap) ac_agentcaps;
int ac_agentcap_lastid;
uint32_t ac_agentcap_lastchange;
struct timespec ac_starttime;
TAILQ_ENTRY(appl_context) ac_entries;
};
struct appl_region {
struct ber_oid ar_oid;
uint8_t ar_priority;
int32_t ar_timeout;
int ar_instance;
int ar_subtree;
struct appl_backend *ar_backend;
struct appl_region *ar_next;
RB_ENTRY(appl_region) ar_entry;
};
struct appl_request_upstream {
struct appl_context *aru_ctx;
struct snmp_message *aru_statereference;
enum snmp_pdutype aru_requesttype;
enum snmp_pdutype aru_responsetype;
int32_t aru_requestid;
int32_t aru_transactionid;
uint16_t aru_nonrepeaters;
uint16_t aru_maxrepetitions;
struct appl_varbind_internal *aru_vblist;
size_t aru_varbindlen;
enum appl_error aru_error;
int16_t aru_index;
int aru_locked;
enum snmp_version aru_pduversion;
};
struct appl_request_downstream {
struct appl_request_upstream *ard_request;
struct appl_backend *ard_backend;
enum snmp_pdutype ard_requesttype;
uint16_t ard_nonrepeaters;
uint16_t ard_maxrepetitions;
int32_t ard_requestid;
uint8_t ard_retries;
struct appl_varbind_internal *ard_vblist;
struct event ard_timer;
RB_ENTRY(appl_request_downstream) ard_entry;
};
enum appl_varbind_state {
APPL_VBSTATE_MUSTFILL,
APPL_VBSTATE_NEW,
APPL_VBSTATE_PENDING,
APPL_VBSTATE_DONE
};
struct appl_varbind_internal {
enum appl_varbind_state avi_state;
struct appl_varbind avi_varbind;
struct appl_region *avi_region;
struct ber_oid avi_origid;
int16_t avi_index;
struct appl_request_upstream *avi_request_upstream;
struct appl_request_downstream *avi_request_downstream;
struct appl_varbind_internal *avi_next;
struct appl_varbind_internal *avi_sub;
};
struct snmp_target_mib {
uint32_t snmp_unavailablecontexts;
uint32_t snmp_unknowncontexts;
} snmp_target_mib;
void appl_agentcap_free(struct appl_agentcap *);
enum appl_error appl_region(struct appl_context *, uint32_t, uint8_t,
struct ber_oid *, uint8_t, int, int, struct appl_backend *);
void appl_region_free(struct appl_context *, struct appl_region *);
enum appl_error appl_region_unregister_match(struct appl_context *, uint8_t,
struct ber_oid *, char *, struct appl_backend *, int);
struct appl_region *appl_region_find(struct appl_context *,
const struct ber_oid *);
struct appl_region *appl_region_next(struct appl_context *,
struct ber_oid *, struct appl_region *);
void appl_request_upstream_free(struct appl_request_upstream *);
void appl_request_downstream_free(struct appl_request_downstream *);
void appl_request_upstream_resolve(struct appl_request_upstream *);
void appl_request_downstream_send(struct appl_request_downstream *);
void appl_request_downstream_timeout(int, short, void *);
void appl_request_upstream_reply(struct appl_request_upstream *);
int appl_varbind_valid(struct appl_varbind *, struct appl_varbind_internal *,
int, int, int, const char **);
int appl_error_valid(enum appl_error, enum snmp_pdutype);
unsigned int appl_ber_any(struct ber_element *);
int appl_varbind_backend(struct appl_varbind_internal *);
void appl_varbind_error(struct appl_varbind_internal *, enum appl_error);
void appl_pdu_log(struct appl_backend *, enum snmp_pdutype, int32_t, uint16_t,
uint16_t, struct appl_varbind *);
void ober_oid_nextsibling(struct ber_oid *);
int appl_region_cmp(struct appl_region *, struct appl_region *);
int appl_request_cmp(struct appl_request_downstream *,
struct appl_request_downstream *);
RB_PROTOTYPE_STATIC(appl_regions, appl_region, ar_entry, appl_region_cmp);
RB_PROTOTYPE_STATIC(appl_requests, appl_request_downstream, ard_entry,
appl_request_cmp);
#define APPL_CONTEXT_NAME(ctx) (ctx->ac_name[0] == '\0' ? NULL : ctx->ac_name)
void
appl(void)
{
appl_agentx();
}
void
appl_init(void)
{
if (appl_context(NULL, 1) == NULL)
fatal("Failed to create default context");
appl_blocklist_init();
appl_internal_init();
appl_agentx_init();
}
void
appl_shutdown(void)
{
struct appl_context *ctx, *tctx;
appl_blocklist_shutdown();
appl_internal_shutdown();
appl_agentx_shutdown();
TAILQ_FOREACH_SAFE(ctx, &contexts, ac_entries, tctx) {
assert(RB_EMPTY(&(ctx->ac_regions)));
assert(TAILQ_EMPTY(&(ctx->ac_agentcaps)));
TAILQ_REMOVE(&contexts, ctx, ac_entries);
free(ctx);
}
}
struct appl_context *
appl_context(const char *name, int create)
{
struct appl_context *ctx;
if (name == NULL)
name = "";
if (strlen(name) > APPL_CONTEXTNAME_MAX) {
errno = EINVAL;
return NULL;
}
TAILQ_FOREACH(ctx, &contexts, ac_entries) {
if (strcmp(name, ctx->ac_name) == 0)
return ctx;
}
if (!create) {
errno = ENOENT;
return NULL;
}
if ((ctx = malloc(sizeof(*ctx))) == NULL)
return NULL;
strlcpy(ctx->ac_name, name, sizeof(ctx->ac_name));
RB_INIT(&(ctx->ac_regions));
TAILQ_INIT(&(ctx->ac_agentcaps));
ctx->ac_agentcap_lastid = 0;
ctx->ac_agentcap_lastchange = 0;
TAILQ_INSERT_TAIL(&contexts, ctx, ac_entries);
if (clock_gettime(CLOCK_MONOTONIC, &ctx->ac_starttime) == -1)
fatal("clock_gettime");
return ctx;
}
enum appl_error
appl_addagentcaps(const char *ctxname, struct ber_oid *oid, const char *descr,
struct appl_backend *backend)
{
struct appl_context *ctx;
struct appl_agentcap *cap;
char oidbuf[1024];
if (ctxname == NULL)
ctxname = "";
mib_oid2string(oid, oidbuf, sizeof(oidbuf), snmpd_env->sc_oidfmt);
log_info("%s: Adding agent capabilities %s context(%s)",
backend->ab_name, oidbuf, ctxname);
if ((ctx = appl_context(ctxname, 0)) == NULL) {
log_info("%s: Can't add agent capabilities %s: "
"Unsupported context \"%s\"", backend->ab_name, oidbuf,
ctxname);
return APPL_ERROR_UNSUPPORTEDCONTEXT;
}
if ((cap = malloc(sizeof(*cap))) == NULL) {
log_warn("%s: Can't add agent capabilities %s",
backend->ab_name, oidbuf);
return APPL_ERROR_PROCESSINGERROR;
}
cap->aa_backend = backend;
cap->aa_context = ctx;
cap->aa_index = ++ctx->ac_agentcap_lastid;
cap->aa_oid = *oid;
cap->aa_uptime = appl_sysuptime(ctxname);
if (strlcpy(cap->aa_descr, descr,
sizeof(cap->aa_descr)) >= sizeof(cap->aa_descr)) {
log_info("%s: Can't add agent capabilities %s: "
"Invalid description", backend->ab_name, oidbuf);
free(cap);
return APPL_ERROR_PARSEERROR;
}
TAILQ_INSERT_TAIL(&(ctx->ac_agentcaps), cap, aa_entry);
ctx->ac_agentcap_lastchange = cap->aa_uptime;
return APPL_ERROR_NOERROR;
}
enum appl_error
appl_removeagentcaps(const char *ctxname, struct ber_oid *oid,
struct appl_backend *backend)
{
struct appl_context *ctx;
struct appl_agentcap *cap, *tmp;
char oidbuf[1024];
int found = 0;
if (ctxname == NULL)
ctxname = "";
mib_oid2string(oid, oidbuf, sizeof(oidbuf), snmpd_env->sc_oidfmt);
log_info("%s: Removing agent capabilities %s context(%s)",
backend->ab_name, oidbuf, ctxname);
if ((ctx = appl_context(ctxname, 0)) == NULL) {
log_info("%s: Can't remove agent capabilities %s: "
"Unsupported context \"%s\"", backend->ab_name, oidbuf,
ctxname);
return APPL_ERROR_UNSUPPORTEDCONTEXT;
}
TAILQ_FOREACH_SAFE(cap, &(ctx->ac_agentcaps), aa_entry, tmp) {
if (cap->aa_backend != backend ||
ober_oid_cmp(oid, &(cap->aa_oid)) != 0)
continue;
found = 1;
appl_agentcap_free(cap);
}
if (found)
return APPL_ERROR_NOERROR;
log_info("%s: Can't remove agent capabilities %s: not found",
backend->ab_name, oidbuf);
return APPL_ERROR_UNKNOWNAGENTCAPS;
}
void
appl_agentcap_free(struct appl_agentcap *cap)
{
TAILQ_REMOVE(&(cap->aa_context->ac_agentcaps), cap, aa_entry);
cap->aa_context->ac_agentcap_lastchange =
appl_sysuptime(cap->aa_context->ac_name);
free(cap);
}
uint32_t
appl_sysuptime(const char *ctxname)
{
struct appl_context *ctx;
struct timespec now, uptime;
if ((ctx = appl_context(ctxname, 0)) == NULL)
return 0;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
fatal("clock_gettime");
timespecsub(&now, &ctx->ac_starttime, &uptime);
return uptime.tv_sec * 100 + uptime.tv_nsec / 10000000;
}
struct ber_element *
appl_sysorlastchange(struct ber_oid *oid)
{
struct appl_context *ctx;
struct ber_element *value;
ctx = appl_context(NULL, 0);
value = ober_add_integer(NULL, ctx->ac_agentcap_lastchange);
if (value != NULL)
ober_set_header(value, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS);
else
log_warn("ober_add_integer");
return value;
}
#define SYSORIDX_POS 10
struct ber_element *
appl_sysortable(struct ber_oid *oid)
{
struct appl_context *ctx;
struct appl_agentcap *cap;
struct ber_element *value = NULL;
if (oid->bo_n != SYSORIDX_POS + 1)
goto notfound;
ctx = appl_context(NULL, 0);
TAILQ_FOREACH(cap, &(ctx->ac_agentcaps), aa_entry) {
if (cap->aa_index == oid->bo_id[SYSORIDX_POS])
break;
}
if (cap == NULL)
goto notfound;
if (ober_oid_cmp(&OID(MIB_sysORID), oid) == -2)
value = ober_add_oid(NULL, &(cap->aa_oid));
else if (ober_oid_cmp(&OID(MIB_sysORDescr), oid) == -2)
value = ober_add_string(NULL, cap->aa_descr);
else if (ober_oid_cmp(&OID(MIB_sysORUpTime), oid) == -2) {
if ((value = ober_add_integer(NULL, cap->aa_uptime)) != NULL)
ober_set_header(value,
BER_CLASS_APPLICATION, SNMP_T_TIMETICKS);
}
if (value == NULL)
log_warn("ober_add_*");
return value;
notfound:
if ((value = appl_exception(APPL_EXC_NOSUCHINSTANCE)) == NULL)
log_warn("appl_exception");
return value;
}
struct ber_element *
appl_sysortable_getnext(int8_t include, struct ber_oid *oid)
{
struct appl_context *ctx;
struct appl_agentcap *cap;
struct ber_element *value = NULL;
if (oid->bo_n < SYSORIDX_POS + 1) {
include = 1;
oid->bo_id[SYSORIDX_POS] = 0;
} else if (oid->bo_n < SYSORIDX_POS + 1)
include = 0;
ctx = appl_context(NULL, 0);
TAILQ_FOREACH(cap, &(ctx->ac_agentcaps), aa_entry) {
if (cap->aa_index > oid->bo_id[SYSORIDX_POS])
break;
if (cap->aa_index == oid->bo_id[SYSORIDX_POS] && include)
break;
}
if (cap == NULL) {
value = appl_exception(APPL_EXC_NOSUCHINSTANCE);
goto done;
}
oid->bo_id[SYSORIDX_POS] = cap->aa_index;
oid->bo_n = SYSORIDX_POS + 1;
if (ober_oid_cmp(&OID(MIB_sysORID), oid) == -2)
value = ober_add_oid(NULL, &(cap->aa_oid));
else if (ober_oid_cmp(&OID(MIB_sysORDescr), oid) == -2)
value = ober_add_string(NULL, cap->aa_descr);
else if (ober_oid_cmp(&OID(MIB_sysORUpTime), oid) == -2) {
if ((value = ober_add_integer(NULL, cap->aa_uptime)) != NULL)
ober_set_header(value,
BER_CLASS_APPLICATION, SNMP_T_TIMETICKS);
}
done:
if (value == NULL)
log_warn("ober_add_*");
return value;
}
struct ber_element *
appl_targetmib(struct ber_oid *oid)
{
struct ber_element *value = NULL;
if (ober_oid_cmp(oid, &OID(MIB_snmpUnavailableContexts, 0)) == 0)
value = ober_add_integer(NULL,
snmp_target_mib.snmp_unavailablecontexts);
else if (ober_oid_cmp(oid, &OID(MIB_snmpUnknownContexts, 0)) == 0)
value = ober_add_integer(NULL,
snmp_target_mib.snmp_unknowncontexts);
if (value != NULL)
ober_set_header(value, BER_CLASS_APPLICATION, SNMP_T_COUNTER32);
return value;
}
enum appl_error
appl_region(struct appl_context *ctx, uint32_t timeout, uint8_t priority,
struct ber_oid *oid, uint8_t range_subid, int instance, int subtree,
struct appl_backend *backend)
{
struct appl_region *region = NULL, *nregion;
char oidbuf[1024], regionbuf[1024], subidbuf[11];
size_t i, bo_n;
bo_n = oid->bo_n;
if (range_subid != 0)
oid->bo_n = range_subid;
mib_oid2string(oid, oidbuf, sizeof(oidbuf), snmpd_env->sc_oidfmt);
if (range_subid != 0) {
oid->bo_n = bo_n;
i = range_subid + 1;
} else
i = oid->bo_n;
for (; i < oid->bo_n; i++) {
strlcat(oidbuf, ".", sizeof(oidbuf));
snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
oid->bo_id[i]);
strlcat(oidbuf, subidbuf, sizeof(oidbuf));
}
region = appl_region_find(ctx, oid);
if (region != NULL && region->ar_subtree &&
region->ar_backend != backend)
goto overlap;
if ((nregion = malloc(sizeof(*nregion))) == NULL) {
log_warn("%s: Can't register %s: Processing error",
backend->ab_name, oidbuf);
return APPL_ERROR_PROCESSINGERROR;
}
nregion->ar_oid = *oid;
nregion->ar_priority = priority;
nregion->ar_timeout = timeout;
nregion->ar_instance = instance;
nregion->ar_subtree = subtree;
nregion->ar_backend = backend;
nregion->ar_next = NULL;
region = RB_INSERT(appl_regions, &(ctx->ac_regions), nregion);
if (region == NULL)
return APPL_ERROR_NOERROR;
if (region->ar_priority == priority)
goto duplicate;
if (region->ar_priority > priority) {
RB_REMOVE(appl_regions, &(ctx->ac_regions), region);
RB_INSERT(appl_regions, &(ctx->ac_regions), nregion);
nregion->ar_next = region;
return APPL_ERROR_NOERROR;
}
while (region->ar_next != NULL &&
region->ar_next->ar_priority < priority)
region = region->ar_next;
if (region->ar_next != NULL && region->ar_next->ar_priority == priority)
goto duplicate;
nregion->ar_next = region->ar_next;
region->ar_next = nregion;
return APPL_ERROR_NOERROR;
duplicate:
free(nregion);
log_info("%s: %s priority %"PRId8": Duplicate registration",
backend->ab_name, oidbuf, priority);
return APPL_ERROR_DUPLICATEREGISTRATION;
overlap:
regionbuf[0] = '\0';
for (i = 0; i < region->ar_oid.bo_n; i++) {
if (i != 0)
strlcat(regionbuf, ".", sizeof(regionbuf));
snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
region->ar_oid.bo_id[i]);
strlcat(regionbuf, subidbuf, sizeof(regionbuf));
}
log_info("%s: %s overlaps with %s: Request denied",
backend->ab_name, oidbuf, regionbuf);
return APPL_ERROR_REQUESTDENIED;
}
enum appl_error
appl_register(const char *ctxname, uint32_t timeout, uint8_t priority,
struct ber_oid *oid, int instance, int subtree, uint8_t range_subid,
uint32_t upper_bound, struct appl_backend *backend)
{
struct appl_context *ctx;
struct appl_region *region, search;
char oidbuf[1024], subidbuf[11];
enum appl_error error;
size_t i, bo_n;
uint32_t lower_bound;
bo_n = oid->bo_n;
if (range_subid != 0)
oid->bo_n = range_subid;
mib_oid2string(oid, oidbuf, sizeof(oidbuf), snmpd_env->sc_oidfmt);
if (range_subid != 0) {
oid->bo_n = bo_n;
i = range_subid + 1;
} else
i = oid->bo_n;
for (; i < oid->bo_n; i++) {
strlcat(oidbuf, ".", sizeof(oidbuf));
snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32, oid->bo_id[i]);
if (range_subid == i + 1) {
strlcat(oidbuf, "[", sizeof(oidbuf));
strlcat(oidbuf, subidbuf, sizeof(oidbuf));
strlcat(oidbuf, "-", sizeof(oidbuf));
snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
upper_bound);
strlcat(oidbuf, subidbuf, sizeof(oidbuf));
strlcat(oidbuf, "]", sizeof(oidbuf));
} else
strlcat(oidbuf, subidbuf, sizeof(oidbuf));
}
if (ctxname == NULL)
ctxname = "";
log_info("%s: Registering %s%s context(%s) priority(%"PRIu8") "
"timeout(%"PRIu32".%02us)", backend->ab_name, oidbuf,
instance ? "(instance)" : "", ctxname, priority,
timeout/100, timeout % 100);
if ((ctx = appl_context(ctxname, 0)) == NULL) {
if (errno == ENOMEM) {
log_warn("%s: Can't register %s: Processing error",
backend->ab_name, oidbuf);
return APPL_ERROR_PROCESSINGERROR;
}
log_info("%s: Can't register %s: Unsupported context \"%s\"",
backend->ab_name, oidbuf, ctxname);
return APPL_ERROR_UNSUPPORTEDCONTEXT;
}
if (timeout == 0)
fatalx("%s: Timeout can't be 0", __func__);
if (priority == 0) {
log_warnx("%s: Can't register %s: priority can't be 0",
backend->ab_name, oidbuf);
return APPL_ERROR_PARSEERROR;
}
if (range_subid == 0)
return appl_region(ctx, timeout, priority, oid, range_subid,
instance, subtree, backend);
range_subid--;
if (range_subid >= oid->bo_n) {
log_warnx("%s: Can't register %s: range_subid too large",
backend->ab_name, oidbuf);
return APPL_ERROR_PARSEERROR;
}
if (oid->bo_id[range_subid] > upper_bound) {
log_warnx("%s: Can't register %s: upper bound smaller than "
"range_subid", backend->ab_name, oidbuf);
return APPL_ERROR_PARSEERROR;
}
lower_bound = oid->bo_id[range_subid];
do {
if ((error = appl_region(ctx, timeout, priority, oid,
range_subid, instance, subtree,
backend)) != APPL_ERROR_NOERROR)
goto fail;
} while (oid->bo_id[range_subid]++ != upper_bound);
if ((error = appl_region(ctx, timeout, priority, oid, range_subid,
instance, subtree, backend)) != APPL_ERROR_NOERROR)
goto fail;
return APPL_ERROR_NOERROR;
fail:
search.ar_oid = *oid;
if (search.ar_oid.bo_id[range_subid] == lower_bound)
return error;
for (search.ar_oid.bo_id[range_subid]--;
search.ar_oid.bo_id[range_subid] != lower_bound;
search.ar_oid.bo_id[range_subid]--) {
region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
while (region->ar_priority != priority)
region = region->ar_next;
appl_region_free(ctx, region);
}
region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
while (region->ar_priority != priority)
region = region->ar_next;
appl_region_free(ctx, region);
return error;
}
enum appl_error
appl_unregister(const char *ctxname, uint8_t priority, struct ber_oid *oid,
uint8_t range_subid, uint32_t upper_bound, struct appl_backend *backend)
{
struct appl_context *ctx;
char oidbuf[1024], subidbuf[11];
enum appl_error error;
uint32_t lower_bound;
size_t i;
oidbuf[0] = '\0';
for (i = 0; i < oid->bo_n; i++) {
snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32, oid->bo_id[i]);
if (i != 0)
strlcat(oidbuf, ".", sizeof(oidbuf));
if (range_subid == i + 1) {
strlcat(oidbuf, "[", sizeof(oidbuf));
strlcat(oidbuf, subidbuf, sizeof(oidbuf));
strlcat(oidbuf, "-", sizeof(oidbuf));
snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
upper_bound);
strlcat(oidbuf, subidbuf, sizeof(oidbuf));
strlcat(oidbuf, "]", sizeof(oidbuf));
} else
strlcat(oidbuf, subidbuf, sizeof(oidbuf));
}
if (ctxname == NULL)
ctxname = "";
log_info("%s: Unregistering %s context(%s) priority(%"PRIu8")",
backend->ab_name, oidbuf,ctxname, priority);
if ((ctx = appl_context(ctxname, 0)) == NULL) {
if (errno == ENOMEM) {
log_warn("%s: Can't unregister %s: Processing error",
backend->ab_name, oidbuf);
return APPL_ERROR_PROCESSINGERROR;
}
log_info("%s: Can't unregister %s: Unsupported context \"%s\"",
backend->ab_name, oidbuf, ctxname);
return APPL_ERROR_UNSUPPORTEDCONTEXT;
}
if (priority == 0) {
log_warnx("%s: Can't unregister %s: priority can't be 0",
backend->ab_name, oidbuf);
return APPL_ERROR_PARSEERROR;
}
if (range_subid == 0)
return appl_region_unregister_match(ctx, priority, oid, oidbuf,
backend, 1);
range_subid--;
if (range_subid >= oid->bo_n) {
log_warnx("%s: Can't unregiser %s: range_subid too large",
backend->ab_name, oidbuf);
return APPL_ERROR_PARSEERROR;
}
if (oid->bo_id[range_subid] > upper_bound) {
log_warnx("%s: Can't unregister %s: upper bound smaller than "
"range_subid", backend->ab_name, oidbuf);
return APPL_ERROR_PARSEERROR;
}
lower_bound = oid->bo_id[range_subid];
do {
if ((error = appl_region_unregister_match(ctx, priority, oid,
oidbuf, backend, 0)) != APPL_ERROR_NOERROR)
return error;
} while (oid->bo_id[range_subid]++ != upper_bound);
oid->bo_id[range_subid] = lower_bound;
do {
(void)appl_region_unregister_match(ctx, priority, oid, oidbuf,
backend, 1);
} while (oid->bo_id[range_subid]++ != upper_bound);
return APPL_ERROR_NOERROR;
}
enum appl_error
appl_region_unregister_match(struct appl_context *ctx, uint8_t priority,
struct ber_oid *oid, char *oidbuf, struct appl_backend *backend, int dofree)
{
struct appl_region *region, search;
search.ar_oid = *oid;
region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
while (region != NULL && region->ar_priority < priority)
region = region->ar_next;
if (region == NULL || region->ar_priority != priority) {
log_warnx("%s: Can't unregister %s: region not found",
backend->ab_name, oidbuf);
return APPL_ERROR_UNKNOWNREGISTRATION;
}
if (region->ar_backend != backend) {
log_warnx("%s: Can't unregister %s: region not owned "
"by backend", backend->ab_name, oidbuf);
return APPL_ERROR_UNKNOWNREGISTRATION;
}
if (dofree)
appl_region_free(ctx, region);
return APPL_ERROR_NOERROR;
}
void
appl_region_free(struct appl_context *ctx, struct appl_region *region)
{
struct appl_region *pregion;
pregion = RB_FIND(appl_regions, &(ctx->ac_regions), region);
if (pregion == region) {
RB_REMOVE(appl_regions, &(ctx->ac_regions), region);
if (region->ar_next != NULL)
RB_INSERT(appl_regions, &(ctx->ac_regions),
region->ar_next);
} else {
while (pregion->ar_next != region)
pregion = pregion->ar_next;
pregion->ar_next = region->ar_next;
}
free(region);
}
void
appl_close(struct appl_backend *backend)
{
struct appl_context *ctx;
struct appl_agentcap *cap, *tcap;
struct appl_region *region, *tregion, *nregion;
struct appl_request_downstream *request, *trequest;
TAILQ_FOREACH(ctx, &contexts, ac_entries) {
TAILQ_FOREACH_SAFE(cap, &(ctx->ac_agentcaps), aa_entry, tcap) {
if (cap->aa_backend == backend)
appl_agentcap_free(cap);
}
RB_FOREACH_SAFE(region, appl_regions,
&(ctx->ac_regions), tregion) {
while (region != NULL) {
nregion = region->ar_next;
if (region->ar_backend == backend)
appl_region_free(ctx, region);
region = nregion;
}
}
}
RB_FOREACH_SAFE(request, appl_requests,
&(backend->ab_requests), trequest)
appl_request_downstream_free(request);
}
struct appl_region *
appl_region_find(struct appl_context *ctx,
const struct ber_oid *oid)
{
struct appl_region *region, search;
search.ar_oid = *oid;
while (search.ar_oid.bo_n > 0) {
region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
if (region != NULL)
return region;
search.ar_oid.bo_n--;
}
return NULL;
}
struct appl_region *
appl_region_next(struct appl_context *ctx, struct ber_oid *oid,
struct appl_region *cregion)
{
struct appl_region search, *nregion, *pregion;
int cmp;
search.ar_oid = *oid;
nregion = RB_NFIND(appl_regions, &(ctx->ac_regions), &search);
if (cregion == nregion)
nregion = RB_NEXT(appl_regions, &(ctx->ac_regions), nregion);
if (nregion == NULL) {
search.ar_oid = cregion->ar_oid;
search.ar_oid.bo_n--;
return appl_region_find(ctx, &(search.ar_oid));
}
cmp = appl_region_cmp(cregion, nregion);
if (cmp >= 0)
fatalx("%s: wrong OID order", __func__);
if (cmp == -2)
return nregion;
search.ar_oid = cregion->ar_oid;
ober_oid_nextsibling(&(search.ar_oid));
if (ober_oid_cmp(&(nregion->ar_oid), &(search.ar_oid)) == 0)
return nregion;
search.ar_oid = cregion->ar_oid;
search.ar_oid.bo_n--;
pregion = appl_region_find(ctx, &(search.ar_oid));
return pregion != NULL ? pregion : nregion;
}
void
appl_processpdu(struct snmp_message *statereference, const char *ctxname,
enum snmp_version pduversion, struct ber_element *pdu)
{
struct appl_context *ctx;
struct appl_request_upstream *ureq;
struct ber_element *varbind, *varbindlist;
long long nonrepeaters, maxrepetitions;
static uint32_t transactionid;
int32_t requestid;
size_t i, varbindlen = 0, repeaterlen;
(void) ober_scanf_elements(pdu, "{diie", &requestid, &nonrepeaters,
&maxrepetitions, &varbindlist);
if ((ctx = appl_context(ctxname, 0)) == NULL) {
snmp_target_mib.snmp_unknowncontexts++;
appl_report(statereference, requestid,
&OID(MIB_snmpUnknownContexts, 0));
return;
}
if ((ureq = malloc(sizeof(*ureq))) == NULL)
fatal("malloc");
ureq->aru_ctx = ctx;
ureq->aru_statereference = statereference;
ureq->aru_transactionid = transactionid++;
ureq->aru_requesttype = pdu->be_type;
ureq->aru_responsetype = SNMP_C_RESPONSE;
ureq->aru_requestid = requestid;
ureq->aru_error = APPL_ERROR_NOERROR;
ureq->aru_index = 0;
ureq->aru_nonrepeaters = nonrepeaters;
ureq->aru_maxrepetitions = maxrepetitions;
ureq->aru_varbindlen = 0;
ureq->aru_locked = 0;
ureq->aru_pduversion = pduversion;
varbind = varbindlist->be_sub;
for (; varbind != NULL; varbind = varbind->be_next)
varbindlen++;
repeaterlen = varbindlen - nonrepeaters;
if (pdu->be_type == SNMP_C_GETBULKREQ)
ureq->aru_varbindlen = nonrepeaters +
(repeaterlen * maxrepetitions);
else
ureq->aru_varbindlen = varbindlen;
if ((ureq->aru_vblist = calloc(ureq->aru_varbindlen,
sizeof(*ureq->aru_vblist))) == NULL)
fatal("malloc");
varbind = varbindlist->be_sub;
for (i = 0; i < ureq->aru_varbindlen; i++) {
ureq->aru_vblist[i].avi_request_upstream = ureq;
ureq->aru_vblist[i].avi_index = i + 1;
ureq->aru_vblist[i].avi_state = APPL_VBSTATE_NEW;
if (varbind == NULL) {
ureq->aru_vblist[i - repeaterlen].avi_sub =
&(ureq->aru_vblist[i]);
ureq->aru_vblist[i].avi_state = APPL_VBSTATE_MUSTFILL;
ureq->aru_vblist[i].avi_index =
ureq->aru_vblist[i - repeaterlen].avi_index;
continue;
}
ober_get_oid(varbind->be_sub,
&(ureq->aru_vblist[i].avi_varbind.av_oid));
ureq->aru_vblist[i].avi_origid =
ureq->aru_vblist[i].avi_varbind.av_oid;
if (i + 1 < varbindlen)
ureq->aru_vblist[i].avi_varbind.av_next =
&(ureq->aru_vblist[i + 1].avi_varbind);
else
ureq->aru_vblist[i].avi_varbind.av_next = NULL;
varbind = varbind->be_next;
}
appl_pdu_log(NULL, pdu->be_type, requestid, nonrepeaters,
maxrepetitions, &(ureq->aru_vblist[0].avi_varbind));
appl_request_upstream_resolve(ureq);
}
void
appl_request_upstream_free(struct appl_request_upstream *ureq)
{
size_t i;
struct appl_varbind_internal *vb;
if (ureq == NULL)
return;
ureq->aru_locked = 1;
for (i = 0; i < ureq->aru_varbindlen && ureq->aru_vblist != NULL; i++) {
vb = &(ureq->aru_vblist[i]);
ober_free_elements(vb->avi_varbind.av_value);
appl_request_downstream_free(vb->avi_request_downstream);
}
free(ureq->aru_vblist);
assert(ureq->aru_statereference == NULL);
free(ureq);
}
void
appl_request_downstream_free(struct appl_request_downstream *dreq)
{
struct appl_varbind_internal *vb;
if (dreq == NULL)
return;
RB_REMOVE(appl_requests, &(dreq->ard_backend->ab_requests), dreq);
evtimer_del(&(dreq->ard_timer));
for (vb = dreq->ard_vblist; vb != NULL; vb = vb->avi_next) {
vb->avi_request_downstream = NULL;
if (vb->avi_state == APPL_VBSTATE_PENDING)
vb->avi_state = APPL_VBSTATE_NEW;
}
appl_request_upstream_resolve(dreq->ard_request);
free(dreq);
}
void
appl_request_upstream_resolve(struct appl_request_upstream *ureq)
{
struct appl_varbind_internal *vb, *lvb, *tvb;
struct appl_request_downstream *dreq;
struct appl_region *region, *lregion;
struct timeval tv;
int done;
size_t i;
int32_t maxrepetitions;
int32_t timeout;
if (ureq->aru_locked)
return;
ureq->aru_locked = 1;
if (ureq->aru_requesttype == SNMP_C_SETREQ) {
ureq->aru_error = APPL_ERROR_NOTWRITABLE;
ureq->aru_index = 1;
appl_request_upstream_reply(ureq);
return;
}
next:
dreq = NULL;
lvb = NULL;
done = 1;
timeout = 0;
if (ureq->aru_error != APPL_ERROR_NOERROR) {
appl_request_upstream_reply(ureq);
return;
}
for (i = 0; i < ureq->aru_varbindlen; i++) {
vb = &(ureq->aru_vblist[i]);
switch (vb->avi_state) {
case APPL_VBSTATE_MUSTFILL:
case APPL_VBSTATE_PENDING:
done = 0;
continue;
case APPL_VBSTATE_DONE:
continue;
case APPL_VBSTATE_NEW:
break;
}
if (appl_varbind_backend(vb) == -1)
fatal("appl_varbind_backend");
if (vb->avi_state != APPL_VBSTATE_DONE)
done = 0;
}
for (i = 0; i < ureq->aru_varbindlen; i++) {
vb = &(ureq->aru_vblist[i]);
if (vb->avi_state != APPL_VBSTATE_NEW)
continue;
vb = &(ureq->aru_vblist[i]);
region = vb->avi_region;
lregion = lvb != NULL ? lvb->avi_region : NULL;
if (lvb != NULL && region->ar_backend != lregion->ar_backend)
continue;
vb->avi_varbind.av_next = NULL;
vb->avi_next = NULL;
tvb = vb;
for (maxrepetitions = 0; tvb != NULL; tvb = tvb->avi_sub)
maxrepetitions++;
if (dreq == NULL) {
if ((dreq = malloc(sizeof(*dreq))) == NULL)
fatal("malloc");
dreq->ard_request = ureq;
dreq->ard_vblist = vb;
dreq->ard_backend = vb->avi_region->ar_backend;
dreq->ard_retries = dreq->ard_backend->ab_retries;
dreq->ard_requesttype = ureq->aru_requesttype;
if (dreq->ard_requesttype == SNMP_C_GETBULKREQ &&
dreq->ard_backend->ab_fn->ab_getbulk == NULL)
dreq->ard_requesttype = SNMP_C_GETNEXTREQ;
if (maxrepetitions == 1) {
dreq->ard_maxrepetitions = 0;
dreq->ard_nonrepeaters = 1;
} else {
dreq->ard_maxrepetitions = maxrepetitions;
dreq->ard_nonrepeaters = 0;
}
do {
dreq->ard_requestid = arc4random();
} while (RB_INSERT(appl_requests,
&(dreq->ard_backend->ab_requests), dreq) != NULL);
lvb = vb;
} else if (maxrepetitions == 1) {
dreq->ard_nonrepeaters++;
vb->avi_varbind.av_next =
&(dreq->ard_vblist->avi_varbind);
vb->avi_next = dreq->ard_vblist;
dreq->ard_vblist = vb;
} else {
lvb->avi_varbind.av_next = &(vb->avi_varbind);
lvb->avi_next = vb;
if (dreq->ard_maxrepetitions > maxrepetitions ||
dreq->ard_maxrepetitions == 0)
dreq->ard_maxrepetitions = maxrepetitions;
lvb = vb;
}
vb->avi_request_downstream = dreq;
vb->avi_state = APPL_VBSTATE_PENDING;
if (region->ar_timeout > timeout)
timeout = region->ar_timeout;
}
if (dreq == NULL) {
ureq->aru_locked = 0;
if (done)
appl_request_upstream_reply(ureq);
return;
}
tv.tv_sec = timeout / 100;
tv.tv_usec = (timeout % 100) * 10000;
evtimer_set(&(dreq->ard_timer), appl_request_downstream_timeout, dreq);
evtimer_add(&(dreq->ard_timer), &tv);
appl_request_downstream_send(dreq);
goto next;
}
void
appl_request_downstream_send(struct appl_request_downstream *dreq)
{
appl_pdu_log(dreq->ard_backend, dreq->ard_requesttype,
dreq->ard_requestid, 0, 0, &(dreq->ard_vblist->avi_varbind));
if (dreq->ard_requesttype == SNMP_C_GETREQ) {
dreq->ard_backend->ab_fn->ab_get(dreq->ard_backend,
dreq->ard_request->aru_transactionid,
dreq->ard_requestid,
APPL_CONTEXT_NAME(dreq->ard_request->aru_ctx),
&(dreq->ard_vblist->avi_varbind));
} else if (dreq->ard_requesttype == SNMP_C_GETNEXTREQ) {
dreq->ard_backend->ab_fn->ab_getnext(dreq->ard_backend,
dreq->ard_request->aru_transactionid,
dreq->ard_requestid,
APPL_CONTEXT_NAME(dreq->ard_request->aru_ctx),
&(dreq->ard_vblist->avi_varbind));
}
}
void
appl_request_downstream_timeout(__unused int fd, __unused short event,
void *cookie)
{
struct appl_request_downstream *dreq = cookie;
log_info("%s: %"PRIu32" timed out%s",
dreq->ard_backend->ab_name, dreq->ard_requestid,
dreq->ard_retries > 0 ? ": retrying" : "");
if (dreq->ard_retries > 0) {
dreq->ard_retries--;
appl_request_downstream_send(dreq);
} else
appl_response(dreq->ard_backend, dreq->ard_requestid,
APPL_ERROR_GENERR, 1, &(dreq->ard_vblist->avi_varbind));
}
void
appl_request_upstream_reply(struct appl_request_upstream *ureq)
{
struct ber_element *varbindlist = NULL, *varbind = NULL, *value;
struct appl_varbind_internal *vb;
size_t i, repvarbinds, varbindlen;
ssize_t match = -1;
varbindlen = ureq->aru_varbindlen;
if (ureq->aru_pduversion == SNMP_V1) {
for (i = 0; i < varbindlen; i++) {
vb = &(ureq->aru_vblist[i]);
value = vb->avi_varbind.av_value;
if (value != NULL &&
value->be_class == BER_CLASS_CONTEXT)
appl_varbind_error(vb, APPL_ERROR_NOSUCHNAME);
}
switch (ureq->aru_error) {
case APPL_ERROR_WRONGVALUE:
case APPL_ERROR_WRONGENCODING:
case APPL_ERROR_WRONGTYPE:
case APPL_ERROR_WRONGLENGTH:
case APPL_ERROR_INCONSISTENTVALUE:
ureq->aru_error = APPL_ERROR_BADVALUE;
break;
case APPL_ERROR_NOACCESS:
case APPL_ERROR_NOTWRITABLE:
case APPL_ERROR_NOCREATION:
case APPL_ERROR_INCONSISTENTNAME:
case APPL_ERROR_AUTHORIZATIONERROR:
ureq->aru_error = APPL_ERROR_NOSUCHNAME;
break;
case APPL_ERROR_RESOURCEUNAVAILABLE:
case APPL_ERROR_COMMITFAILED:
case APPL_ERROR_UNDOFAILED:
ureq->aru_error = APPL_ERROR_GENERR;
break;
default:
break;
}
}
if (ureq->aru_error != APPL_ERROR_NOERROR) {
if (ureq->aru_requesttype == SNMP_C_GETBULKREQ)
varbindlen =
(ureq->aru_varbindlen - ureq->aru_nonrepeaters) /
ureq->aru_maxrepetitions;
for (i = 0; i < varbindlen; i++) {
vb = &(ureq->aru_vblist[i]);
vb->avi_varbind.av_oid = vb->avi_origid;
ober_free_elements(vb->avi_varbind.av_value);
vb->avi_varbind.av_value = ober_add_null(NULL);
}
} else if (ureq->aru_requesttype == SNMP_C_GETBULKREQ) {
repvarbinds = (ureq->aru_varbindlen - ureq->aru_nonrepeaters) /
ureq->aru_maxrepetitions;
for (i = ureq->aru_nonrepeaters;
i < ureq->aru_varbindlen - repvarbinds; i++) {
value = ureq->aru_vblist[i].avi_varbind.av_value;
if ((i - ureq->aru_nonrepeaters) % repvarbinds == 0 &&
value->be_class == BER_CLASS_CONTEXT &&
value->be_type == APPL_EXC_ENDOFMIBVIEW) {
if (match != -1)
break;
match = i;
}
if (value->be_class != BER_CLASS_CONTEXT ||
value->be_type != APPL_EXC_ENDOFMIBVIEW)
match = -1;
}
if (match != -1)
varbindlen = match + repvarbinds;
}
for (i = 0; i < varbindlen; i++) {
vb = &(ureq->aru_vblist[i]);
vb->avi_varbind.av_next =
&(ureq->aru_vblist[i + 1].avi_varbind);
value = vb->avi_varbind.av_value;
if (value->be_class == BER_CLASS_CONTEXT &&
value->be_type == APPL_EXC_ENDOFMIBVIEW)
vb->avi_varbind.av_oid = vb->avi_origid;
}
ureq->aru_vblist[i - 1].avi_varbind.av_next = NULL;
appl_pdu_log(NULL, ureq->aru_responsetype, ureq->aru_requestid,
ureq->aru_error, ureq->aru_index,
&(ureq->aru_vblist[0].avi_varbind));
for (i = 0; i < varbindlen; i++) {
varbind = ober_printf_elements(varbind, "{Oe}",
&(ureq->aru_vblist[i].avi_varbind.av_oid),
ureq->aru_vblist[i].avi_varbind.av_value);
ureq->aru_vblist[i].avi_varbind.av_value = NULL;
if (varbind == NULL)
fatal("ober_printf_elements");
if (varbindlist == NULL)
varbindlist = varbind;
}
snmpe_send(ureq->aru_statereference, ureq->aru_responsetype,
ureq->aru_requestid, ureq->aru_error, ureq->aru_index, varbindlist);
ureq->aru_statereference = NULL;
appl_request_upstream_free(ureq);
}
void
appl_response(struct appl_backend *backend, int32_t requestid,
enum appl_error error, int16_t index, struct appl_varbind *vblist)
{
struct appl_request_downstream *dreq, search;
struct appl_request_upstream *ureq = NULL;
const char *errstr;
char oidbuf[1024];
struct appl_varbind *vb;
struct appl_varbind_internal *origvb = NULL;
int invalid = 0;
int next = 0, eomv;
int32_t i;
appl_pdu_log(backend, SNMP_C_RESPONSE, requestid, error, index, vblist);
search.ard_requestid = requestid;
dreq = RB_FIND(appl_requests, &(backend->ab_requests), &search);
if (dreq == NULL) {
log_debug("%s: %"PRIu32" not outstanding",
backend->ab_name, requestid);
} else {
ureq = dreq->ard_request;
next = ureq->aru_requesttype == SNMP_C_GETNEXTREQ ||
ureq->aru_requesttype == SNMP_C_GETBULKREQ;
origvb = dreq->ard_vblist;
if (!appl_error_valid(error, dreq->ard_requesttype)) {
log_warnx("%s: %"PRIu32" Invalid error",
backend->ab_name, requestid);
invalid = 1;
}
}
vb = vblist;
for (i = 1; vb != NULL; vb = vb->av_next, i++) {
if (!appl_varbind_valid(vb, origvb, next,
error != APPL_ERROR_NOERROR, backend->ab_range, &errstr)) {
mib_oid2string(&(vb->av_oid), oidbuf, sizeof(oidbuf),
snmpd_env->sc_oidfmt);
log_warnx("%s: %"PRIu32" %s: %s",
backend->ab_name, requestid, oidbuf, errstr);
invalid = 1;
}
if (origvb != NULL) {
if (error != APPL_ERROR_NOERROR && i == index)
appl_varbind_error(origvb, error);
origvb->avi_state = APPL_VBSTATE_DONE;
origvb->avi_varbind.av_oid = vb->av_oid;
eomv = vb->av_value != NULL &&
vb->av_value->be_class == BER_CLASS_CONTEXT &&
vb->av_value->be_type == APPL_EXC_ENDOFMIBVIEW;
eomv |= !backend->ab_range && next &&
ober_oid_cmp(&(vb->av_oid),
&(origvb->avi_varbind.av_oid_end)) >= 0;
if (ureq->aru_pduversion == SNMP_V1 &&
vb->av_value != NULL &&
vb->av_value->be_class == BER_CLASS_APPLICATION &&
vb->av_value->be_type == SNMP_COUNTER64) {
if (next)
eomv = 1;
else
appl_varbind_error(origvb,
APPL_ERROR_NOSUCHNAME);
}
if (eomv) {
ober_free_elements(vb->av_value);
origvb->avi_varbind.av_oid =
origvb->avi_varbind.av_oid_end;
origvb->avi_varbind.av_include = 1;
vb->av_value = NULL;
origvb->avi_state = APPL_VBSTATE_NEW;
}
origvb->avi_varbind.av_value = vb->av_value;
if (origvb->avi_varbind.av_next == NULL &&
vb->av_next != NULL) {
log_warnx("%s: Request %"PRIu32" returned more "
"varbinds then requested",
backend->ab_name, requestid);
invalid = 1;
}
if (origvb->avi_sub != NULL &&
origvb->avi_state == APPL_VBSTATE_DONE) {
origvb->avi_sub->avi_varbind.av_oid =
origvb->avi_varbind.av_oid;
origvb->avi_sub->avi_origid =
origvb->avi_varbind.av_oid;
origvb->avi_sub->avi_state = APPL_VBSTATE_NEW;
}
origvb = origvb->avi_next;
} else {
ober_free_elements(vb->av_value);
vb->av_value = NULL;
}
}
if (error != APPL_ERROR_NOERROR && (index <= 0 || index >= i)) {
log_warnx("Invalid error index");
invalid = 1;
}
#if PEDANTIC
if (error == APPL_ERROR_NOERROR && index != 0) {
log_warnx("error index with no error");
invalid = 1;
}
#endif
if (vb == NULL && origvb != NULL) {
log_warnx("%s: Request %"PRIu32" returned less varbinds then "
"requested", backend->ab_name, requestid);
invalid = 1;
}
if (dreq != NULL) {
if (invalid)
appl_varbind_error(dreq->ard_vblist, APPL_ERROR_GENERR);
appl_request_downstream_free(dreq);
}
if (invalid && backend->ab_fn->ab_close != NULL) {
log_warnx("%s: Closing: Too many parse errors",
backend->ab_name);
backend->ab_fn->ab_close(backend, APPL_CLOSE_REASONPARSEERROR);
}
}
int
appl_varbind_valid(struct appl_varbind *varbind,
struct appl_varbind_internal *request, int next, int null, int range,
const char **errstr)
{
int cmp;
int eomv = 0;
long long intval;
void *buf;
size_t len;
struct ber ber;
struct ber_element *elm;
if (null)
next = 0;
if (varbind->av_value == NULL) {
if (!null) {
*errstr = "missing value";
return 0;
}
return 1;
}
if (null) {
if (varbind->av_value->be_class != BER_CLASS_UNIVERSAL ||
varbind->av_value->be_type != BER_TYPE_NULL) {
*errstr = "expecting null value";
return 0;
}
} else {
if (varbind->av_value->be_class == BER_CLASS_UNIVERSAL &&
varbind->av_value->be_type == BER_TYPE_NULL) {
*errstr = "unexpected null value";
return 0;
}
}
if (varbind->av_value->be_class == BER_CLASS_UNIVERSAL) {
switch (varbind->av_value->be_type) {
case BER_TYPE_NULL:
break;
case BER_TYPE_INTEGER:
(void)ober_get_integer(varbind->av_value, &intval);
if (intval < SMI_INTEGER_MIN) {
*errstr = "INTEGER value too small";
return 0;
}
if (intval > SMI_INTEGER_MAX) {
*errstr = "INTEGER value too large";
return 0;
}
break;
case BER_TYPE_OCTETSTRING:
(void)ober_get_nstring(varbind->av_value, NULL, &len);
if (len > SMI_OCTETSTRING_MAX) {
*errstr = "OCTET STRING too long";
return 0;
}
break;
case BER_TYPE_OBJECT:
break;
default:
*errstr = "invalid value encoding";
return 0;
}
} else if (varbind->av_value->be_class == BER_CLASS_APPLICATION) {
switch (varbind->av_value->be_type) {
case SNMP_T_IPADDR:
(void)ober_get_nstring(varbind->av_value, NULL, &len);
if (len != SMI_IPADDRESS_MAX) {
*errstr = "invalid IpAddress size";
return 0;
}
break;
case SNMP_T_COUNTER32:
(void)ober_get_integer(varbind->av_value, &intval);
if (intval < SMI_COUNTER32_MIN) {
*errstr = "Counter32 value too small";
return 0;
}
if (intval > SMI_COUNTER32_MAX) {
*errstr = "Counter32 value too large";
return 0;
}
break;
case SNMP_T_GAUGE32:
(void)ober_get_integer(varbind->av_value, &intval);
if (intval < SMI_GAUGE32_MIN) {
*errstr = "Gauge32 value too small";
return 0;
}
if (intval > SMI_GAUGE32_MAX) {
*errstr = "Gauge32 value too large";
return 0;
}
break;
case SNMP_T_TIMETICKS:
(void)ober_get_integer(varbind->av_value, &intval);
if (intval < SMI_TIMETICKS_MIN) {
*errstr = "TimeTicks value too small";
return 0;
}
if (intval > SMI_TIMETICKS_MAX) {
*errstr = "TimeTicks value too large";
return 0;
}
break;
case SNMP_T_OPAQUE:
(void)ober_get_nstring(varbind->av_value, &buf, &len);
memset(&ber, 0, sizeof(ber));
ober_set_application(&ber, appl_ber_any);
ober_set_readbuf(&ber, buf, len);
elm = ober_read_elements(&ber, NULL);
if (elm == NULL || ober_calc_len(elm) != len) {
ober_free_elements(elm);
*errstr = "Opaque not valid ber encoded value";
return 0;
}
ober_free_elements(elm);
break;
case SNMP_T_COUNTER64:
break;
default:
*errstr = "invalid value encoding";
return 0;
}
} else if (varbind->av_value->be_class == BER_CLASS_CONTEXT) {
switch (varbind->av_value->be_type) {
case APPL_EXC_NOSUCHOBJECT:
if (next && request != NULL) {
*errstr = "Unexpected noSuchObject";
return 0;
}
break;
case APPL_EXC_NOSUCHINSTANCE:
if (next && request != NULL) {
*errstr = "Unexpected noSuchInstance";
return 0;
}
break;
case APPL_EXC_ENDOFMIBVIEW:
if (!next && request != NULL) {
*errstr = "Unexpected endOfMibView";
return 0;
}
eomv = 1;
break;
default:
*errstr = "invalid value encoding";
return 0;
}
} else {
*errstr = "invalid value encoding";
return 0;
}
if (request == NULL)
return 1;
cmp = ober_oid_cmp(&(request->avi_varbind.av_oid), &(varbind->av_oid));
if (next) {
if (request->avi_region->ar_instance &&
ober_oid_cmp(&(request->avi_region->ar_oid),
&(varbind->av_oid)) != 0) {
*errstr = "oid below instance";
return 0;
}
if (!eomv) {
if (request->avi_varbind.av_include) {
if (cmp > 0) {
*errstr = "oid not incrementing";
return 0;
}
} else {
if (cmp >= 0) {
*errstr = "oid not incrementing";
return 0;
}
}
if (range && ober_oid_cmp(&(varbind->av_oid),
&(request->avi_varbind.av_oid_end)) >= 0) {
*errstr = "end oid not honoured";
return 0;
}
}
} else {
if (cmp != 0) {
*errstr = "oids not equal";
return 0;
}
}
return 1;
}
unsigned int
appl_ber_any(struct ber_element *elm)
{
return BER_TYPE_OCTETSTRING;
}
int
appl_error_valid(enum appl_error error, enum snmp_pdutype type)
{
switch (error) {
case APPL_ERROR_NOERROR:
case APPL_ERROR_TOOBIG:
case APPL_ERROR_NOSUCHNAME:
case APPL_ERROR_GENERR:
return 1;
case APPL_ERROR_BADVALUE:
case APPL_ERROR_READONLY:
case APPL_ERROR_NOACCESS:
case APPL_ERROR_WRONGTYPE:
case APPL_ERROR_WRONGLENGTH:
case APPL_ERROR_WRONGENCODING:
case APPL_ERROR_WRONGVALUE:
case APPL_ERROR_NOCREATION:
case APPL_ERROR_INCONSISTENTVALUE:
case APPL_ERROR_RESOURCEUNAVAILABLE:
case APPL_ERROR_COMMITFAILED:
case APPL_ERROR_UNDOFAILED:
case APPL_ERROR_NOTWRITABLE:
case APPL_ERROR_INCONSISTENTNAME:
return type == SNMP_C_SETREQ;
case APPL_ERROR_AUTHORIZATIONERROR:
return type == SNMP_C_GETREQ || type == SNMP_C_SETREQ;
default:
return 0;
}
}
int
appl_varbind_backend(struct appl_varbind_internal *ivb)
{
struct appl_request_upstream *ureq = ivb->avi_request_upstream;
struct appl_region search, *region, *pregion;
struct appl_varbind *vb = &(ivb->avi_varbind);
struct ber_oid oid, nextsibling;
int next, cmp;
next = ureq->aru_requesttype == SNMP_C_GETNEXTREQ ||
ureq->aru_requesttype == SNMP_C_GETBULKREQ;
region = appl_region_find(ureq->aru_ctx, &(vb->av_oid));
if (region == NULL) {
if (!next) {
vb->av_value = appl_exception(APPL_EXC_NOSUCHOBJECT);
ivb->avi_state = APPL_VBSTATE_DONE;
if (vb->av_value == NULL)
return -1;
return 0;
}
search.ar_oid = vb->av_oid;
region = RB_NFIND(appl_regions,
&(ureq->aru_ctx->ac_regions), &search);
if (region == NULL)
goto eomv;
vb->av_oid = region->ar_oid;
vb->av_include = 1;
}
cmp = ober_oid_cmp(&(region->ar_oid), &(vb->av_oid));
if (cmp == -2) {
if (region->ar_instance) {
if (!next) {
vb->av_value =
appl_exception(APPL_EXC_NOSUCHINSTANCE);
ivb->avi_state = APPL_VBSTATE_DONE;
if (vb->av_value == NULL)
return -1;
return 0;
}
vb->av_oid = region->ar_oid;
ober_oid_nextsibling(&(vb->av_oid));
vb->av_include = 1;
return appl_varbind_backend(ivb);
}
} else if (cmp == 0) {
if (region->ar_instance && next && !vb->av_include) {
vb->av_oid = region->ar_oid;
ober_oid_nextsibling(&(vb->av_oid));
vb->av_include = 1;
return appl_varbind_backend(ivb);
}
}
ivb->avi_region = region;
if (next) {
oid = vb->av_oid;
do {
pregion = region;
region = appl_region_next(ureq->aru_ctx, &oid, pregion);
if (region == NULL) {
oid = pregion->ar_oid;
ober_oid_nextsibling(&oid);
break;
}
cmp = ober_oid_cmp(&(region->ar_oid), &oid);
if (cmp == 2)
oid = region->ar_oid;
else if (cmp == 1) {
nextsibling = pregion->ar_oid;
ober_oid_nextsibling(&nextsibling);
if (ober_oid_cmp(&(region->ar_oid),
&nextsibling) != 0) {
oid = pregion->ar_oid;
ober_oid_nextsibling(&oid);
break;
}
oid = region->ar_oid;
} else if (cmp == -2) {
oid = pregion->ar_oid;
ober_oid_nextsibling(&oid);
} else
fatalx("We can't stop/move back on getnext");
} while (region->ar_backend == pregion->ar_backend);
vb->av_oid_end = oid;
}
return 0;
eomv:
do {
ivb->avi_varbind.av_value =
appl_exception(APPL_EXC_ENDOFMIBVIEW);
ivb->avi_state = APPL_VBSTATE_DONE;
if (ivb->avi_varbind.av_value == NULL)
return -1;
if (ivb->avi_sub != NULL) {
ivb->avi_sub->avi_varbind.av_oid =
ivb->avi_varbind.av_oid;
ivb->avi_sub->avi_origid = ivb->avi_origid;
}
ivb = ivb->avi_sub;
} while (ivb != NULL);
return 0;
}
void
appl_varbind_error(struct appl_varbind_internal *avi, enum appl_error error)
{
struct appl_request_upstream *ureq = avi->avi_request_upstream;
if (ureq->aru_error == APPL_ERROR_GENERR)
return;
if (ureq->aru_error != APPL_ERROR_NOERROR && error != APPL_ERROR_GENERR)
return;
ureq->aru_error = error;
ureq->aru_index = avi->avi_index;
}
void
appl_report(struct snmp_message *statereference, int32_t requestid,
struct ber_oid *oid)
{
struct appl_request_upstream *ureq;
if ((ureq = calloc(1, sizeof(*ureq))) == NULL)
fatal("malloc");
ureq->aru_ctx = appl_context(NULL, 0);
ureq->aru_statereference = statereference;
ureq->aru_requesttype = SNMP_C_GETREQ;
ureq->aru_responsetype = SNMP_C_REPORT;
ureq->aru_requestid = requestid;
ureq->aru_transactionid = 0;
ureq->aru_nonrepeaters = 0;
ureq->aru_maxrepetitions = 0;
if ((ureq->aru_vblist = calloc(1, sizeof(*ureq->aru_vblist))) == NULL)
fatal("malloc");
ureq->aru_varbindlen = 1;
ureq->aru_error = APPL_ERROR_NOERROR;
ureq->aru_index = 0;
ureq->aru_locked = 0;
ureq->aru_pduversion = SNMP_V3;
ureq->aru_vblist[0].avi_state = APPL_VBSTATE_NEW;
ureq->aru_vblist[0].avi_varbind.av_oid = *oid;
ureq->aru_vblist[0].avi_varbind.av_value = NULL;
ureq->aru_vblist[0].avi_varbind.av_next = NULL;
ureq->aru_vblist[0].avi_origid = *oid;
ureq->aru_vblist[0].avi_index = 1;
ureq->aru_vblist[0].avi_request_upstream = ureq;
ureq->aru_vblist[0].avi_request_downstream = NULL;
ureq->aru_vblist[0].avi_next = NULL;
ureq->aru_vblist[0].avi_sub = NULL;
appl_request_upstream_resolve(ureq);
}
struct ber_element *
appl_exception(enum appl_exception type)
{
struct ber_element *value;
if ((value = ober_add_null(NULL)) == NULL) {
log_warn("malloc");
return NULL;
}
ober_set_header(value, BER_CLASS_CONTEXT, type);
return value;
}
void
appl_pdu_log(struct appl_backend *backend, enum snmp_pdutype pdutype,
int32_t requestid, uint16_t error, uint16_t index,
struct appl_varbind *vblist)
{
struct appl_varbind *vb;
char buf[1024], oidbuf[1024], *str;
int next;
if (log_getverbose() < 2)
return;
next = (pdutype == SNMP_C_GETNEXTREQ || pdutype == SNMP_C_GETBULKREQ);
buf[0] = '\0';
for (vb = vblist; vb != NULL; vb = vb->av_next) {
strlcat(buf, "{", sizeof(buf));
strlcat(buf, mib_oid2string(&(vb->av_oid), oidbuf,
sizeof(oidbuf), snmpd_env->sc_oidfmt), sizeof(buf));
if (next) {
if (vb->av_include)
strlcat(buf, "(incl)", sizeof(buf));
if (vb->av_oid_end.bo_n > 0) {
strlcat(buf, "-", sizeof(buf));
strlcat(buf, mib_oid2string(&(vb->av_oid_end),
oidbuf, sizeof(oidbuf),
snmpd_env->sc_oidfmt), sizeof(buf));
}
}
strlcat(buf, ":", sizeof(buf));
if (vb->av_value != NULL) {
str = smi_print_element(vb->av_value);
strlcat(buf, str == NULL ? "???" : str, sizeof(buf));
free(str);
} else
strlcat(buf, "null", sizeof(buf));
strlcat(buf, "}", sizeof(buf));
}
log_debug("%s%s%s{%"PRId32", %"PRIu16", %"PRIu16", {%s}}",
backend != NULL ? backend->ab_name : "",
backend != NULL ? ": " : "",
snmpe_pdutype2string(pdutype), requestid, error, index, buf);
}
void
ober_oid_nextsibling(struct ber_oid *oid)
{
while (oid->bo_n > 0) {
oid->bo_id[oid->bo_n - 1]++;
if (oid->bo_id[oid->bo_n - 1] != 0)
return;
oid->bo_n--;
}
}
int
appl_region_cmp(struct appl_region *r1, struct appl_region *r2)
{
return ober_oid_cmp(&(r1->ar_oid), &(r2->ar_oid));
}
int
appl_request_cmp(struct appl_request_downstream *r1,
struct appl_request_downstream *r2)
{
return r1->ard_requestid < r2->ard_requestid ? -1 :
r1->ard_requestid > r2->ard_requestid;
}
RB_GENERATE_STATIC(appl_regions, appl_region, ar_entry, appl_region_cmp);
RB_GENERATE_STATIC(appl_requests, appl_request_downstream, ard_entry,
appl_request_cmp);