| #include <net-snmp/net-snmp-config.h> |
| |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <ctype.h> |
| #include <errno.h> |
| |
| #ifdef WIN32 |
| #include <net-snmp/library/winpipe.h> |
| #endif |
| #if HAVE_STRING_H |
| #include <string.h> |
| #else |
| #include <strings.h> |
| #endif |
| #if HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #if HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #if HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| #if HAVE_SYS_UN_H |
| #include <sys/un.h> |
| #endif |
| #if HAVE_IO_H |
| #include <io.h> |
| #endif |
| #if HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| |
| #if HAVE_DMALLOC_H |
| #include <dmalloc.h> |
| #endif |
| |
| #include <net-snmp/types.h> |
| #include <net-snmp/output_api.h> |
| #include <net-snmp/config_api.h> |
| #include <net-snmp/utilities.h> |
| |
| #include <net-snmp/library/snmp_transport.h> |
| #include <net-snmp/library/snmpUnixDomain.h> |
| #include <net-snmp/library/snmp_api.h> |
| #include <net-snmp/library/snmp_client.h> |
| #include <net-snmp/library/snmpCallbackDomain.h> |
| |
| #ifndef NETSNMP_STREAM_QUEUE_LEN |
| #define NETSNMP_STREAM_QUEUE_LEN 5 |
| #endif |
| |
| #ifdef NETSNMP_TRANSPORT_CALLBACK_DOMAIN |
| |
| static netsnmp_transport_list *trlist = NULL; |
| |
| static int callback_count = 0; |
| |
| typedef struct callback_hack_s { |
| void *orig_transport_data; |
| netsnmp_pdu *pdu; |
| } callback_hack; |
| |
| typedef struct callback_queue_s { |
| int callback_num; |
| netsnmp_callback_pass *item; |
| struct callback_queue_s *next, *prev; |
| } callback_queue; |
| |
| callback_queue *thequeue; |
| |
| static netsnmp_transport * |
| find_transport_from_callback_num(int num) |
| { |
| static netsnmp_transport_list *ptr; |
| for (ptr = trlist; ptr; ptr = ptr->next) |
| if (((netsnmp_callback_info *) ptr->transport->data)-> |
| callback_num == num) |
| return ptr->transport; |
| return NULL; |
| } |
| |
| static void |
| callback_debug_pdu(const char *ourstring, netsnmp_pdu *pdu) |
| { |
| netsnmp_variable_list *vb; |
| int i = 1; |
| DEBUGMSGTL((ourstring, |
| "PDU: command = %d, errstat = %d, errindex = %d\n", |
| pdu->command, pdu->errstat, pdu->errindex)); |
| for (vb = pdu->variables; vb; vb = vb->next_variable) { |
| DEBUGMSGTL((ourstring, " var %d:", i++)); |
| DEBUGMSGVAR((ourstring, vb)); |
| DEBUGMSG((ourstring, "\n")); |
| } |
| } |
| |
| void |
| callback_push_queue(int num, netsnmp_callback_pass *item) |
| { |
| callback_queue *newitem = SNMP_MALLOC_TYPEDEF(callback_queue); |
| callback_queue *ptr; |
| |
| newitem->callback_num = num; |
| newitem->item = item; |
| if (thequeue) { |
| for (ptr = thequeue; ptr && ptr->next; ptr = ptr->next) { |
| } |
| ptr->next = newitem; |
| newitem->prev = ptr; |
| } else { |
| thequeue = newitem; |
| } |
| DEBUGIF("dump_send_callback_transport") { |
| callback_debug_pdu("dump_send_callback_transport", item->pdu); |
| } |
| } |
| |
| netsnmp_callback_pass * |
| callback_pop_queue(int num) |
| { |
| netsnmp_callback_pass *cp; |
| callback_queue *ptr; |
| |
| for (ptr = thequeue; ptr; ptr = ptr->next) { |
| if (ptr->callback_num == num) { |
| if (ptr->prev) { |
| ptr->prev->next = ptr->next; |
| } else { |
| thequeue = ptr->next; |
| } |
| if (ptr->next) { |
| ptr->next->prev = ptr->prev; |
| } |
| cp = ptr->item; |
| SNMP_FREE(ptr); |
| DEBUGIF("dump_recv_callback_transport") { |
| callback_debug_pdu("dump_recv_callback_transport", |
| cp->pdu); |
| } |
| return cp; |
| } |
| } |
| return NULL; |
| } |
| |
| /* |
| * Return a string representing the address in data, or else the "far end" |
| * address if data is NULL. |
| */ |
| |
| char * |
| netsnmp_callback_fmtaddr(netsnmp_transport *t, void *data, int len) |
| { |
| char buf[SPRINT_MAX_LEN]; |
| netsnmp_callback_info *mystuff; |
| |
| if (!t) |
| return strdup("callback: unknown"); |
| |
| mystuff = (netsnmp_callback_info *) t->data; |
| |
| if (!mystuff) |
| return strdup("callback: unknown"); |
| |
| snprintf(buf, SPRINT_MAX_LEN, "callback: %d on fd %d", |
| mystuff->callback_num, mystuff->pipefds[0]); |
| return strdup(buf); |
| } |
| |
| |
| |
| /* |
| * You can write something into opaque that will subsequently get passed back |
| * to your send function if you like. For instance, you might want to |
| * remember where a PDU came from, so that you can send a reply there... |
| */ |
| |
| int |
| netsnmp_callback_recv(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int rc = -1; |
| char newbuf[1]; |
| netsnmp_callback_info *mystuff = (netsnmp_callback_info *) t->data; |
| |
| DEBUGMSGTL(("transport_callback", "hook_recv enter\n")); |
| |
| while (rc < 0) { |
| #ifdef WIN32 |
| rc = recvfrom(mystuff->pipefds[0], newbuf, 1, 0, NULL, 0); |
| #else |
| rc = read(mystuff->pipefds[0], newbuf, 1); |
| #endif |
| if (rc < 0 && errno != EINTR) { |
| break; |
| } |
| } |
| if (rc > 0) |
| memset(buf, 0, rc); |
| |
| if (mystuff->linkedto) { |
| /* |
| * we're the client. We don't need to do anything. |
| */ |
| } else { |
| /* |
| * malloc the space here, but it's filled in by |
| * snmp_callback_created_pdu() below |
| */ |
| int *returnnum = (int *) calloc(1, sizeof(int)); |
| *opaque = returnnum; |
| *olength = sizeof(int); |
| } |
| DEBUGMSGTL(("transport_callback", "hook_recv exit\n")); |
| return rc; |
| } |
| |
| |
| |
| int |
| netsnmp_callback_send(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int from, rc = -1; |
| netsnmp_callback_info *mystuff = (netsnmp_callback_info *) t->data; |
| netsnmp_callback_pass *cp; |
| |
| /* |
| * extract the pdu from the hacked buffer |
| */ |
| netsnmp_transport *other_side; |
| callback_hack *ch = (callback_hack *) * opaque; |
| netsnmp_pdu *pdu = ch->pdu; |
| *opaque = ch->orig_transport_data; |
| SNMP_FREE(ch); |
| |
| DEBUGMSGTL(("transport_callback", "hook_send enter\n")); |
| |
| cp = SNMP_MALLOC_TYPEDEF(netsnmp_callback_pass); |
| if (!cp) |
| return -1; |
| |
| cp->pdu = snmp_clone_pdu(pdu); |
| if (cp->pdu->transport_data) { |
| /* |
| * not needed and not properly freed later |
| */ |
| SNMP_FREE(cp->pdu->transport_data); |
| } |
| |
| if (cp->pdu->flags & UCD_MSG_FLAG_EXPECT_RESPONSE) |
| cp->pdu->flags ^= UCD_MSG_FLAG_EXPECT_RESPONSE; |
| |
| /* |
| * push the sent pdu onto the stack |
| */ |
| /* |
| * AND send a bogus byte to the remote callback receiver's pipe |
| */ |
| if (mystuff->linkedto) { |
| /* |
| * we're the client, send it to the parent |
| */ |
| cp->return_transport_num = mystuff->callback_num; |
| |
| other_side = find_transport_from_callback_num(mystuff->linkedto); |
| if (!other_side) { |
| snmp_free_pdu(cp->pdu); |
| SNMP_FREE(cp); |
| return -1; |
| } |
| |
| while (rc < 0) { |
| #ifdef WIN32 |
| rc = sendto(((netsnmp_callback_info*) other_side->data)->pipefds[1], " ", 1, 0, NULL, 0); |
| #else |
| rc = write(((netsnmp_callback_info *)other_side->data)->pipefds[1], |
| " ", 1); |
| #endif |
| if (rc < 0 && errno != EINTR) { |
| break; |
| } |
| } |
| callback_push_queue(mystuff->linkedto, cp); |
| /* |
| * we don't need the transport data any more |
| */ |
| if (*opaque) { |
| SNMP_FREE(*opaque); |
| *opaque = NULL; |
| } |
| } else { |
| /* |
| * we're the server, send it to the person that sent us the request |
| */ |
| from = **((int **) opaque); |
| /* |
| * we don't need the transport data any more |
| */ |
| if (*opaque) { |
| SNMP_FREE(*opaque); |
| *opaque = NULL; |
| } |
| other_side = find_transport_from_callback_num(from); |
| if (!other_side) { |
| snmp_free_pdu(cp->pdu); |
| SNMP_FREE(cp); |
| return -1; |
| } |
| while (rc < 0) { |
| #ifdef WIN32 |
| rc = sendto(((netsnmp_callback_info*) other_side->data)->pipefds[1], " ", 1, 0, NULL, 0); |
| #else |
| rc = write(((netsnmp_callback_info *)other_side->data)->pipefds[1], |
| " ", 1); |
| #endif |
| if (rc < 0 && errno != EINTR) { |
| break; |
| } |
| } |
| callback_push_queue(from, cp); |
| } |
| |
| DEBUGMSGTL(("transport_callback", "hook_send exit\n")); |
| return 0; |
| } |
| |
| |
| |
| int |
| netsnmp_callback_close(netsnmp_transport *t) |
| { |
| int rc; |
| netsnmp_callback_info *mystuff = (netsnmp_callback_info *) t->data; |
| DEBUGMSGTL(("transport_callback", "hook_close enter\n")); |
| |
| #ifdef WIN32 |
| rc = closesocket(mystuff->pipefds[0]); |
| rc |= closesocket(mystuff->pipefds[1]); |
| #else |
| rc = close(mystuff->pipefds[0]); |
| rc |= close(mystuff->pipefds[1]); |
| #endif |
| |
| rc |= netsnmp_transport_remove_from_list(&trlist, t); |
| |
| DEBUGMSGTL(("transport_callback", "hook_close exit\n")); |
| return rc; |
| } |
| |
| |
| |
| int |
| netsnmp_callback_accept(netsnmp_transport *t) |
| { |
| DEBUGMSGTL(("transport_callback", "hook_accept enter\n")); |
| DEBUGMSGTL(("transport_callback", "hook_accept exit\n")); |
| return 0; |
| } |
| |
| |
| |
| /* |
| * Open a Callback-domain transport for SNMP. Local is TRUE if addr |
| * is the local address to bind to (i.e. this is a server-type |
| * session); otherwise addr is the remote address to send things to |
| * (and we make up a temporary name for the local end of the |
| * connection). |
| */ |
| |
| netsnmp_transport * |
| netsnmp_callback_transport(int to) |
| { |
| |
| netsnmp_transport *t = NULL; |
| netsnmp_callback_info *mydata; |
| int rc; |
| |
| /* |
| * transport |
| */ |
| t = SNMP_MALLOC_TYPEDEF(netsnmp_transport); |
| if (!t) |
| return NULL; |
| |
| /* |
| * our stuff |
| */ |
| mydata = SNMP_MALLOC_TYPEDEF(netsnmp_callback_info); |
| mydata->linkedto = to; |
| mydata->callback_num = ++callback_count; |
| mydata->data = NULL; |
| t->data = mydata; |
| |
| #ifdef WIN32 |
| rc = create_winpipe_transport(mydata->pipefds); |
| #else |
| rc = pipe(mydata->pipefds); |
| #endif |
| t->sock = mydata->pipefds[0]; |
| |
| if (rc) { |
| SNMP_FREE(mydata); |
| SNMP_FREE(t); |
| return NULL; |
| } |
| |
| t->f_recv = netsnmp_callback_recv; |
| t->f_send = netsnmp_callback_send; |
| t->f_close = netsnmp_callback_close; |
| t->f_accept = netsnmp_callback_accept; |
| t->f_fmtaddr = netsnmp_callback_fmtaddr; |
| |
| netsnmp_transport_add_to_list(&trlist, t); |
| |
| if (to) |
| DEBUGMSGTL(("transport_callback", "initialized %d linked to %d\n", |
| mydata->callback_num, to)); |
| else |
| DEBUGMSGTL(("transport_callback", |
| "initialized master listening on %d\n", |
| mydata->callback_num)); |
| return t; |
| } |
| |
| int |
| netsnmp_callback_hook_parse(netsnmp_session * sp, |
| netsnmp_pdu *pdu, |
| u_char * packetptr, size_t len) |
| { |
| if (SNMP_MSG_RESPONSE == pdu->command || |
| SNMP_MSG_REPORT == pdu->command) |
| pdu->flags |= UCD_MSG_FLAG_RESPONSE_PDU; |
| else |
| pdu->flags &= (~UCD_MSG_FLAG_RESPONSE_PDU); |
| |
| return SNMP_ERR_NOERROR; |
| } |
| |
| int |
| netsnmp_callback_hook_build(netsnmp_session * sp, |
| netsnmp_pdu *pdu, u_char * ptk, size_t * len) |
| { |
| /* |
| * very gross hack, as this is passed later to the transport_send |
| * function |
| */ |
| callback_hack *ch = SNMP_MALLOC_TYPEDEF(callback_hack); |
| DEBUGMSGTL(("transport_callback", "hook_build enter\n")); |
| ch->pdu = pdu; |
| ch->orig_transport_data = pdu->transport_data; |
| pdu->transport_data = ch; |
| switch (pdu->command) { |
| case SNMP_MSG_GETBULK: |
| if (pdu->max_repetitions < 0) { |
| sp->s_snmp_errno = SNMPERR_BAD_REPETITIONS; |
| return -1; |
| } |
| if (pdu->non_repeaters < 0) { |
| sp->s_snmp_errno = SNMPERR_BAD_REPEATERS; |
| return -1; |
| } |
| break; |
| case SNMP_MSG_RESPONSE: |
| case SNMP_MSG_TRAP: |
| case SNMP_MSG_TRAP2: |
| pdu->flags &= (~UCD_MSG_FLAG_EXPECT_RESPONSE); |
| /* |
| * Fallthrough |
| */ |
| default: |
| if (pdu->errstat == SNMP_DEFAULT_ERRSTAT) |
| pdu->errstat = 0; |
| if (pdu->errindex == SNMP_DEFAULT_ERRINDEX) |
| pdu->errindex = 0; |
| break; |
| } |
| |
| /* |
| * Copy missing values from session defaults |
| */ |
| switch (pdu->version) { |
| #ifndef NETSNMP_DISABLE_SNMPV1 |
| case SNMP_VERSION_1: |
| #endif |
| #ifndef NETSNMP_DISABLE_SNMPV2C |
| case SNMP_VERSION_2c: |
| #endif |
| #if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C) |
| if (pdu->community_len == 0) { |
| if (sp->community_len == 0) { |
| sp->s_snmp_errno = SNMPERR_BAD_COMMUNITY; |
| return -1; |
| } |
| pdu->community = (u_char *) malloc(sp->community_len); |
| if (pdu->community == NULL) { |
| sp->s_snmp_errno = SNMPERR_MALLOC; |
| return -1; |
| } |
| memmove(pdu->community, |
| sp->community, sp->community_len); |
| pdu->community_len = sp->community_len; |
| } |
| break; |
| #endif |
| case SNMP_VERSION_3: |
| if (pdu->securityNameLen == 0) { |
| pdu->securityName = (char *)malloc(sp->securityNameLen); |
| if (pdu->securityName == NULL) { |
| sp->s_snmp_errno = SNMPERR_MALLOC; |
| return -1; |
| } |
| memmove(pdu->securityName, |
| sp->securityName, sp->securityNameLen); |
| pdu->securityNameLen = sp->securityNameLen; |
| } |
| if (pdu->securityModel == -1) |
| pdu->securityModel = sp->securityModel; |
| if (pdu->securityLevel == 0) |
| pdu->securityLevel = sp->securityLevel; |
| /* WHAT ELSE ?? */ |
| } |
| ptk[0] = 0; |
| *len = 1; |
| DEBUGMSGTL(("transport_callback", "hook_build exit\n")); |
| return 1; |
| } |
| |
| int |
| netsnmp_callback_check_packet(u_char * pkt, size_t len) |
| { |
| return 1; |
| } |
| |
| netsnmp_pdu * |
| netsnmp_callback_create_pdu(netsnmp_transport *transport, |
| void *opaque, size_t olength) |
| { |
| netsnmp_pdu *pdu; |
| netsnmp_callback_pass *cp = |
| callback_pop_queue(((netsnmp_callback_info *) transport->data)-> |
| callback_num); |
| if (!cp) |
| return NULL; |
| pdu = cp->pdu; |
| pdu->transport_data = opaque; |
| pdu->transport_data_length = olength; |
| if (opaque) /* if created, we're the server */ |
| *((int *) opaque) = cp->return_transport_num; |
| SNMP_FREE(cp); |
| return pdu; |
| } |
| |
| netsnmp_session * |
| netsnmp_callback_open(int attach_to, |
| int (*return_func) (int op, |
| netsnmp_session * session, |
| int reqid, netsnmp_pdu *pdu, |
| void *magic), |
| int (*fpre_parse) (netsnmp_session *, |
| struct netsnmp_transport_s *, |
| void *, int), |
| int (*fpost_parse) (netsnmp_session *, netsnmp_pdu *, |
| int)) |
| { |
| netsnmp_session callback_sess, *callback_ss; |
| netsnmp_transport *callback_tr; |
| |
| callback_tr = netsnmp_callback_transport(attach_to); |
| snmp_sess_init(&callback_sess); |
| callback_sess.callback = return_func; |
| if (attach_to) { |
| /* |
| * client |
| */ |
| /* |
| * trysess.community = (u_char *) callback_ss; |
| */ |
| } else { |
| callback_sess.isAuthoritative = SNMP_SESS_AUTHORITATIVE; |
| } |
| callback_sess.remote_port = 0; |
| callback_sess.retries = 0; |
| callback_sess.timeout = 30000000; |
| callback_sess.version = SNMP_DEFAULT_VERSION; /* (mostly) bogus */ |
| callback_ss = snmp_add_full(&callback_sess, callback_tr, |
| fpre_parse, |
| netsnmp_callback_hook_parse, fpost_parse, |
| netsnmp_callback_hook_build, |
| NULL, |
| netsnmp_callback_check_packet, |
| netsnmp_callback_create_pdu); |
| if (callback_ss) |
| callback_ss->local_port = |
| ((netsnmp_callback_info *) callback_tr->data)->callback_num; |
| return callback_ss; |
| } |
| |
| |
| |
| void |
| netsnmp_clear_callback_list(void) |
| { |
| |
| netsnmp_transport_list *list = trlist, *next = NULL; |
| netsnmp_transport *tr = NULL; |
| |
| DEBUGMSGTL(("callback_clear", "called netsnmp_callback_clear_list()\n")); |
| while (list != NULL) { |
| next = list->next; |
| tr = list->transport; |
| |
| if (tr != NULL) { |
| tr->f_close(tr); |
| netsnmp_transport_remove_from_list(&trlist, tr); |
| netsnmp_transport_free(tr); |
| } |
| list = next; |
| } |
| trlist = NULL; |
| |
| } |
| |
| #endif /* NETSNMP_TRANSPORT_CALLBACK_DOMAIN */ |