summaryrefslogtreecommitdiffstats
path: root/contrib/bsnmp/lib/snmpagent.c
diff options
context:
space:
mode:
authorharti <harti@FreeBSD.org>2003-11-10 08:53:38 +0000
committerharti <harti@FreeBSD.org>2003-11-10 08:53:38 +0000
commitea9d8683bc24e904e026c05abd5768f41c3b551e (patch)
tree150e45ef74a56ce93475bd8e0436d6da856d4a18 /contrib/bsnmp/lib/snmpagent.c
downloadFreeBSD-src-ea9d8683bc24e904e026c05abd5768f41c3b551e.zip
FreeBSD-src-ea9d8683bc24e904e026c05abd5768f41c3b551e.tar.gz
Virgin import of bsnmp 1.4
Diffstat (limited to 'contrib/bsnmp/lib/snmpagent.c')
-rw-r--r--contrib/bsnmp/lib/snmpagent.c1031
1 files changed, 1031 insertions, 0 deletions
diff --git a/contrib/bsnmp/lib/snmpagent.c b/contrib/bsnmp/lib/snmpagent.c
new file mode 100644
index 0000000..19ac661
--- /dev/null
+++ b/contrib/bsnmp/lib/snmpagent.c
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * All rights reserved.
+ *
+ * Author: Harti Brandt <harti@freebsd.org>
+ *
+ * Redistribution of this software and documentation 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 or documentation 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. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
+ * AND ITS CONTRIBUTORS ``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
+ * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS 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.
+ *
+ * $Begemot: bsnmp/lib/snmpagent.c,v 1.14 2003/01/30 11:23:00 hbb Exp $
+ *
+ * SNMP Agent functions
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "asn1.h"
+#include "snmp.h"
+#include "snmppriv.h"
+#include "snmpagent.h"
+
+static void snmp_debug_func(const char *fmt, ...);
+
+void (*snmp_debug)(const char *fmt, ...) = snmp_debug_func;
+
+struct snmp_node *tree;
+u_int tree_size;
+
+/*
+ * Structure to hold dependencies during SET processing
+ * The last two members of this structure must be the
+ * dependency visible by the user and the user data.
+ */
+struct depend {
+ TAILQ_ENTRY(depend) link;
+ size_t len; /* size of data part */
+ snmp_depop_t func;
+ struct snmp_dependency dep;
+ u_char data[];
+};
+TAILQ_HEAD(depend_list, depend);
+
+/*
+ * Structure to hold atfinish functions during SET processing.
+ */
+struct finish {
+ STAILQ_ENTRY(finish) link;
+ snmp_set_finish_t func;
+ void *arg;
+};
+STAILQ_HEAD(finish_list, finish);
+
+/*
+ * Set context
+ */
+struct context {
+ struct snmp_context ctx;
+ struct depend_list dlist;
+ struct finish_list flist;
+ const struct snmp_node *node[SNMP_MAX_BINDINGS];
+ struct snmp_scratch scratch[SNMP_MAX_BINDINGS];
+ struct depend *depend;
+};
+
+#define TR(W) (snmp_trace & SNMP_TRACE_##W)
+u_int snmp_trace = 0;
+
+static char oidbuf[ASN_OIDSTRLEN];
+
+/*
+ * Allocate a context
+ */
+struct snmp_context *
+snmp_init_context(void)
+{
+ struct context *context;
+
+ if ((context = malloc(sizeof(*context))) == NULL)
+ return (NULL);
+
+ memset(context, 0, sizeof(*context));
+ TAILQ_INIT(&context->dlist);
+ STAILQ_INIT(&context->flist);
+
+ return (&context->ctx);
+}
+
+/*
+ * Find a variable for SET/GET and the first GETBULK pass.
+ * Return the node pointer. If the search fails, set the errp to
+ * the correct SNMPv2 GET exception code.
+ */
+static struct snmp_node *
+find_node(const struct snmp_value *value, enum snmp_syntax *errp)
+{
+ struct snmp_node *tp;
+
+ if (TR(FIND))
+ snmp_debug("find: searching %s",
+ asn_oid2str_r(&value->var, oidbuf));
+
+ /*
+ * If we have an exact match (the entry in the table is a
+ * sub-oid from the variable) we have found what we are for.
+ * If the table oid is higher than the variable, there is no match.
+ */
+ for (tp = tree; tp < tree + tree_size; tp++) {
+ if (asn_is_suboid(&tp->oid, &value->var))
+ goto found;
+ if (asn_compare_oid(&tp->oid, &value->var) >= 0)
+ break;
+ }
+
+ if (TR(FIND))
+ snmp_debug("find: no match");
+ *errp = SNMP_SYNTAX_NOSUCHOBJECT;
+ return (NULL);
+
+ found:
+ /* leafs must have a 0 instance identifier */
+ if (tp->type == SNMP_NODE_LEAF &&
+ (value->var.len != tp->oid.len + 1 ||
+ value->var.subs[tp->oid.len] != 0)) {
+ if (TR(FIND))
+ snmp_debug("find: bad leaf index");
+ *errp = SNMP_SYNTAX_NOSUCHINSTANCE;
+ return (NULL);
+ }
+ if (TR(FIND))
+ snmp_debug("find: found %s",
+ asn_oid2str_r(&value->var, oidbuf));
+ return (tp);
+}
+
+static struct snmp_node *
+find_subnode(const struct snmp_value *value)
+{
+ struct snmp_node *tp;
+
+ for (tp = tree; tp < tree + tree_size; tp++) {
+ if (asn_is_suboid(&value->var, &tp->oid))
+ return (tp);
+ }
+ return (NULL);
+}
+
+/*
+ * Execute a GET operation. The tree is rooted at the global 'root'.
+ * Build the response PDU on the fly. If the return code is SNMP_RET_ERR
+ * the pdu error status and index will be set.
+ */
+enum snmp_ret
+snmp_get(struct snmp_pdu *pdu, struct asn_buf *resp_b,
+ struct snmp_pdu *resp, void *data)
+{
+ int ret;
+ u_int i;
+ struct snmp_node *tp;
+ enum snmp_syntax except;
+ struct context context;
+ enum asn_err err;
+
+ memset(&context, 0, sizeof(context));
+ context.ctx.data = data;
+
+ memset(resp, 0, sizeof(*resp));
+ strcpy(resp->community, pdu->community);
+ resp->version = pdu->version;
+ resp->type = SNMP_PDU_RESPONSE;
+ resp->request_id = pdu->request_id;
+ resp->version = pdu->version;
+
+ if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK)
+ /* cannot even encode header - very bad */
+ return (SNMP_RET_IGN);
+
+ for (i = 0; i < pdu->nbindings; i++) {
+ resp->bindings[i].var = pdu->bindings[i].var;
+ if ((tp = find_node(&pdu->bindings[i], &except)) == NULL) {
+ if (pdu->version == SNMP_V1) {
+ if (TR(GET))
+ snmp_debug("get: nosuchname");
+ pdu->error_status = SNMP_ERR_NOSUCHNAME;
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ if (TR(GET))
+ snmp_debug("get: exception %u", except);
+ resp->bindings[i].syntax = except;
+
+ } else {
+ /* call the action to fetch the value. */
+ resp->bindings[i].syntax = tp->syntax;
+ ret = (*tp->op)(&context.ctx, &resp->bindings[i],
+ tp->oid.len, tp->index, SNMP_OP_GET);
+ if (TR(GET))
+ snmp_debug("get: action returns %d", ret);
+
+ if (ret == SNMP_ERR_NOSUCHNAME) {
+ if (pdu->version == SNMP_V1) {
+ pdu->error_status = SNMP_ERR_NOSUCHNAME;
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ if (TR(GET))
+ snmp_debug("get: exception noSuchInstance");
+ resp->bindings[i].syntax = SNMP_SYNTAX_NOSUCHINSTANCE;
+
+ } else if (ret != SNMP_ERR_NOERROR) {
+ pdu->error_status = SNMP_ERR_GENERR;
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ }
+ resp->nbindings++;
+
+ err = snmp_binding_encode(resp_b, &resp->bindings[i]);
+
+ if (err == ASN_ERR_EOBUF) {
+ pdu->error_status = SNMP_ERR_TOOBIG;
+ pdu->error_index = 0;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ if (err != ASN_ERR_OK) {
+ if (TR(GET))
+ snmp_debug("get: binding encoding: %u", err);
+ pdu->error_status = SNMP_ERR_GENERR;
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ }
+
+ return (snmp_fix_encoding(resp_b, resp));
+}
+
+static struct snmp_node *
+next_node(const struct snmp_value *value, int *pnext)
+{
+ struct snmp_node *tp;
+
+ if (TR(FIND))
+ snmp_debug("next: searching %s",
+ asn_oid2str_r(&value->var, oidbuf));
+
+ *pnext = 0;
+ for (tp = tree; tp < tree + tree_size; tp++) {
+ if (asn_is_suboid(&tp->oid, &value->var)) {
+ /* the tree OID is a sub-oid of the requested OID. */
+ if (tp->type == SNMP_NODE_LEAF) {
+ if (tp->oid.len == value->var.len) {
+ /* request for scalar type */
+ if (TR(FIND))
+ snmp_debug("next: found scalar %s",
+ asn_oid2str_r(&tp->oid, oidbuf));
+ return (tp);
+ }
+ /* try next */
+ } else {
+ if (TR(FIND))
+ snmp_debug("next: found column %s",
+ asn_oid2str_r(&tp->oid, oidbuf));
+ return (tp);
+ }
+ } else if (asn_is_suboid(&value->var, &tp->oid) ||
+ asn_compare_oid(&tp->oid, &value->var) >= 0) {
+ if (TR(FIND))
+ snmp_debug("next: found %s",
+ asn_oid2str_r(&tp->oid, oidbuf));
+ *pnext = 1;
+ return (tp);
+ }
+ }
+
+ if (TR(FIND))
+ snmp_debug("next: failed");
+
+ return (NULL);
+}
+
+static enum snmp_ret
+do_getnext(struct context *context, const struct snmp_value *inb,
+ struct snmp_value *outb, struct snmp_pdu *pdu)
+{
+ const struct snmp_node *tp;
+ int ret, next;
+
+ if ((tp = next_node(inb, &next)) == NULL)
+ goto eofMib;
+
+ /* retain old variable if we are doing a GETNEXT on an exact
+ * matched leaf only */
+ if (tp->type == SNMP_NODE_LEAF || next)
+ outb->var = tp->oid;
+ else
+ outb->var = inb->var;
+
+ for (;;) {
+ outb->syntax = tp->syntax;
+ if (tp->type == SNMP_NODE_LEAF) {
+ /* make a GET operation */
+ outb->var.subs[outb->var.len++] = 0;
+ ret = (*tp->op)(&context->ctx, outb, tp->oid.len,
+ tp->index, SNMP_OP_GET);
+ } else {
+ /* make a GETNEXT */
+ ret = (*tp->op)(&context->ctx, outb, tp->oid.len,
+ tp->index, SNMP_OP_GETNEXT);
+ }
+ if (ret != SNMP_ERR_NOSUCHNAME) {
+ /* got something */
+ if (ret != SNMP_ERR_NOERROR && TR(GETNEXT))
+ snmp_debug("getnext: %s returns %u",
+ asn_oid2str(&outb->var), ret);
+ break;
+ }
+
+ /* object has no data - try next */
+ if (++tp == tree + tree_size)
+ break;
+ outb->var = tp->oid;
+ }
+
+ if (ret == SNMP_ERR_NOSUCHNAME) {
+ eofMib:
+ outb->var = inb->var;
+ if (pdu->version == SNMP_V1) {
+ pdu->error_status = SNMP_ERR_NOSUCHNAME;
+ return (SNMP_RET_ERR);
+ }
+ outb->syntax = SNMP_SYNTAX_ENDOFMIBVIEW;
+
+ } else if (ret != SNMP_ERR_NOERROR) {
+ pdu->error_status = SNMP_ERR_GENERR;
+ return (SNMP_RET_ERR);
+ }
+ return (SNMP_RET_OK);
+}
+
+
+/*
+ * Execute a GETNEXT operation. The tree is rooted at the global 'root'.
+ * Build the response PDU on the fly. The return is:
+ */
+enum snmp_ret
+snmp_getnext(struct snmp_pdu *pdu, struct asn_buf *resp_b,
+ struct snmp_pdu *resp, void *data)
+{
+ struct context context;
+ u_int i;
+ enum asn_err err;
+ enum snmp_ret result;
+
+ memset(&context, 0, sizeof(context));
+ context.ctx.data = data;
+
+ memset(resp, 0, sizeof(*resp));
+ strcpy(resp->community, pdu->community);
+ resp->type = SNMP_PDU_RESPONSE;
+ resp->request_id = pdu->request_id;
+ resp->version = pdu->version;
+
+ if (snmp_pdu_encode_header(resp_b, resp))
+ return (SNMP_RET_IGN);
+
+ for (i = 0; i < pdu->nbindings; i++) {
+ result = do_getnext(&context, &pdu->bindings[i],
+ &resp->bindings[i], pdu);
+
+ if (result != SNMP_RET_OK) {
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (result);
+ }
+
+ resp->nbindings++;
+
+ err = snmp_binding_encode(resp_b, &resp->bindings[i]);
+
+ if (err == ASN_ERR_EOBUF) {
+ pdu->error_status = SNMP_ERR_TOOBIG;
+ pdu->error_index = 0;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ if (err != ASN_ERR_OK) {
+ if (TR(GET))
+ snmp_debug("getnext: binding encoding: %u", err);
+ pdu->error_status = SNMP_ERR_GENERR;
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ }
+ return (snmp_fix_encoding(resp_b, resp));
+}
+
+enum snmp_ret
+snmp_getbulk(struct snmp_pdu *pdu, struct asn_buf *resp_b,
+ struct snmp_pdu *resp, void *data)
+{
+ struct context context;
+ u_int i;
+ int cnt;
+ u_int non_rep;
+ int eomib;
+ enum snmp_ret result;
+ enum asn_err err;
+
+ memset(&context, 0, sizeof(context));
+ context.ctx.data = data;
+
+ memset(resp, 0, sizeof(*resp));
+ strcpy(resp->community, pdu->community);
+ resp->version = pdu->version;
+ resp->type = SNMP_PDU_RESPONSE;
+ resp->request_id = pdu->request_id;
+ resp->version = pdu->version;
+
+ if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK)
+ /* cannot even encode header - very bad */
+ return (SNMP_RET_IGN);
+
+ if ((non_rep = pdu->error_status) > pdu->nbindings)
+ non_rep = pdu->nbindings;
+
+ /* non-repeaters */
+ for (i = 0; i < non_rep; i++) {
+ result = do_getnext(&context, &pdu->bindings[i],
+ &resp->bindings[resp->nbindings], pdu);
+
+ if (result != SNMP_RET_OK) {
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (result);
+ }
+
+ err = snmp_binding_encode(resp_b,
+ &resp->bindings[resp->nbindings++]);
+
+ if (err == ASN_ERR_EOBUF)
+ goto done;
+
+ if (err != ASN_ERR_OK) {
+ if (TR(GET))
+ snmp_debug("getnext: binding encoding: %u", err);
+ pdu->error_status = SNMP_ERR_GENERR;
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ }
+
+ if (non_rep == pdu->nbindings)
+ goto done;
+
+ /* repeates */
+ for (cnt = 0; cnt < pdu->error_index; cnt++) {
+ eomib = 1;
+ for (i = non_rep; i < pdu->nbindings; i++) {
+ if (cnt == 0)
+ result = do_getnext(&context, &pdu->bindings[i],
+ &resp->bindings[resp->nbindings], pdu);
+ else
+ result = do_getnext(&context,
+ &resp->bindings[resp->nbindings -
+ (pdu->nbindings - non_rep)],
+ &resp->bindings[resp->nbindings], pdu);
+
+ if (result != SNMP_RET_OK) {
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (result);
+ }
+ if (resp->bindings[resp->nbindings].syntax !=
+ SNMP_SYNTAX_ENDOFMIBVIEW)
+ eomib = 0;
+
+ err = snmp_binding_encode(resp_b,
+ &resp->bindings[resp->nbindings++]);
+
+ if (err == ASN_ERR_EOBUF)
+ goto done;
+
+ if (err != ASN_ERR_OK) {
+ if (TR(GET))
+ snmp_debug("getnext: binding encoding: %u", err);
+ pdu->error_status = SNMP_ERR_GENERR;
+ pdu->error_index = i + 1;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ }
+ if (eomib)
+ break;
+ }
+
+ done:
+ return (snmp_fix_encoding(resp_b, resp));
+}
+
+/*
+ * Rollback a SET operation. Failed index is 'i'.
+ */
+static void
+rollback(struct context *context, struct snmp_pdu *pdu, u_int i)
+{
+ struct snmp_value *b;
+ const struct snmp_node *np;
+ int ret;
+
+ while (i-- > 0) {
+ b = &pdu->bindings[i];
+ np = context->node[i];
+
+ context->ctx.scratch = &context->scratch[i];
+
+ ret = (*np->op)(&context->ctx, b, np->oid.len, np->index,
+ SNMP_OP_ROLLBACK);
+
+ if (ret != SNMP_ERR_NOERROR) {
+ snmp_error("set: rollback failed (%d) on variable %s "
+ "index %u", ret, asn_oid2str(&b->var), i);
+ if (pdu->version != SNMP_V1) {
+ pdu->error_status = SNMP_ERR_UNDO_FAILED;
+ pdu->error_index = 0;
+ }
+ }
+ }
+}
+
+/*
+ * Commit dependencies.
+ */
+int
+snmp_dep_commit(struct snmp_context *ctx)
+{
+ struct context *context = (struct context *)ctx;
+ int ret;
+
+ TAILQ_FOREACH(context->depend, &context->dlist, link) {
+ ctx->dep = &context->depend->dep;
+
+ if (TR(SET))
+ snmp_debug("set: dependency commit %s",
+ asn_oid2str(&ctx->dep->obj));
+
+ ret = context->depend->func(ctx, ctx->dep, SNMP_DEPOP_COMMIT);
+
+ if (ret != SNMP_ERR_NOERROR) {
+ if (TR(SET))
+ snmp_debug("set: dependency failed %d", ret);
+ return (ret);
+ }
+ }
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Rollback dependencies
+ */
+int
+snmp_dep_rollback(struct snmp_context *ctx)
+{
+ struct context *context = (struct context *)ctx;
+ int ret, ret1;
+ char objbuf[ASN_OIDSTRLEN];
+ char idxbuf[ASN_OIDSTRLEN];
+
+ ret1 = SNMP_ERR_NOERROR;
+ while ((context->depend =
+ TAILQ_PREV(context->depend, depend_list, link)) != NULL) {
+ ctx->dep = &context->depend->dep;
+
+ if (TR(SET))
+ snmp_debug("set: dependency rollback %s",
+ asn_oid2str(&ctx->dep->obj));
+
+ ret = context->depend->func(ctx, ctx->dep, SNMP_DEPOP_ROLLBACK);
+
+ if (ret != SNMP_ERR_NOERROR) {
+ snmp_debug("set: dep rollback returns %u: %s %s", ret,
+ asn_oid2str_r(&ctx->dep->obj, objbuf),
+ asn_oid2str_r(&ctx->dep->idx, idxbuf));
+ if (ret1 == SNMP_ERR_NOERROR)
+ ret1 = ret;
+ }
+ }
+ return (ret1);
+}
+
+/*
+ * Do a SET operation.
+ */
+enum snmp_ret
+snmp_set(struct snmp_pdu *pdu, struct asn_buf *resp_b,
+ struct snmp_pdu *resp, void *data)
+{
+ int ret;
+ u_int i;
+ enum snmp_ret code;
+ enum asn_err asnerr;
+ struct context context;
+ const struct snmp_node *np;
+ struct finish *f;
+ struct depend *d;
+ struct snmp_value *b;
+ enum snmp_syntax except;
+
+ memset(&context, 0, sizeof(context));
+ TAILQ_INIT(&context.dlist);
+ STAILQ_INIT(&context.flist);
+ context.ctx.data = data;
+
+ memset(resp, 0, sizeof(*resp));
+ strcpy(resp->community, pdu->community);
+ resp->type = SNMP_PDU_RESPONSE;
+ resp->request_id = pdu->request_id;
+ resp->version = pdu->version;
+
+ if (snmp_pdu_encode_header(resp_b, resp))
+ return (SNMP_RET_IGN);
+
+ /*
+ * 1. Find all nodes, check that they are writeable and
+ * that the syntax is ok, copy over the binding to the response.
+ */
+ for (i = 0; i < pdu->nbindings; i++) {
+ b = &pdu->bindings[i];
+
+ if ((np = context.node[i] = find_node(b, &except)) == NULL) {
+ /* not found altogether or LEAF with wrong index */
+ if (TR(SET))
+ snmp_debug("set: node not found %s",
+ asn_oid2str_r(&b->var, oidbuf));
+ if (pdu->version == SNMP_V1) {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_NOSUCHNAME;
+ } else if ((np = find_subnode(b)) != NULL) {
+ /* 2. intermediate object */
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_NOT_WRITEABLE;
+ } else if (except == SNMP_SYNTAX_NOSUCHOBJECT) {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_NO_ACCESS;
+ } else {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_NO_CREATION;
+ }
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ /*
+ * 2. write/createable?
+ * Can check this for leafs only, because in v2 we have
+ * to differentiate between NOT_WRITEABLE and NO_CREATION
+ * and only the action routine for COLUMNS knows, whether
+ * a column exists.
+ */
+ if (np->type == SNMP_NODE_LEAF &&
+ !(np->flags & SNMP_NODE_CANSET)) {
+ if (pdu->version == SNMP_V1) {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_NOSUCHNAME;
+ } else {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_NOT_WRITEABLE;
+ }
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ /*
+ * 3. Ensure the right syntax
+ */
+ if (np->syntax != b->syntax) {
+ if (pdu->version == SNMP_V1) {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_BADVALUE; /* v2: wrongType */
+ } else {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_WRONG_TYPE;
+ }
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ /*
+ * 4. Copy binding
+ */
+ if (snmp_value_copy(&resp->bindings[i], b)) {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_GENERR;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ asnerr = snmp_binding_encode(resp_b, &resp->bindings[i]);
+ if (asnerr == ASN_ERR_EOBUF) {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_TOOBIG;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ } else if (asnerr != ASN_ERR_OK) {
+ pdu->error_index = i + 1;
+ pdu->error_status = SNMP_ERR_GENERR;
+ snmp_pdu_free(resp);
+ return (SNMP_RET_ERR);
+ }
+ resp->nbindings++;
+ }
+
+ code = SNMP_RET_OK;
+
+ /*
+ * 2. Call the SET method for each node. If a SET fails, rollback
+ * everything. Map error codes depending on the version.
+ */
+ for (i = 0; i < pdu->nbindings; i++) {
+ b = &pdu->bindings[i];
+ np = context.node[i];
+
+ context.ctx.var_index = i + 1;
+ context.ctx.scratch = &context.scratch[i];
+
+ ret = (*np->op)(&context.ctx, b, np->oid.len, np->index,
+ SNMP_OP_SET);
+
+ if (TR(SET))
+ snmp_debug("set: action %s returns %d", np->name, ret);
+
+ if (pdu->version == SNMP_V1) {
+ switch (ret) {
+ case SNMP_ERR_NO_ACCESS:
+ ret = SNMP_ERR_NOSUCHNAME;
+ break;
+ case SNMP_ERR_WRONG_TYPE:
+ /* should no happen */
+ ret = SNMP_ERR_BADVALUE;
+ break;
+ case SNMP_ERR_WRONG_LENGTH:
+ ret = SNMP_ERR_BADVALUE;
+ break;
+ case SNMP_ERR_WRONG_ENCODING:
+ /* should not happen */
+ ret = SNMP_ERR_BADVALUE;
+ break;
+ case SNMP_ERR_WRONG_VALUE:
+ ret = SNMP_ERR_BADVALUE;
+ break;
+ case SNMP_ERR_NO_CREATION:
+ ret = SNMP_ERR_NOSUCHNAME;
+ break;
+ case SNMP_ERR_INCONS_VALUE:
+ ret = SNMP_ERR_BADVALUE;
+ break;
+ case SNMP_ERR_RES_UNAVAIL:
+ ret = SNMP_ERR_GENERR;
+ break;
+ case SNMP_ERR_COMMIT_FAILED:
+ ret = SNMP_ERR_GENERR;
+ break;
+ case SNMP_ERR_UNDO_FAILED:
+ ret = SNMP_ERR_GENERR;
+ break;
+ case SNMP_ERR_AUTH_ERR:
+ /* should not happen */
+ ret = SNMP_ERR_GENERR;
+ break;
+ case SNMP_ERR_NOT_WRITEABLE:
+ ret = SNMP_ERR_NOSUCHNAME;
+ break;
+ case SNMP_ERR_INCONS_NAME:
+ ret = SNMP_ERR_BADVALUE;
+ break;
+ }
+ }
+ if (ret != SNMP_ERR_NOERROR) {
+ pdu->error_index = i + 1;
+ pdu->error_status = ret;
+
+ rollback(&context, pdu, i);
+ snmp_pdu_free(resp);
+
+ code = SNMP_RET_ERR;
+
+ goto errout;
+ }
+ }
+
+ /*
+ * 3. Call dependencies
+ */
+ if (TR(SET))
+ snmp_debug("set: set operations ok");
+
+ if ((ret = snmp_dep_commit(&context.ctx)) != SNMP_ERR_NOERROR) {
+ pdu->error_status = ret;
+ pdu->error_index = context.ctx.var_index;
+
+ if ((ret = snmp_dep_rollback(&context.ctx)) != SNMP_ERR_NOERROR) {
+ if (pdu->version != SNMP_V1) {
+ pdu->error_status = SNMP_ERR_UNDO_FAILED;
+ pdu->error_index = 0;
+ }
+ }
+ rollback(&context, pdu, i);
+ snmp_pdu_free(resp);
+
+ code = SNMP_RET_ERR;
+
+ goto errout;
+ }
+
+ /*
+ * 4. Commit and copy values from the original packet to the response.
+ * This is not the commit operation from RFC 1905 but rather an
+ * 'FREE RESOURCES' operation. It shouldn't fail.
+ */
+ if (TR(SET))
+ snmp_debug("set: commiting");
+
+ for (i = 0; i < pdu->nbindings; i++) {
+ b = &resp->bindings[i];
+ np = context.node[i];
+
+ context.ctx.var_index = i + 1;
+ context.ctx.scratch = &context.scratch[i];
+
+ ret = (*np->op)(&context.ctx, b, np->oid.len, np->index,
+ SNMP_OP_COMMIT);
+
+ if (ret != SNMP_ERR_NOERROR)
+ snmp_error("set: commit failed (%d) on"
+ " variable %s index %u", ret,
+ asn_oid2str_r(&b->var, oidbuf), i);
+ }
+
+ if (snmp_fix_encoding(resp_b, resp) != SNMP_CODE_OK) {
+ snmp_error("set: fix_encoding failed");
+ snmp_pdu_free(resp);
+ code = SNMP_RET_IGN;
+ }
+
+ /*
+ * Done
+ */
+ errout:
+ while ((d = TAILQ_FIRST(&context.dlist)) != NULL) {
+ TAILQ_REMOVE(&context.dlist, d, link);
+ free(d);
+ }
+
+ /*
+ * call finish function
+ */
+ while ((f = STAILQ_FIRST(&context.flist)) != NULL) {
+ STAILQ_REMOVE_HEAD(&context.flist, link);
+ (*f->func)(&context.ctx, code != SNMP_RET_OK, f->arg);
+ free(f);
+ }
+
+ if (TR(SET))
+ snmp_debug("set: returning %d", code);
+
+ return (code);
+}
+/*
+ * Lookup a dependency. If it doesn't exist, create one
+ */
+struct snmp_dependency *
+snmp_dep_lookup(struct snmp_context *ctx, const struct asn_oid *obj,
+ const struct asn_oid *idx, size_t len, snmp_depop_t func)
+{
+ struct context *context;
+ struct depend *d;
+
+ context = (struct context *)(void *)
+ ((char *)ctx - offsetof(struct context, ctx));
+ if (TR(DEPEND)) {
+ snmp_debug("depend: looking for %s", asn_oid2str(obj));
+ if (idx)
+ snmp_debug("depend: index is %s", asn_oid2str(idx));
+ }
+ TAILQ_FOREACH(d, &context->dlist, link)
+ if (asn_compare_oid(obj, &d->dep.obj) == 0 &&
+ ((idx == NULL && d->dep.idx.len == 0) ||
+ (idx != NULL && asn_compare_oid(idx, &d->dep.idx) == 0))) {
+ if(TR(DEPEND))
+ snmp_debug("depend: found");
+ return (&d->dep);
+ }
+
+ if(TR(DEPEND))
+ snmp_debug("depend: creating");
+
+ if ((d = malloc(offsetof(struct depend, dep) + len)) == NULL)
+ return (NULL);
+ memset(&d->dep, 0, len);
+
+ d->dep.obj = *obj;
+ if (idx == NULL)
+ d->dep.idx.len = 0;
+ else
+ d->dep.idx = *idx;
+ d->len = len;
+ d->func = func;
+
+ TAILQ_INSERT_TAIL(&context->dlist, d, link);
+
+ return (&d->dep);
+}
+
+/*
+ * Register a finish function.
+ */
+int
+snmp_set_atfinish(struct snmp_context *ctx, snmp_set_finish_t func, void *arg)
+{
+ struct context *context;
+ struct finish *f;
+
+ context = (struct context *)(void *)
+ ((char *)ctx - offsetof(struct context, ctx));
+ if ((f = malloc(sizeof(struct finish))) == NULL)
+ return (-1);
+ f->func = func;
+ f->arg = arg;
+ STAILQ_INSERT_TAIL(&context->flist, f, link);
+
+ return (0);
+}
+
+/*
+ * Make an error response from a PDU. We do this without decoding the
+ * variable bindings. This means we can sent the junk back to a caller
+ * that has sent us junk in the first place.
+ */
+enum snmp_ret
+snmp_make_errresp(const struct snmp_pdu *pdu, struct asn_buf *pdu_b,
+ struct asn_buf *resp_b)
+{
+ asn_len_t len;
+ struct snmp_pdu resp;
+ enum asn_err err;
+ enum snmp_code code;
+
+ memset(&resp, 0, sizeof(resp));
+
+ /* Message sequence */
+ if (asn_get_sequence(pdu_b, &len) != ASN_ERR_OK)
+ return (SNMP_RET_IGN);
+ if (pdu_b->asn_len < len)
+ return (SNMP_RET_IGN);
+
+ err = snmp_parse_message_hdr(pdu_b, &resp, &len);
+ if (ASN_ERR_STOPPED(err))
+ return (SNMP_RET_IGN);
+ if (pdu_b->asn_len < len)
+ return (SNMP_RET_IGN);
+ pdu_b->asn_len = len;
+
+ err = snmp_parse_pdus_hdr(pdu_b, &resp, &len);
+ if (ASN_ERR_STOPPED(err))
+ return (SNMP_RET_IGN);
+ if (pdu_b->asn_len < len)
+ return (SNMP_RET_IGN);
+ pdu_b->asn_len = len;
+
+ /* now we have the bindings left - construct new message */
+ resp.error_status = pdu->error_status;
+ resp.error_index = pdu->error_index;
+ resp.type = SNMP_PDU_RESPONSE;
+
+ code = snmp_pdu_encode_header(resp_b, &resp);
+ if (code != SNMP_CODE_OK)
+ return (SNMP_RET_IGN);
+
+ if (pdu_b->asn_len > resp_b->asn_len)
+ /* too short */
+ return (SNMP_RET_IGN);
+ (void)memcpy(resp_b->asn_ptr, pdu_b->asn_cptr, pdu_b->asn_len);
+ resp_b->asn_len -= pdu_b->asn_len;
+ resp_b->asn_ptr += pdu_b->asn_len;
+
+ code = snmp_fix_encoding(resp_b, &resp);
+ if (code != SNMP_CODE_OK)
+ return (SNMP_RET_IGN);
+
+ return (SNMP_RET_OK);
+}
+
+static void
+snmp_debug_func(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
OpenPOWER on IntegriCloud