/***************************************************************************
 * nsock_iod.c -- This contains the functions relating to nsock_iod (and   *
 * its nsock internal manifestation -- nsockiod.  This is is similar to a  *
 * file descriptor in that you create it and then use it to initiate       *
 * connections, read/write data, etc.                                      *
 *                                                                         *
 ***********************IMPORTANT NSOCK LICENSE TERMS***********************
 *                                                                         *
 * The nsock parallel socket event library is (C) 1999-2017 Insecure.Com   *
 * 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, Insecure.Com LLC   *
 * may be willing to sell alternative licenses (contact                    *
 * sales@insecure.com ).                                                   *
 *                                                                         *
 * As a special exception to the GPL terms, Insecure.Com 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 (Insecure.Com 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.h"
#include "nsock_internal.h"
#include "nsock_log.h"
#include "gh_list.h"
#include "netutils.h"

#if HAVE_PCAP
#include "nsock_pcap.h"
#endif

#include <string.h>


/* nsock_iod is like a "file descriptor" for the nsock library. You use it to
 * request events. And here is how you create an nsock_iod. nsock_iod_new returns
 * NULL if the iod cannot be allocated. Pass NULL as userdata if you don't want
 * to immediately associate any user data with the iod. */
nsock_iod nsock_iod_new(nsock_pool nsockp, void *userdata) {
  return nsock_iod_new2(nsockp, -1, userdata);
}

/* This version allows you to associate an existing sd with the msi so that you
 * can read/write it using the nsock infrastructure.  For example, you may want
 * to watch for data from STDIN_FILENO at the same time as you read/write
 * various sockets.  STDIN_FILENO is a special case, however. Any other sd is
 * dup()ed, so you may close or otherwise manipulate your copy.  The duped copy
 * will be destroyed when the nsi is destroyed. */
nsock_iod nsock_iod_new2(nsock_pool nsockp, int sd, void *userdata) {
  struct npool *nsp = (struct npool *)nsockp;
  gh_lnode_t *lnode;
  struct niod *nsi;

  lnode = gh_list_pop(&nsp->free_iods);
  if (!lnode) {
    nsi = (struct niod *)safe_malloc(sizeof(*nsi));
    memset(nsi, 0, sizeof(*nsi));
  } else {
    nsi = container_of(lnode, struct niod, nodeq);
  }

  if (sd == -1) {
    nsi->sd = -1;
    nsi->state = NSIOD_STATE_INITIAL;
  } else if (sd == STDIN_FILENO) {
    nsi->sd = STDIN_FILENO;
    nsi->state = NSIOD_STATE_UNKNOWN;
  } else {
    nsi->sd = dup_socket(sd);
    if (nsi->sd == -1) {
      free(nsi);
      return NULL;
    }
    unblock_socket(nsi->sd);
    nsi->state = NSIOD_STATE_UNKNOWN;
  }

  nsi->first_connect = NULL;
  nsi->first_read = NULL;
  nsi->first_write = NULL;
#if HAVE_PCAP
  nsi->first_pcap_read = NULL;
  nsi->readpcapsd_count = 0;
#endif
  nsi->readsd_count = 0;
  nsi->write_count = 0;

  nsi->userdata = userdata;
  nsi->nsp = (struct npool *)nsockp;

  nsi->_flags = 0;

  nsi->read_count = 0;
  nsi->write_count = 0;

  nsi->hostname = NULL;

  nsi->ipopts = NULL;
  nsi->ipoptslen = 0;

#if HAVE_OPENSSL
  nsi->ssl_session = NULL;
#endif

  if (nsp->px_chain) {
    nsi->px_ctx = proxy_chain_context_new(nsp);
  } else {
    nsi->px_ctx = NULL;
  }

  nsi->id = nsp->next_iod_serial++;
  if (nsi->id == 0)
    nsi->id = nsp->next_iod_serial++;

  /* The nsp keeps track of active iods so it can delete them if it is deleted */
  gh_list_append(&nsp->active_iods, &nsi->nodeq);

  nsock_log_info("nsock_iod_new (IOD #%lu)", nsi->id);

  return (nsock_iod)nsi;
}

/* Defined in nsock_core.c. */
int socket_count_zero(struct niod *iod, struct npool *ms);

/* If nsock_iod_new returned success, you must free the iod when you are done with
 * it to conserve memory (and in some cases, sockets).  After this call,
 * nsockiod may no longer be used -- you need to create a new one with
 * nsock_iod_new().  pending_response tells what to do with any events that are
 * pending on this nsock_iod.  This can be NSOCK_PENDING_NOTIFY (send a KILL
 * notification to each event), NSOCK_PENDING_SILENT (do not send notification
 * to the killed events), or NSOCK_PENDING_ERROR (print an error message and
 * quit the program) */
