blob: 866936e2d98ce819fdd6d5178f2f1b554810dd79 [file] [log] [blame]
/*
* AgentX Administrative request handling
*/
#include <net-snmp/net-snmp-config.h>
#include <sys/types.h>
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "agentx/protocol.h"
#include "agentx/client.h"
#include <net-snmp/agent/agent_index.h>
#include <net-snmp/agent/agent_trap.h>
#include <net-snmp/agent/agent_callbacks.h>
#include <net-snmp/agent/agent_sysORTable.h>
#include "master.h"
netsnmp_session *
find_agentx_session(netsnmp_session * session, int sessid)
{
netsnmp_session *sp;
for (sp = session->subsession; sp != NULL; sp = sp->next) {
if (sp->sessid == sessid)
return sp;
}
return NULL;
}
int
open_agentx_session(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
DEBUGMSGTL(("agentx/master", "open %8p\n", session));
sp = (netsnmp_session *) malloc(sizeof(netsnmp_session));
if (sp == NULL) {
session->s_snmp_errno = AGENTX_ERR_OPEN_FAILED;
return -1;
}
memcpy(sp, session, sizeof(netsnmp_session));
sp->sessid = snmp_get_next_sessid();
sp->version = pdu->version;
sp->timeout = pdu->time;
/*
* Be careful with fields: if these aren't zeroed, they will get free()d
* more than once when the session is closed -- once in the main session,
* and once in each subsession. Basically, if it's not being used for
* some AgentX-specific purpose, it ought to be zeroed here.
*/
sp->community = NULL;
sp->peername = NULL;
sp->contextEngineID = NULL;
sp->contextName = NULL;
sp->securityEngineID = NULL;
sp->securityPrivProto = NULL;
/*
* This next bit utilises unused SNMPv3 fields
* to store the subagent OID and description.
* This really ought to use AgentX-specific fields,
* but it hardly seems worth it for a one-off use.
*
* But I'm willing to be persuaded otherwise.... */
sp->securityAuthProto = snmp_duplicate_objid(pdu->variables->name,
pdu->variables->
name_length);
sp->securityAuthProtoLen = pdu->variables->name_length;
sp->securityName = strdup((char *) pdu->variables->val.string);
sp->engineTime = (netsnmp_get_agent_runtime() + 50) / 100;
sp->subsession = session; /* link back to head */
sp->flags |= SNMP_FLAGS_SUBSESSION;
sp->flags &= ~AGENTX_MSG_FLAG_NETWORK_BYTE_ORDER;
sp->flags |= (pdu->flags & AGENTX_MSG_FLAG_NETWORK_BYTE_ORDER);
sp->next = session->subsession;
session->subsession = sp;
DEBUGMSGTL(("agentx/master", "opened %8p = %ld with flags = %02lx\n",
sp, sp->sessid, sp->flags & AGENTX_MSG_FLAGS_MASK));
return sp->sessid;
}
int
close_agentx_session(netsnmp_session * session, int sessid)
{
netsnmp_session *sp, **prevNext;
if (!session)
return AGENTX_ERR_NOT_OPEN;
DEBUGMSGTL(("agentx/master", "close %8p, %d\n", session, sessid));
if (sessid == -1) {
/*
* The following is necessary to avoid locking up the agent when
* a sugagent dies during a set request. We must clean up the
* requests, so that the delegated request will be completed and
* further requests can be processed
*/
netsnmp_remove_delegated_requests_for_session(session);
if (session->subsession != NULL) {
netsnmp_session *subsession = session->subsession;
for(; subsession; subsession = subsession->next) {
netsnmp_remove_delegated_requests_for_session(subsession);
}
}
unregister_mibs_by_session(session);
unregister_index_by_session(session);
unregister_sysORTable_by_session(session);
SNMP_FREE(session->myvoid);
return AGENTX_ERR_NOERROR;
}
prevNext = &(session->subsession);
for (sp = session->subsession; sp != NULL; sp = sp->next) {
if (sp->sessid == sessid) {
unregister_mibs_by_session(sp);
unregister_index_by_session(sp);
unregister_sysORTable_by_session(sp);
*prevNext = sp->next;
if (sp->securityAuthProto != NULL) {
free(sp->securityAuthProto);
}
if (sp->securityName != NULL) {
free(sp->securityName);
}
free(sp);
sp = NULL;
DEBUGMSGTL(("agentx/master", "closed %8p, %d okay\n",
session, sessid));
return AGENTX_ERR_NOERROR;
}
prevNext = &(sp->next);
}
DEBUGMSGTL(("agentx/master", "sessid %d not found\n", sessid));
return AGENTX_ERR_NOT_OPEN;
}
int
register_agentx_list(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
char buf[128];
oid ubound = 0;
u_long flags = 0;
netsnmp_handler_registration *reg;
int rc = 0;
int cacheid;
DEBUGMSGTL(("agentx/master", "in register_agentx_list\n"));
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL)
return AGENTX_ERR_NOT_OPEN;
sprintf(buf, "AgentX subagent %ld, session %8p, subsession %8p",
sp->sessid, session, sp);
/*
* * TODO: registration timeout
* * registration context
*/
if (pdu->range_subid) {
ubound = pdu->variables->val.objid[pdu->range_subid - 1];
}
if (pdu->flags & AGENTX_MSG_FLAG_INSTANCE_REGISTER) {
flags = FULLY_QUALIFIED_INSTANCE;
}
reg = netsnmp_create_handler_registration(buf, agentx_master_handler, pdu->variables->name, pdu->variables->name_length, HANDLER_CAN_RWRITE | HANDLER_CAN_GETBULK); /* fake it */
if (!session->myvoid) {
session->myvoid = malloc(sizeof(cacheid));
cacheid = netsnmp_allocate_globalcacheid();
*((int *) session->myvoid) = cacheid;
} else {
cacheid = *((int *) session->myvoid);
}
reg->handler->myvoid = session;
reg->global_cacheid = cacheid;
if (NULL != pdu->community)
reg->contextName = strdup((char *)pdu->community);
/*
* register mib. Note that for failure cases, the registration info
* (reg) will be freed, and thus is no longer a valid pointer.
*/
switch (netsnmp_register_mib(buf, NULL, 0, 0,
pdu->variables->name,
pdu->variables->name_length,
pdu->priority, pdu->range_subid, ubound,
sp, (char *) pdu->community, pdu->time,
flags, reg, 1)) {
case MIB_REGISTERED_OK:
DEBUGMSGTL(("agentx/master", "registered ok\n"));
return AGENTX_ERR_NOERROR;
case MIB_DUPLICATE_REGISTRATION:
DEBUGMSGTL(("agentx/master", "duplicate registration\n"));
rc = AGENTX_ERR_DUPLICATE_REGISTRATION;
break;
case MIB_REGISTRATION_FAILED:
default:
rc = AGENTX_ERR_REQUEST_DENIED;
DEBUGMSGTL(("agentx/master", "failed registration\n"));
}
return rc;
}
int
unregister_agentx_list(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
int rc = 0;
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL) {
return AGENTX_ERR_NOT_OPEN;
}
if (pdu->range_subid != 0) {
oid ubound =
pdu->variables->val.objid[pdu->range_subid - 1];
rc = netsnmp_unregister_mib_table_row(pdu->variables->name,
pdu->variables->name_length,
pdu->priority,
pdu->range_subid, ubound,
(char *) pdu->community);
} else {
rc = unregister_mib_context(pdu->variables->name,
pdu->variables->name_length,
pdu->priority, 0, 0,
(char *) pdu->community);
}
switch (rc) {
case MIB_UNREGISTERED_OK:
return AGENTX_ERR_NOERROR;
case MIB_NO_SUCH_REGISTRATION:
return AGENTX_ERR_UNKNOWN_REGISTRATION;
case MIB_UNREGISTRATION_FAILED:
default:
return AGENTX_ERR_REQUEST_DENIED;
}
}
int
allocate_idx_list(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
netsnmp_variable_list *vp, *vp2, *next, *res;
int flags = 0;
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL)
return AGENTX_ERR_NOT_OPEN;
if (pdu->flags & AGENTX_MSG_FLAG_ANY_INSTANCE)
flags |= ALLOCATE_ANY_INDEX;
if (pdu->flags & AGENTX_MSG_FLAG_NEW_INSTANCE)
flags |= ALLOCATE_NEW_INDEX;
/*
* XXX - what about errors?
*
* If any allocations fail, then we need to
* *fully* release the earlier ones.
* (i.e. remove them completely from the index registry,
* not simply mark them as available for re-use)
*
* For now - assume they all succeed.
*/
for (vp = pdu->variables; vp != NULL; vp = next) {
next = vp->next_variable;
res = register_index(vp, flags, session);
if (res == NULL) {
/*
* If any allocations fail, we need to *fully* release
* all previous ones (i.e. remove them completely
* from the index registry)
*/
for (vp2 = pdu->variables; vp2 != vp; vp2 = vp2->next_variable) {
remove_index(vp2, session);
}
return AGENTX_ERR_INDEX_NONE_AVAILABLE; /* XXX */
} else {
(void) snmp_clone_var(res, vp);
free(res);
}
vp->next_variable = next;
}
return AGENTX_ERR_NOERROR;
}
int
release_idx_list(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
netsnmp_variable_list *vp, *vp2, *rv = NULL;
int res;
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL)
return AGENTX_ERR_NOT_OPEN;
for (vp = pdu->variables; vp != NULL; vp = vp->next_variable) {
res = unregister_index(vp, TRUE, session);
/*
* If any releases fail,
* we need to reinstate all previous ones.
*/
if (res != SNMP_ERR_NOERROR) {
for (vp2 = pdu->variables; vp2 != vp; vp2 = vp2->next_variable) {
rv = register_index(vp2, ALLOCATE_THIS_INDEX, session);
free(rv);
}
return AGENTX_ERR_INDEX_NOT_ALLOCATED; /* Probably */
}
}
return AGENTX_ERR_NOERROR;
}
int
add_agent_caps_list(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
char* description;
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL)
return AGENTX_ERR_NOT_OPEN;
description = netsnmp_strdup_and_null(pdu->variables->val.string,
pdu->variables->val_len);
register_sysORTable_sess(pdu->variables->name, pdu->variables->name_length,
description, sp);
free(description);
return AGENTX_ERR_NOERROR;
}
int
remove_agent_caps_list(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
int rc;
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL)
return AGENTX_ERR_NOT_OPEN;
rc = unregister_sysORTable_sess(pdu->variables->name,
pdu->variables->name_length, sp);
if (rc < 0)
return AGENTX_ERR_UNKNOWN_AGENTCAPS;
return AGENTX_ERR_NOERROR;
}
int
agentx_notify(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
netsnmp_variable_list *var;
extern const oid sysuptime_oid[], snmptrap_oid[];
extern const size_t sysuptime_oid_len, snmptrap_oid_len;
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL)
return AGENTX_ERR_NOT_OPEN;
var = pdu->variables;
if (!var)
return AGENTX_ERR_PROCESSING_ERROR;
if (snmp_oid_compare(var->name, var->name_length,
sysuptime_oid, sysuptime_oid_len) == 0) {
var = var->next_variable;
}
if (!var || snmp_oid_compare(var->name, var->name_length,
snmptrap_oid, snmptrap_oid_len) != 0)
return AGENTX_ERR_PROCESSING_ERROR;
/*
* If sysUptime isn't the first varbind, don't worry.
* send_trap_vars() will add it if necessary.
*
* Note that if this behaviour is altered, it will
* be necessary to add sysUptime here,
* as this is valid AgentX syntax.
*/
/* If a context name was specified, send the trap using that context.
* Otherwise, send the trap without the context using the old method */
if (pdu->contextName != NULL)
{
send_trap_vars_with_context(-1, -1, pdu->variables,
pdu->contextName);
}
else
{
send_trap_vars(-1, -1, pdu->variables);
}
return AGENTX_ERR_NOERROR;
}
int
agentx_ping_response(netsnmp_session * session, netsnmp_pdu *pdu)
{
netsnmp_session *sp;
sp = find_agentx_session(session, pdu->sessid);
if (sp == NULL)
return AGENTX_ERR_NOT_OPEN;
else
return AGENTX_ERR_NOERROR;
}
int
handle_master_agentx_packet(int operation,
netsnmp_session * session,
int reqid, netsnmp_pdu *pdu, void *magic)
{
netsnmp_agent_session *asp;
if (operation == NETSNMP_CALLBACK_OP_DISCONNECT) {
DEBUGMSGTL(("agentx/master",
"transport disconnect on session %8p\n", session));
/*
* Shut this session down gracefully.
*/
close_agentx_session(session, -1);
return 1;
} else if (operation == NETSNMP_CALLBACK_OP_CONNECT) {
DEBUGMSGTL(("agentx/master",
"transport connect on session %8p\n", session));
return 1;
} else if (operation != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) {
DEBUGMSGTL(("agentx/master", "unexpected callback op %d\n",
operation));
return 1;
}
/*
* Okay, it's a NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE op.
*/
if (magic) {
asp = (netsnmp_agent_session *) magic;
} else {
asp = init_agent_snmp_session(session, pdu);
}
DEBUGMSGTL(("agentx/master", "handle pdu (req=0x%lx,trans=0x%lx,sess=0x%lx)\n",
(unsigned long)pdu->reqid, (unsigned long)pdu->transid,
(unsigned long)pdu->sessid));
switch (pdu->command) {
case AGENTX_MSG_OPEN:
asp->pdu->sessid = open_agentx_session(session, pdu);
if (asp->pdu->sessid == -1)
asp->status = session->s_snmp_errno;
break;
case AGENTX_MSG_CLOSE:
asp->status = close_agentx_session(session, pdu->sessid);
break;
case AGENTX_MSG_REGISTER:
asp->status = register_agentx_list(session, pdu);
break;
case AGENTX_MSG_UNREGISTER:
asp->status = unregister_agentx_list(session, pdu);
break;
case AGENTX_MSG_INDEX_ALLOCATE:
asp->status = allocate_idx_list(session, asp->pdu);
if (asp->status != AGENTX_ERR_NOERROR) {
snmp_free_pdu(asp->pdu);
asp->pdu = snmp_clone_pdu(pdu);
}
break;
case AGENTX_MSG_INDEX_DEALLOCATE:
asp->status = release_idx_list(session, pdu);
break;
case AGENTX_MSG_ADD_AGENT_CAPS:
asp->status = add_agent_caps_list(session, pdu);
break;
case AGENTX_MSG_REMOVE_AGENT_CAPS:
asp->status = remove_agent_caps_list(session, pdu);
break;
case AGENTX_MSG_NOTIFY:
asp->status = agentx_notify(session, pdu);
break;
case AGENTX_MSG_PING:
asp->status = agentx_ping_response(session, pdu);
break;
/*
* TODO: Other admin packets
*/
case AGENTX_MSG_GET:
case AGENTX_MSG_GETNEXT:
case AGENTX_MSG_GETBULK:
case AGENTX_MSG_TESTSET:
case AGENTX_MSG_COMMITSET:
case AGENTX_MSG_UNDOSET:
case AGENTX_MSG_CLEANUPSET:
case AGENTX_MSG_RESPONSE:
/*
* Shouldn't be handled here
*/
break;
default:
asp->status = AGENTX_ERR_PARSE_FAILED;
break;
}
asp->pdu->time = netsnmp_get_agent_uptime();
asp->pdu->command = AGENTX_MSG_RESPONSE;
asp->pdu->errstat = asp->status;
DEBUGMSGTL(("agentx/master", "send response, stat %d (req=0x%lx,trans="
"0x%lx,sess=0x%lx)\n",
asp->status, (unsigned long)pdu->reqid,
(unsigned long)pdu->transid, (unsigned long)pdu->sessid));
if (!snmp_send(asp->session, asp->pdu)) {
char *eb = NULL;
int pe, pse;
snmp_error(asp->session, &pe, &pse, &eb);
snmp_free_pdu(asp->pdu);
DEBUGMSGTL(("agentx/master", "FAILED %d %d %s\n", pe, pse, eb));
free(eb);
}
asp->pdu = NULL;
free_agent_snmp_session(asp);
return 1;
}