diff options
| author | Hartmut Brandt <harti@FreeBSD.org> | 2003-11-10 08:53:38 +0000 | 
|---|---|---|
| committer | Hartmut Brandt <harti@FreeBSD.org> | 2003-11-10 08:53:38 +0000 | 
| commit | f06ca4af1879f1cb903660d2a12ba7edcf152938 (patch) | |
| tree | 150e45ef74a56ce93475bd8e0436d6da856d4a18 /contrib/bsnmp/lib/snmpagent.c | |
Diffstat (limited to 'contrib/bsnmp/lib/snmpagent.c')
| -rw-r--r-- | contrib/bsnmp/lib/snmpagent.c | 1031 | 
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 0000000000000..19ac661a2f542 --- /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"); +} | 