void nsock_iod_delete(nsock_iod nsockiod, enum nsock_del_mode pending_response) {
#if HAVE_PCAP
#define NUM_EVT_TYPES 4
#else
#define NUM_EVT_TYPES 3
#endif
  struct niod *nsi = (struct niod *)nsockiod;
  gh_lnode_t *evlist_ar[NUM_EVT_TYPES];
  gh_list_t *corresp_list[NUM_EVT_TYPES];
  int i;
  gh_lnode_t *current, *next;

  assert(nsi);

  if (nsi->state == NSIOD_STATE_DELETED) {
    /* This nsi is already marked as deleted, will probably be removed from the
     * list very soon. Just return to avoid breaking reentrancy. */
    return;
  }

  nsock_log_info("nsock_iod_delete (IOD #%lu)", nsi->id);

  if (nsi->events_pending > 0) {
    /* shit -- they killed the struct niod while an event was still pending on it.
     * Maybe I should store the pending events in the iod.  On the other hand,
     * this should be a pretty rare occurrence and so I'll save space and hassle
     * by just locating the events here by searching through the active events
     * list */
    if (pending_response == NSOCK_PENDING_ERROR)
      fatal("nsock_iod_delete called with argument NSOCK_PENDING_ERROR on a nsock_iod that has %d pending event(s) associated with it", nsi->events_pending);

    assert(pending_response == NSOCK_PENDING_NOTIFY || pending_response == NSOCK_PENDING_SILENT);

    evlist_ar[0] = nsi->first_connect;
    evlist_ar[1] = nsi->first_read;
    evlist_ar[2] = nsi->first_write;
#if HAVE_PCAP
    evlist_ar[3] = nsi->first_pcap_read;
#endif

    corresp_list[0] = &nsi->nsp->connect_events;
    corresp_list[1] = &nsi->nsp->read_events;
    corresp_list[2] = &nsi->nsp->write_events;
#if HAVE_PCAP
    corresp_list[3] = &nsi->nsp->pcap_read_events;
#endif

    for (i = 0; i < NUM_EVT_TYPES && nsi->events_pending > 0; i++) {
      for (current = evlist_ar[i]; current != NULL; current = next) {
        struct nevent *nse;

        next = gh_lnode_next(current);
        nse = lnode_nevent(current);

        /* we're done with this list of events for the current IOD */
        if (nse->iod != nsi)
          break;

        nevent_delete(nsi->nsp, nse, corresp_list[i], current, pending_response == NSOCK_PENDING_NOTIFY);
      }
    }
  }

  if (nsi->events_pending != 0)
    fatal("Trying to delete NSI, but could not find %d of the purportedly pending events on that IOD.\n", nsi->events_pending);

  /* Make sure we no longer select on this socket, in case the socket counts
   * weren't already decremented to zero. */
  if (nsi->sd >= 0)
    socket_count_zero(nsi, nsi->nsp);

  free(nsi->hostname);

#if HAVE_OPENSSL
  /* Close any SSL resources */
  if (nsi->ssl) {
    /* No longer free session because copy nsi stores is not reference counted */
#if 0
    if (nsi->ssl_session)
    SSL_SESSION_free(nsi->ssl_session);
    nsi->ssl_session = NULL;
#endif

    if (SSL_shutdown(nsi->ssl) == -1) {
      nsock_log_info("nsock_iod_delete: SSL shutdown failed (%s) on NSI %li",
                     ERR_reason_error_string(SSL_get_error(nsi->ssl, -1)), nsi->id);
    }

    /* I don't really care if the SSL_shutdown() succeeded politely. I could
     * make the SD blocking temporarily for this, but I'm hoping it will succeed
     * 95% of the time because we can usually write to a socket. */
    SSL_free(nsi->ssl);
    nsi->ssl = NULL;
  }
#endif

  if (nsi->sd >= 0 && nsi->sd != STDIN_FILENO) {
    close(nsi->sd);
    nsi->sd = -1;
  }

  nsi->state = NSIOD_STATE_DELETED;
  nsi->userdata = NULL;

  if (nsi->ipoptslen)
    free(nsi->ipopts);

#if HAVE_PCAP
  if (nsi->pcap){
    mspcap *mp = (mspcap *)nsi->pcap;

    if (mp->pt){
      pcap_close(mp->pt);
      mp->pt = NULL;
    }
    if (mp->pcap_desc) {
      /* pcap_close() will close the associated pcap descriptor */
      mp->pcap_desc = -1;
    }
    if (mp->pcap_device) {
      free(mp->pcap_device);
      mp->pcap_device = NULL;
    }
    free(mp);
    nsi->pcap = NULL;
  }
#endif

  if (nsi->px_ctx)
    proxy_chain_context_delete(nsi->px_ctx);
}

/* Returns the ID of an nsock_iod . This ID is always unique amongst ids for a
 * given nspool (unless you blow through billions of them). */
unsigned long nsock_iod_id(nsock_iod nsockiod) {
  assert(nsockiod);
  return ((struct niod *)nsockiod)->id;
}

