/* * AgentX Administrative request handling */ #include #include #include #ifdef HAVE_STRING_H #include #else #include #endif #ifdef HAVE_STDLIB_H #include #endif #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #if HAVE_NETINET_IN_H #include #endif #if HAVE_SYS_SOCKET_H #include #endif #include #include #include "agent_global_vars.h" #include "agentx/protocol.h" #include "agentx/client.h" #include "agentx/subagent.h" #include "agentx/master_admin.h" #include #include #include #include #include "master.h" netsnmp_feature_require(unregister_mib_table_row); netsnmp_feature_require(trap_vars_with_context); netsnmp_feature_require(calculate_sectime_diff); netsnmp_feature_require(allocate_globalcacheid); netsnmp_feature_require(remove_index); 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 = (uint32_t)((netsnmp_get_agent_runtime() + 50) / 100) & 0x7fffffffL; 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 */ while (netsnmp_remove_delegated_requests_for_session(session)) { DEBUGMSGTL(("agentx/master", "Continue removing delegated reqests\n")); } if (session->subsession != NULL) { netsnmp_session *subsession = session->subsession; for(; subsession; subsession = subsession->next) { while (netsnmp_remove_delegated_requests_for_session(subsession)) { DEBUGMSGTL(("agentx/master", "Continue removing delegated subsession reqests\n")); } } } 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) { netsnmp_remove_delegated_requests_for_session(sp); 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; 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; }