diff options
Diffstat (limited to '')
-rw-r--r-- | nsock/src/nsock_event.c | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/nsock/src/nsock_event.c b/nsock/src/nsock_event.c new file mode 100644 index 0000000..bfc489b --- /dev/null +++ b/nsock/src/nsock_event.c @@ -0,0 +1,538 @@ +/*************************************************************************** + * nsock_event.c -- Functions dealing with nsock_events (and their * + * struct nevent internal representation. An event is created when you do * + * various calls (for reading, writing, connecting, timers, etc) and is * + * provided back to you in the callback when the call completes or * + * fails. It is automatically destroyed after the callback returns * + * * + ***********************IMPORTANT NSOCK LICENSE TERMS*********************** + * + * The nsock parallel socket event library is (C) 1999-2023 Nmap Software LLC + * This library is free software; you may redistribute and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; Version 2. This guarantees your right to use, modify, and + * redistribute this software under certain conditions. If this license is + * unacceptable to you, Nmap Software LLC may be willing to sell alternative + * licenses (contact sales@nmap.com ). + * + * As a special exception to the GPL terms, Nmap Software LLC grants permission + * to link the code of this program with any version of the OpenSSL library + * which is distributed under a license identical to that listed in the included + * docs/licenses/OpenSSL.txt file, and distribute linked combinations including + * the two. You must obey the GNU GPL in all respects for all of the code used + * other than OpenSSL. If you modify this file, you may extend this exception to + * your version of the file, but you are not obligated to do so. + * + * If you received these files with a written license agreement stating terms + * other than the (GPL) terms above, then that alternative license agreement + * takes precedence over this comment. + * + * Source is provided to this software because we believe users have a right to + * know exactly what a program is going to do before they run it. This also + * allows you to audit the software for security holes. + * + * Source code also allows you to port Nmap to new platforms, fix bugs, and add + * new features. You are highly encouraged to send your changes to the + * dev@nmap.org mailing list for possible incorporation into the main + * distribution. By sending these changes to Fyodor or one of the Insecure.Org + * development mailing lists, or checking them into the Nmap source code + * repository, it is understood (unless you specify otherwise) that you are + * offering the Nmap Project (Nmap Software LLC) the unlimited, non-exclusive + * right to reuse, modify, and relicense the code. Nmap will always be available + * Open Source, but this is important because the inability to relicense code + * has caused devastating problems for other Free Software projects (such as KDE + * and NASM). We also occasionally relicense the code to third parties as + * discussed above. If you wish to specify special license conditions of your + * contributions, just say so when you send them. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License v2.0 for more + * details (http://www.gnu.org/licenses/gpl-2.0.html). + * + ***************************************************************************/ + +/* $Id$ */ + +#include "nsock_internal.h" +#include "nsock_log.h" +#include "gh_list.h" + +#if HAVE_PCAP +#include "nsock_pcap.h" +#endif + +#include <string.h> + +extern struct timeval nsock_tod; + +/* Find the type of an event that spawned a callback */ +enum nse_type nse_type(nsock_event nse) { + struct nevent *me = (struct nevent *)nse; + return me->type; +} + +enum nse_status nse_status(nsock_event nse) { + struct nevent *me = (struct nevent *)nse; + return me->status; +} + +int nse_eof(nsock_event nse) { + struct nevent *me = (struct nevent *)nse; + return me->eof; +} + +/* Obtains the nsock_iod (see below) associated with the event. Note that + * some events (such as timers) don't have an nsock_iod associated with them */ +nsock_iod nse_iod(nsock_event ms_event) { + struct nevent *nse = (struct nevent *)ms_event; + return (nsock_iod) nse->iod; +} + +/* This next function returns the errno style error code -- which is only valid + * if the status is NSE_STATUS_ERROR */ +int nse_errorcode(nsock_event nse) { + struct nevent *me = (struct nevent *)nse; + return me->errnum; +} + +/* Every event has an ID which will be unique throughout the program's execution + * unless you use (literally) billions of them */ +nsock_event_id nse_id(nsock_event nse) { + struct nevent *me = (struct nevent *)nse; + return me->id; +} + +/* If you did a read request, and the result was STATUS_SUCCESS, this function + * provides the buffer that was read in as well as the number of chars read. + * The buffer should not be modified or free'd */ +char *nse_readbuf(nsock_event nse, int *nbytes) { + struct nevent *me = (struct nevent *)nse; + + if (nbytes) + *nbytes = fs_length(&(me->iobuf)); + return fs_str(&(me->iobuf)); +} + +static void first_ev_next(struct nevent *nse, gh_lnode_t **first, int nodeq2) { + if (!first || !*first) + return; + + if (&nse->nodeq_io == *first || &nse->nodeq_pcap == *first) { + gh_lnode_t *next; + + next = gh_lnode_next(*first); + if (next) { + struct nevent *newevent; + + if (nodeq2) + newevent = lnode_nevent2(next); + else + newevent = lnode_nevent(next); + + if (newevent->iod == nse->iod) + *first = next; + else + *first = NULL; + } else { + *first = NULL; + } + } +} + +void update_first_events(struct nevent *nse) { + switch (get_event_id_type(nse->id)) { + case NSE_TYPE_CONNECT: + case NSE_TYPE_CONNECT_SSL: + first_ev_next(nse, &nse->iod->first_connect, 0); + break; + + case NSE_TYPE_READ: + first_ev_next(nse, &nse->iod->first_read, 0); + break; + + case NSE_TYPE_WRITE: + first_ev_next(nse, &nse->iod->first_write, 0); + break; + +#if HAVE_PCAP + case NSE_TYPE_PCAP_READ: + first_ev_next(nse, &nse->iod->first_read, 0); + first_ev_next(nse, &nse->iod->first_pcap_read, 1); + break; +#endif + + case NSE_TYPE_TIMER: + /* nothing to do */ + break; + + default: + fatal("Bogus event type in update_first_events"); + break; + } +} + +/* Cancel an event (such as a timer or read request). If notify is nonzero, the + * requester will be sent an event CANCELLED status back to the given handler. + * But in some cases there is no need to do this (like if the function deleting + * it is the one which created it), in which case 0 can be passed to skip the + * step. This function returns zero if the event is not found, nonzero + * otherwise. */ +int nsock_event_cancel(nsock_pool ms_pool, nsock_event_id id, int notify) { + struct npool *nsp = (struct npool *)ms_pool; + enum nse_type type; + unsigned int i; + gh_list_t *event_list = NULL, *event_list2 = NULL; + gh_lnode_t *current, *next; + struct nevent *nse = NULL; + + assert(nsp); + + type = get_event_id_type(id); + nsock_log_info("Event #%li (type %s) cancelled", id, nse_type2str(type)); + + /* First we figure out what list it is in */ + switch (type) { + case NSE_TYPE_CONNECT: + case NSE_TYPE_CONNECT_SSL: + event_list = &nsp->connect_events; + break; + + case NSE_TYPE_READ: + event_list = &nsp->read_events; + break; + + case NSE_TYPE_WRITE: + event_list = &nsp->write_events; + break; + + case NSE_TYPE_TIMER: + for (i = 0; i < gh_heap_count(&nsp->expirables); i++) { + gh_hnode_t *hnode; + + hnode = gh_heap_find(&nsp->expirables, i); + nse = container_of(hnode, struct nevent, expire); + if (nse->id == id) + return nevent_delete(nsp, nse, NULL, NULL, notify); + } + return 0; + +#if HAVE_PCAP + case NSE_TYPE_PCAP_READ: + event_list = &nsp->read_events; + event_list2 = &nsp->pcap_read_events; + break; +#endif + + default: + fatal("Bogus event type in nsock_event_cancel"); break; + } + + /* Now we try to find the event in the list */ + for (current = gh_list_first_elem(event_list); current != NULL; current = next) { + next = gh_lnode_next(current); + nse = lnode_nevent(current); + if (nse->id == id) + break; + } + + if (current == NULL && event_list2) { + event_list = event_list2; + for (current = gh_list_first_elem(event_list); current != NULL; current = next) { + next = gh_lnode_next(current); + nse = lnode_nevent2(current); + if (nse->id == id) + break; + } + } + if (current == NULL) + return 0; + + return nevent_delete(nsp, nse, event_list, current, notify); +} + +/* An internal function for cancelling an event when you already have a pointer + * to the struct nevent (use nsock_event_cancel if you just have an ID). The + * event_list passed in should correspond to the type of the event. For example, + * with NSE_TYPE_READ, you would pass in &nsp->read_events;. elem is the list + * element in event_list which holds the event. Pass a nonzero for notify if + * you want the program owning the event to be notified that it has been + * cancelled */ +int nevent_delete(struct npool *nsp, struct nevent *nse, gh_list_t *event_list, + gh_lnode_t *elem, int notify) { + if (nse->event_done) { + /* This event has already been marked for death somewhere else -- it will be + * gone soon (and if we try to kill it now all hell will break loose due to + * reentrancy. */ + return 0; + } + + nsock_log_info("%s on event #%li (type %s)", __func__, nse->id, + nse_type2str(nse->type)); + + /* Now that we found the event... we go through the motions of cleanly + * cancelling it */ + switch (nse->type) { + case NSE_TYPE_CONNECT: + case NSE_TYPE_CONNECT_SSL: + handle_connect_result(nsp, nse, NSE_STATUS_CANCELLED); + break; + + case NSE_TYPE_READ: + handle_read_result(nsp, nse, NSE_STATUS_CANCELLED); + break; + + case NSE_TYPE_WRITE: + handle_write_result(nsp, nse, NSE_STATUS_CANCELLED); + break; + + case NSE_TYPE_TIMER: + handle_timer_result(nsp, nse, NSE_STATUS_CANCELLED); + break; + +#if HAVE_PCAP + case NSE_TYPE_PCAP_READ: + handle_pcap_read_result(nsp, nse, NSE_STATUS_CANCELLED); + break; +#endif + + default: + fatal("Invalid nsock event type (%d)", nse->type); + } + + assert(nse->event_done); + + if (nse->timeout.tv_sec) + gh_heap_remove(&nsp->expirables, &nse->expire); + + if (event_list) { + update_first_events(nse); + gh_list_remove(event_list, elem); + } + + gh_list_append(&nsp->free_events, &nse->nodeq_io); + + nsock_log_debug_all("NSE #%lu: Removing event from list", nse->id); + +#if HAVE_PCAP +#if PCAP_BSD_SELECT_HACK + if (nse->type == NSE_TYPE_PCAP_READ) { + nsock_log_debug_all("PCAP NSE #%lu: CANCEL TEST pcap=%p read=%p curr=%p sd=%i", + nse->id, &nsp->pcap_read_events, &nsp->read_events, + event_list,((mspcap *)nse->iod->pcap)->pcap_desc); + + /* If event occurred, and we're in BSD_HACK mode, then this event was added to + * two queues. read_event and pcap_read_event Of course we should + * destroy it only once. I assume we're now in read_event, so just unlink + * this event from pcap_read_event */ + if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0 && event_list == &nsp->read_events) { + /* event is done, list is read_events and we're in BSD_HACK mode. So unlink + * event from pcap_read_events */ + gh_list_remove(&nsp->pcap_read_events, &nse->nodeq_pcap); + nsock_log_debug_all("PCAP NSE #%lu: Removing event from PCAP_READ_EVENTS", nse->id); + } + + if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0 && event_list == &nsp->pcap_read_events) { + /* event is done, list is read_events and we're in BSD_HACK mode. + * So unlink event from read_events */ + gh_list_remove(&nsp->read_events, &nse->nodeq_io); + + nsock_log_debug_all("PCAP NSE #%lu: Removing event from READ_EVENTS", nse->id); + } + } +#endif +#endif + event_dispatch_and_delete(nsp, nse, notify); + return 1; +} + +/* Adjust various statistics, dispatches the event handler (if notify is + * nonzero) and then deletes the event. This function does NOT delete the event + * from any lists it might be on (eg nsp->read_list etc.) nse->event_done + * MUST be true when you call this */ +void event_dispatch_and_delete(struct npool *nsp, struct nevent *nse, int notify) { + assert(nsp); + assert(nse); + + assert(nse->event_done); + + nsp->events_pending--; + assert(nsp->events_pending >= 0); + + if (nse->iod) { + nse->iod->events_pending--; + assert(nse->iod->events_pending >= 0); + } + + if (notify) { + nsock_trace_handler_callback(nsp, nse); + nse->handler(nsp, nse, nse->userdata); + } + + /* FIXME: We should be updating stats here ... */ + + /* Now we clobber the event ... */ + event_delete(nsp, nse); +} + +/* OK -- the idea is that we want the type included in the rightmost two bits + * and the serial number in the leftmost 30 or 62. But we also want to insure a + * correct wrap-around in the case of an obscene number of event. One + * definition of a "correct" wraparound is that it goes from the highest number + * back to one (not zero) because we don't want event numbers to ever be zero. + * */ +nsock_event_id get_new_event_id(struct npool *ms, enum nse_type type) { + int type_code = (int)type; + unsigned long serial = ms->next_event_serial++; + unsigned long max_serial_allowed; + int shiftbits; + + assert(type < NSE_TYPE_MAX); + + shiftbits = sizeof(nsock_event_id) * 8 - TYPE_CODE_NUM_BITS; + max_serial_allowed = ((unsigned long)1 << shiftbits) - 1; + if (serial == max_serial_allowed) { + /* then the next serial will be one because 0 is forbidden */ + ms->next_event_serial = 1; + } + + return (serial << TYPE_CODE_NUM_BITS) | type_code; +} + +/* Take an event ID and return the type (NSE_TYPE_CONNECT, etc */ +enum nse_type get_event_id_type(nsock_event_id event_id) { + return (enum nse_type)((event_id & ((1 << TYPE_CODE_NUM_BITS) - 1))); +} + +/* Create a new event structure -- must be deleted later with event_delete, + * unless it returns NULL (failure). NULL can be passed in for the struct niod + * and the userdata if not available */ +struct nevent *event_new(struct npool *nsp, enum nse_type type, + struct niod *iod, int timeout_msecs, + nsock_ev_handler handler, void *userdata) { + struct nevent *nse; + gh_lnode_t *lnode; + + /* Bring us up to date for the timeout calculation. */ + gettimeofday(&nsock_tod, NULL); + + if (iod) { + iod->events_pending++; + assert(iod->state != NSIOD_STATE_DELETED); + } + + /* First we check if one is available from the free list ... */ + lnode = gh_list_pop(&nsp->free_events); + if (!lnode) + nse = (struct nevent *)safe_malloc(sizeof(*nse)); + else + nse = lnode_nevent(lnode); + + memset(nse, 0, sizeof(*nse)); + + nse->id = get_new_event_id(nsp, type); + nse->type = type; + nse->status = NSE_STATUS_NONE; + gh_hnode_invalidate(&nse->expire); +#if HAVE_OPENSSL + nse->sslinfo.ssl_desire = SSL_ERROR_NONE; +#endif + + if (type == NSE_TYPE_READ || type == NSE_TYPE_WRITE) + filespace_init(&(nse->iobuf), 1024); + +#if HAVE_PCAP + if (type == NSE_TYPE_PCAP_READ) { + mspcap *mp; + int sz; + + assert(iod != NULL); + mp = (mspcap *)iod->pcap; + assert(mp); + + sz = mp->snaplen+1 + sizeof(nsock_pcap); + filespace_init(&(nse->iobuf), sz); + } +#endif + + if (timeout_msecs != -1) { + assert(timeout_msecs >= 0); + TIMEVAL_MSEC_ADD(nse->timeout, nsock_tod, timeout_msecs); + } + + nse->iod = iod; + nse->handler = handler; + nse->userdata = userdata; + + if (nse->iod == NULL) + nsock_log_debug("%s (IOD #NULL) (EID #%li)", __func__, nse->id); + else + nsock_log_debug("%s (IOD #%li) (EID #%li)", __func__, nse->iod->id, + nse->id); + return nse; +} + +/* Free an struct nevent which was allocated with event_new, including all internal + * resources. Note -- we assume that nse->iod->events_pending (if it exists) + * has ALREADY been decremented (done during event_dispatch_and_delete) -- so + * remember to do this if you call event_delete() directly */ +void event_delete(struct npool *nsp, struct nevent *nse) { + if (nse->iod == NULL) + nsock_log_debug("%s (IOD #NULL) (EID #%li)", __func__, nse->id); + else + nsock_log_debug("%s (IOD #%li) (EID #%li)", __func__, nse->iod->id, nse->id); + + /* First free the IOBuf inside it if necessary */ + if (nse->type == NSE_TYPE_READ || nse->type == NSE_TYPE_WRITE) { + fs_free(&nse->iobuf); + } + #if HAVE_PCAP + if (nse->type == NSE_TYPE_PCAP_READ) { + fs_free(&nse->iobuf); + nsock_log_debug_all("PCAP removed %lu", nse->id); + } + #endif + + /* Now we add the event back into the free pool */ + nse->event_done = 1; +} + + +/* Takes an nse_type (as returned by nse_type() and returns a static string name + * that you can use for printing, etc. */ +const char *nse_type2str(enum nse_type type) { + switch (type) { + case NSE_TYPE_CONNECT: return "CONNECT"; + case NSE_TYPE_CONNECT_SSL: return "SSL-CONNECT"; + case NSE_TYPE_READ: return "READ"; + case NSE_TYPE_WRITE: return "WRITE"; + case NSE_TYPE_TIMER: return "TIMER"; + case NSE_TYPE_PCAP_READ: return "READ-PCAP"; + default: + return "UNKNOWN!"; + } +} + +/* Takes an nse_status (as returned by nse_status() and returns a static string + * name that you can use for printing, etc. */ +const char *nse_status2str(enum nse_status status) { + switch (status) { + case NSE_STATUS_NONE: return "NONE"; + case NSE_STATUS_SUCCESS: return "SUCCESS"; + case NSE_STATUS_ERROR: return "ERROR"; + case NSE_STATUS_TIMEOUT: return "TIMEOUT"; + case NSE_STATUS_CANCELLED: return "CANCELLED"; + case NSE_STATUS_KILL: return "KILL"; + case NSE_STATUS_EOF: return "EOF"; + case NSE_STATUS_PROXYERROR: return "PROXY ERROR"; + default: + return "UNKNOWN!"; + } +} + +int event_timedout(struct nevent *nse) { + if (nse->event_done) + return 0; + + return (nse->timeout.tv_sec && !TIMEVAL_AFTER(nse->timeout, nsock_tod)); +} |