/* Returns the SSL object inside an nsock_iod, or NULL if unset. */
nsock_ssl nsock_iod_get_ssl(nsock_iod iod) {
#if HAVE_OPENSSL
  return ((struct niod *)iod)->ssl;
#else
  return NULL;
#endif
}

/* Returns the SSL_SESSION of an nsock_iod.
 * Increments its usage count if inc_ref is not zero. */
nsock_ssl_session nsock_iod_get_ssl_session(nsock_iod iod, int inc_ref) {
#if HAVE_OPENSSL
  if (inc_ref)
    return SSL_get1_session(((struct niod *)iod)->ssl);
  else
    return SSL_get0_session(((struct niod *)iod)->ssl);
#else
  return NULL;
#endif
}

/* sets the ssl session of an nsock_iod, increments usage count. The session
 * should not have been set yet (as no freeing is done) */
#if HAVE_OPENSSL
void nsi_set_ssl_session(struct niod *iod, SSL_SESSION *sessid) {
  if (sessid) {
    iod->ssl_session = sessid;
    /* No reference counting for the copy stored briefly in nsiod */
  }
}
#endif

/* Sometimes it is useful to store a pointer to information inside the struct niod so
 * you can retrieve it during a callback. */
void nsock_iod_set_udata(nsock_iod iod, void *udata) {
  assert(iod);
  ((struct niod *)iod)->userdata = udata;
}

/* And the function above wouldn't make much sense if we didn't have a way to
 * retrieve that data... */
void *nsock_iod_get_udata(nsock_iod iod) {
  assert(iod);
  return ((struct niod *)iod)->userdata;
}

/* Returns 1 if an NSI is communicating via SSL, 0 otherwise. */
int nsock_iod_check_ssl(nsock_iod iod) {
  return (((struct niod *)iod)->ssl) ? 1 : 0;
}

/* Returns the remote peer port (or -1 if unavailable).  Note the return value
 * is a whole int so that -1 can be distinguished from 65535.  Port is returned
 * in host byte order. */
int nsock_iod_get_peerport(nsock_iod iod) {
  struct niod *nsi = (struct niod *)iod;
  int fam;

  if (nsi->peerlen <= 0)
    return -1;

  fam = ((struct sockaddr_in *)&nsi->peer)->sin_family;

  if (fam == AF_INET)
    return ntohs(((struct sockaddr_in *)&nsi->peer)->sin_port);
#if HAVE_IPV6
  else if (fam == AF_INET6)
    return ntohs(((struct sockaddr_in6 *)&nsi->peer)->sin6_port);
#endif

  return -1;
}

/* Sets the local address to bind to before connect() */
int nsock_iod_set_localaddr(nsock_iod iod, struct sockaddr_storage *ss,
                            size_t sslen) {
  struct niod *nsi = (struct niod *)iod;

  assert(nsi);

  if (sslen > sizeof(nsi->local))
    return -1;

  memcpy(&nsi->local, ss, sslen);
  nsi->locallen = sslen;
  return 0;
}

/* Sets IPv4 options to apply before connect(). It makes a copy of the options,
 * so you can free() yours if necessary. This copy is freed when the iod is
 * destroyed. */
int nsock_iod_set_ipoptions(nsock_iod iod, void *opts, size_t optslen) {
  struct niod *nsi = (struct niod *)iod;

  assert(nsi);

  if (optslen > 44)
    return -1;

  nsi->ipopts = safe_malloc(optslen);
  memcpy(nsi->ipopts, opts, optslen);
  nsi->ipoptslen = optslen;
  return 0;
}

/* I didn't want to do this.  Its an ugly hack, but I suspect it will be
 * necessary.  I certainly can't reproduce in nsock EVERYTHING you might want
 * to do with a socket.  So I'm offering you this function to obtain the socket
 * descriptor which is (usually) wrapped in a nsock_iod).  You can do
 * "reasonable" things with it, like setting socket receive buffers.  But don't
 * create havok by closing the descriptor!  If the descriptor you get back is
 * -1, the iod does not currently possess a valid descriptor */
int nsock_iod_get_sd(nsock_iod iod) {
  struct niod *nsi = (struct niod *)iod;

  assert(nsi);

#if HAVE_PCAP
  if (nsi->pcap)
    return ((mspcap *)nsi->pcap)->pcap_desc;
  else
#endif
    return nsi->sd;
}

unsigned long nsock_iod_get_read_count(nsock_iod iod){
  assert(iod);
  return ((struct niod *)iod)->read_count;
}

unsigned long nsock_iod_get_write_count(nsock_iod iod){
  assert(iod);
  return ((struct niod *)iod)->write_count;
}

int nsock_iod_set_hostname(nsock_iod iod, const char *hostname) {
  struct niod *nsi = (struct niod *)iod;

  if (nsi->hostname != NULL)
    free(nsi->hostname);

  nsi->hostname = strdup(hostname);
  if (nsi->hostname == NULL)
    return -1;

  return 0;
}

