/* * AgentX master agent */ /* Portions of this file are subject to the following copyright(s). See * the Net-SNMP's COPYING file for more details and other copyrights * that may apply: */ /* * Portions of this file are copyrighted by: * Copyright © 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms specified in the COPYING file * distributed with the Net-SNMP package. */ #include #include #if HAVE_IO_H #include #endif #include #include #ifdef HAVE_STDLIB_H #include #endif #if HAVE_STRING_H #include #else #include #endif #if HAVE_NETINET_IN_H #include #endif #if HAVE_SYS_SOCKET_H #include #endif #include #if HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #define SNMP_NEED_REQUEST_LIST #include #include #include "snmpd.h" #include "agentx/protocol.h" #include "agentx/master_admin.h" netsnmp_feature_require(handler_mark_requests_as_delegated) netsnmp_feature_require(unix_socket_paths) netsnmp_feature_require(free_agent_snmp_session_by_session) void real_init_master(void) { netsnmp_session sess, *session = NULL; char *agentx_sockets; char *cp1; if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE) != MASTER_AGENT) return; if (netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET)) { agentx_sockets = strdup(netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET)); #ifdef NETSNMP_AGENTX_DOM_SOCK_ONLY if (agentx_sockets[0] != '/') { /* unix:/path */ if (agentx_sockets[5] != '/') { snmp_log(LOG_ERR, "Error: %s transport is not supported, disabling agentx/master.\n", agentx_sockets); SNMP_FREE(agentx_sockets); return; } } #endif } else { agentx_sockets = strdup(""); } DEBUGMSGTL(("agentx/master", "initializing...\n")); snmp_sess_init(&sess); sess.version = AGENTX_VERSION_1; sess.flags |= SNMP_FLAGS_STREAM_SOCKET; sess.timeout = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_AGENTX_TIMEOUT); sess.retries = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_AGENTX_RETRIES); #ifdef NETSNMP_TRANSPORT_UNIX_DOMAIN { int agentx_dir_perm = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_DIR_PERM); if (agentx_dir_perm == 0) agentx_dir_perm = NETSNMP_AGENT_DIRECTORY_MODE; netsnmp_unix_create_path_with_mode(agentx_dir_perm); } #endif cp1 = agentx_sockets; while (cp1) { netsnmp_transport *t; /* * If the AgentX socket string contains multiple descriptors, * then pick this apart and handle them one by one. * */ sess.peername = cp1; cp1 = strchr(sess.peername, ','); if (cp1 != NULL) { *cp1++ = '\0'; } /* * Let 'snmp_open' interpret the descriptor. */ sess.local_port = AGENTX_PORT; /* Indicate server & set default port */ sess.callback = handle_master_agentx_packet; errno = 0; t = netsnmp_transport_open_server("agentx", sess.peername); if (t == NULL) { /* * diagnose snmp_open errors with the input netsnmp_session * pointer. */ char buf[1024]; if (!netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_NO_ROOT_ACCESS)) { snprintf(buf, sizeof(buf), "Error: Couldn't open a master agentx socket to " "listen on (%s)", sess.peername); snmp_sess_perror(buf, &sess); exit(1); } else { snprintf(buf, sizeof(buf), "Warning: Couldn't open a master agentx socket to " "listen on (%s)", sess.peername); netsnmp_sess_log_error(LOG_WARNING, buf, &sess); } } else { #ifdef NETSNMP_TRANSPORT_UNIX_DOMAIN if (t->domain == netsnmp_UnixDomain && t->local != NULL) { /* * Apply any settings to the ownership/permissions of the * AgentX socket */ int agentx_sock_perm = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCK_PERM); int agentx_sock_user = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCK_USER); int agentx_sock_group = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCK_GROUP); char name[sizeof(struct sockaddr_un) + 1]; memcpy(name, t->local, t->local_length); name[t->local_length] = '\0'; if (agentx_sock_perm != 0) chmod(name, agentx_sock_perm); if (agentx_sock_user || agentx_sock_group) { /* * If either of user or group haven't been set, * then leave them unchanged. */ if (agentx_sock_user == 0 ) agentx_sock_user = -1; if (agentx_sock_group == 0 ) agentx_sock_group = -1; chown(name, agentx_sock_user, agentx_sock_group); } } #endif session = snmp_add_full(&sess, t, NULL, agentx_parse, NULL, NULL, agentx_realloc_build, agentx_check_packet, NULL); } if (session == NULL) { netsnmp_transport_free(t); } } #ifdef NETSNMP_TRANSPORT_UNIX_DOMAIN netsnmp_unix_dont_create_path(); #endif SNMP_FREE(agentx_sockets); DEBUGMSGTL(("agentx/master", "initializing... DONE\n")); } /* * Handle the response from an AgentX subagent, * merging the answers back into the original query */ int agentx_got_response(int operation, netsnmp_session * session, int reqid, netsnmp_pdu *pdu, void *magic) { netsnmp_delegated_cache *cache = (netsnmp_delegated_cache *) magic; int i, ret; netsnmp_request_info *requests, *request; netsnmp_variable_list *var; netsnmp_session *ax_session; cache = netsnmp_handler_check_cache(cache); if (!cache) { DEBUGMSGTL(("agentx/master", "response too late on session %8p\n", session)); /* response is too late, free the cache */ if (magic) netsnmp_free_delegated_cache((netsnmp_delegated_cache*) magic); return 1; } requests = cache->requests; switch (operation) { case NETSNMP_CALLBACK_OP_TIMED_OUT:{ void *s = snmp_sess_pointer(session); DEBUGMSGTL(("agentx/master", "timeout on session %8p req=0x%x\n", session, (unsigned)reqid)); netsnmp_handler_mark_requests_as_delegated(requests, REQUEST_IS_NOT_DELEGATED); netsnmp_set_request_error(cache->reqinfo, requests, /* XXXWWW: should be index=0 */ SNMP_ERR_GENERR); /* * This is a bit sledgehammer because the other sessions on this * transport may be okay (e.g. some thread in the subagent has * wedged, but the others are alright). OTOH the overwhelming * probability is that the whole agent has died somehow. */ if (s != NULL) { netsnmp_transport *t = snmp_sess_transport(s); close_agentx_session(session, -1); if (t != NULL) { DEBUGMSGTL(("agentx/master", "close transport\n")); t->f_close(t); } else { DEBUGMSGTL(("agentx/master", "NULL transport??\n")); } } else { DEBUGMSGTL(("agentx/master", "NULL sess_pointer??\n")); } ax_session = (netsnmp_session *) cache->localinfo; netsnmp_free_agent_snmp_session_by_session(ax_session, NULL); netsnmp_free_delegated_cache(cache); return 0; } case NETSNMP_CALLBACK_OP_DISCONNECT: case NETSNMP_CALLBACK_OP_SEND_FAILED: if (operation == NETSNMP_CALLBACK_OP_DISCONNECT) { DEBUGMSGTL(("agentx/master", "disconnect on session %8p\n", session)); } else { DEBUGMSGTL(("agentx/master", "send failed on session %8p\n", session)); } close_agentx_session(session, -1); netsnmp_handler_mark_requests_as_delegated(requests, REQUEST_IS_NOT_DELEGATED); netsnmp_set_request_error(cache->reqinfo, requests, /* XXXWWW: should be index=0 */ SNMP_ERR_GENERR); netsnmp_free_delegated_cache(cache); return 0; case NETSNMP_CALLBACK_OP_RESEND: DEBUGMSGTL(("agentx/master", "resend on session %8p req=0x%x\n", session, (unsigned)reqid)); return 0; case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: /* * This session is alive */ CLEAR_SNMP_STRIKE_FLAGS(session->flags); break; default: snmp_log(LOG_ERR, "Unknown operation %d in agentx_got_response\n", operation); netsnmp_free_delegated_cache(cache); return 0; } DEBUGMSGTL(("agentx/master", "got response errstat=%ld, (req=0x%x,trans=" "0x%x,sess=0x%x)\n", pdu->errstat, (unsigned)pdu->reqid, (unsigned)pdu->transid, (unsigned)pdu->sessid)); if (pdu->errstat != AGENTX_ERR_NOERROR) { /* [RFC 2471 - 7.2.5.2.] * * 1) For any received AgentX response PDU, if res.error is * not `noError', the SNMP response PDU's error code is * set to this value. If res.error contains an AgentX * specific value (e.g. `parseError'), the SNMP response * PDU's error code is set to a value of genErr instead. * Also, the SNMP response PDU's error index is set to * the index of the variable binding corresponding to the * failed VarBind in the subagent's AgentX response PDU. * * All other AgentX response PDUs received due to * processing this SNMP request are ignored. Processing * is complete; the SNMP Response PDU is ready to be sent * (see section 7.2.6, "Sending the SNMP Response-PDU"). */ int err; DEBUGMSGTL(("agentx/master", "agentx_got_response() error branch\n")); switch (pdu->errstat) { case AGENTX_ERR_PARSE_FAILED: case AGENTX_ERR_REQUEST_DENIED: case AGENTX_ERR_PROCESSING_ERROR: err = SNMP_ERR_GENERR; break; default: err = pdu->errstat; } ret = 0; for (request = requests, i = 1; request; request = request->next, i++) { if (i == pdu->errindex) { /* * Mark this varbind as the one generating the error. * Note that the AgentX errindex may not match the * position in the original SNMP PDU (request->index) */ netsnmp_set_request_error(cache->reqinfo, request, err); ret = 1; } request->delegated = REQUEST_IS_NOT_DELEGATED; } if (!ret) { /* * ack, unknown, mark the first one */ netsnmp_set_request_error(cache->reqinfo, requests, SNMP_ERR_GENERR); } netsnmp_free_delegated_cache(cache); DEBUGMSGTL(("agentx/master", "end error branch\n")); return 1; } else if (cache->reqinfo->mode == MODE_GET || cache->reqinfo->mode == MODE_GETNEXT || cache->reqinfo->mode == MODE_GETBULK) { /* * Replace varbinds for data request types, but not SETs. */ DEBUGMSGTL(("agentx/master", "agentx_got_response() beginning...\n")); for (var = pdu->variables, request = requests; request && var; request = request->next, var = var->next_variable) { /* * Otherwise, process successful requests */ DEBUGMSGTL(("agentx/master", " handle_agentx_response: processing: ")); DEBUGMSGOID(("agentx/master", var->name, var->name_length)); DEBUGMSG(("agentx/master", "\n")); if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_VERBOSE)) { DEBUGMSGTL(("agentx/master", " >> ")); DEBUGMSGVAR(("agentx/master", var)); DEBUGMSG(("agentx/master", "\n")); } /* * update the oid in the original request */ if (var->type != SNMP_ENDOFMIBVIEW) { snmp_set_var_typed_value(request->requestvb, var->type, var->val.string, var->val_len); snmp_set_var_objid(request->requestvb, var->name, var->name_length); } request->delegated = REQUEST_IS_NOT_DELEGATED; } if (request || var) { /* * ack, this is bad. The # of varbinds don't match and * there is no way to fix the problem */ snmp_log(LOG_ERR, "response to agentx request illegal. bailing out.\n"); netsnmp_set_request_error(cache->reqinfo, requests, SNMP_ERR_GENERR); } if (cache->reqinfo->mode == MODE_GETBULK) netsnmp_bulk_to_next_fix_requests(requests); } else { /* * mark set requests as handled */ for (request = requests; request; request = request->next) { request->delegated = REQUEST_IS_NOT_DELEGATED; } } DEBUGMSGTL(("agentx/master", "handle_agentx_response() finishing...\n")); netsnmp_free_delegated_cache(cache); return 1; } /* * * AgentX State diagram. [mode] = internal mode it's mapped from: * * TESTSET -success-> COMMIT -success-> CLEANUP * [RESERVE1] [ACTION] [COMMIT] * | | * | \--failure-> UNDO * | [UNDO] * | * --failure-> CLEANUP * [FREE] */ int agentx_master_handler(netsnmp_mib_handler *handler, netsnmp_handler_registration *reginfo, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests) { netsnmp_session *ax_session = (netsnmp_session *) handler->myvoid; netsnmp_request_info *request = requests; netsnmp_pdu *pdu; void *cb_data; int result; DEBUGMSGTL(("agentx/master", "agentx master handler starting, mode = 0x%02x\n", reqinfo->mode)); if (!ax_session) { netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR); return SNMP_ERR_NOERROR; } /* * build a new pdu based on the pdu type coming in */ switch (reqinfo->mode) { case MODE_GET: pdu = snmp_pdu_create(AGENTX_MSG_GET); break; case MODE_GETNEXT: pdu = snmp_pdu_create(AGENTX_MSG_GETNEXT); break; case MODE_GETBULK: /* WWWXXX */ pdu = snmp_pdu_create(AGENTX_MSG_GETNEXT); break; #ifndef NETSNMP_NO_WRITE_SUPPORT case MODE_SET_RESERVE1: pdu = snmp_pdu_create(AGENTX_MSG_TESTSET); break; case MODE_SET_RESERVE2: /* * don't do anything here for AgentX. Assume all is fine * and go on since AgentX only has one test phase. */ return SNMP_ERR_NOERROR; case MODE_SET_ACTION: pdu = snmp_pdu_create(AGENTX_MSG_COMMITSET); break; case MODE_SET_UNDO: pdu = snmp_pdu_create(AGENTX_MSG_UNDOSET); break; case MODE_SET_COMMIT: case MODE_SET_FREE: pdu = snmp_pdu_create(AGENTX_MSG_CLEANUPSET); break; #endif /* !NETSNMP_NO_WRITE_SUPPORT */ default: snmp_log(LOG_WARNING, "unsupported mode for agentx/master called\n"); return SNMP_ERR_NOERROR; } if (!pdu) { netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR); return SNMP_ERR_NOERROR; } pdu->version = AGENTX_VERSION_1; pdu->reqid = snmp_get_next_transid(); pdu->transid = reqinfo->asp->pdu->transid; pdu->sessid = ax_session->subsession->sessid; if (reginfo->contextName) { pdu->community = (u_char *) strdup(reginfo->contextName); pdu->community_len = strlen(reginfo->contextName); pdu->flags |= AGENTX_MSG_FLAG_NON_DEFAULT_CONTEXT; } if (ax_session->subsession->flags & AGENTX_MSG_FLAG_NETWORK_BYTE_ORDER) pdu->flags |= AGENTX_MSG_FLAG_NETWORK_BYTE_ORDER; while (request) { size_t nlen = request->requestvb->name_length; oid *nptr = request->requestvb->name; DEBUGMSGTL(("agentx/master","request for variable (")); DEBUGMSGOID(("agentx/master", nptr, nlen)); DEBUGMSG(("agentx/master", ")\n")); /* * loop through all the requests and create agentx ones out of them */ if (reqinfo->mode == MODE_GETNEXT || reqinfo->mode == MODE_GETBULK) { if (snmp_oid_compare(nptr, nlen, request->subtree->start_a, request->subtree->start_len) < 0) { DEBUGMSGTL(("agentx/master","inexact request preceding region (")); DEBUGMSGOID(("agentx/master", request->subtree->start_a, request->subtree->start_len)); DEBUGMSG(("agentx/master", ")\n")); nptr = request->subtree->start_a; nlen = request->subtree->start_len; request->inclusive = 1; } if (request->inclusive) { DEBUGMSGTL(("agentx/master", "INCLUSIVE varbind ")); DEBUGMSGOID(("agentx/master", nptr, nlen)); DEBUGMSG(("agentx/master", " scoped to ")); DEBUGMSGOID(("agentx/master", request->range_end, request->range_end_len)); DEBUGMSG(("agentx/master", "\n")); snmp_pdu_add_variable(pdu, nptr, nlen, ASN_PRIV_INCL_RANGE, (u_char *) request->range_end, request->range_end_len * sizeof(oid)); request->inclusive = 0; } else { DEBUGMSGTL(("agentx/master", "EXCLUSIVE varbind ")); DEBUGMSGOID(("agentx/master", nptr, nlen)); DEBUGMSG(("agentx/master", " scoped to ")); DEBUGMSGOID(("agentx/master", request->range_end, request->range_end_len)); DEBUGMSG(("agentx/master", "\n")); snmp_pdu_add_variable(pdu, nptr, nlen, ASN_PRIV_EXCL_RANGE, (u_char *) request->range_end, request->range_end_len * sizeof(oid)); } } else { snmp_pdu_add_variable(pdu, request->requestvb->name, request->requestvb->name_length, request->requestvb->type, request->requestvb->val.string, request->requestvb->val_len); } /* * mark the request as delayed */ if (pdu->command != AGENTX_MSG_CLEANUPSET) request->delegated = REQUEST_IS_DELEGATED; else request->delegated = REQUEST_IS_NOT_DELEGATED; /* * next... */ request = request->next; } /* * When the master sends a CleanupSet PDU, it will never get a response * back from the subagent. So we shouldn't allocate the * netsnmp_delegated_cache structure in this case. */ if (pdu->command != AGENTX_MSG_CLEANUPSET) cb_data = netsnmp_create_delegated_cache(handler, reginfo, reqinfo, requests, (void *) ax_session); else cb_data = NULL; /* * send the requests out. */ DEBUGMSGTL(("agentx/master", "sending pdu (req=0x%x,trans=0x%x,sess=0x%x)\n", (unsigned)pdu->reqid, (unsigned)pdu->transid, (unsigned)pdu->sessid)); result = snmp_async_send(ax_session, pdu, agentx_got_response, cb_data); if (result == 0) { snmp_free_pdu(pdu); if (cb_data) netsnmp_free_delegated_cache((netsnmp_delegated_cache*) cb_data); } return SNMP_ERR_NOERROR; }