// -*- C++ -*-
// generic socket DLL, winsock version
// disclaimer:  a C programmer wrote this.

// $Id: gensock.cpp 1.15 1994/11/23 22:38:10 rushing Exp $

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

extern "C" {
#include <winsock.h>
#include "gensock.h"
}

//MDS increased buffer from 512 19991214
#define SOCKET_BUFFER_SIZE  1500

/* This is for NT */
#ifdef _WIN32
HANDLE  dll_module_handle;
#define GET_CURRENT_TASK    dll_module_handle
#define TASK_HANDLE_TYPE    HANDLE
#define GENSOCK_EXPORT

/* This is for WIN16 */
#else
HINSTANCE dll_module_handle;
#define GET_CURRENT_TASK    GetCurrentTask()
#define TASK_HANDLE_TYPE    HTASK
#define GENSOCK_EXPORT      _export
#endif

int  init_winsock (void);
void deinit_winsock (void);

//
//
//

#ifdef _DEBUG
void complain (char * message)
{
  OutputDebugString (message);
}
#else
void complain (char * message)
{
  MessageBox (NULL, message, "GENSOCK.DLL Error", MB_OK|MB_ICONHAND);
}
#endif

//
// ---------------------------------------------------------------------------
// container for a buffered SOCK_STREAM.

class connection
{
 private:
  SOCKET    the_socket;
  char *    in_buffer;
  char *    out_buffer;
  unsigned int  in_index;
  unsigned int  out_index;
  unsigned int  in_buffer_total;
  unsigned int  out_buffer_total;
  unsigned int  last_winsock_error;
  TASK_HANDLE_TYPE      owner_task;
  fd_set    fds;
  struct timeval    timeout;

 public:

  connection (void);
  ~connection (void);

  int       get_connected (char far * hostname, char far * service);
  SOCKET    get_socket(void) { return (the_socket); }
  TASK_HANDLE_TYPE      get_owner_task(void) { return (owner_task); }
  int       get_buffer(int wait);
  int       close (void);
  int       getchar (int wait, char far * ch);
  int       put_data (char far * data, unsigned long length);
  int       put_data_buffered (char far* data, unsigned long length);
  int       put_data_flush (void);
};

connection::connection (void)
{
  the_socket = 0;
  in_index = 0;
  out_index = 0;
  in_buffer_total = 0;
  out_buffer_total = 0;
  in_buffer = 0;

  in_buffer = new char[SOCKET_BUFFER_SIZE];
  out_buffer = new char[SOCKET_BUFFER_SIZE];

  last_winsock_error = 0;
}

connection::~connection (void)
{
  delete [] in_buffer;
}

int
gensock_is_a_number (char far* string)
{
  while (*string) {
    if (!isdigit (*string)) {
      return (0);
    }
    string++;
  }
  return (1);
}
    
//
// ---------------------------------------------------------------------------
//

int
connection::get_connected (char FAR * hostname, char FAR * service)
{
  struct hostent FAR *  hostentry;
  struct servent FAR *  serventry;
  unsigned long     ip_address;
  struct sockaddr_in    sa_in;
  int           our_port;
  int           not = 0;
  int           retval, err_code;
  unsigned long     ioctl_blocking = 1;
  char          message[512];

  // if the ctor couldn't get a buffer
  if (!in_buffer || !out_buffer)
    return (ERR_CANT_MALLOC);

  // --------------------------------------------------
  // resolve the service name
  //

  // If they've specified a number, just use it.
  if (gensock_is_a_number (service)) {
    char far* tail;
    our_port = (int) strtol (service, &tail, 10);
    if (tail == service) {
      return (ERR_CANT_RESOLVE_SERVICE);
    } else {
      our_port = htons (our_port);
    }
  } else {
    // we have a name, we must resolve it.
    serventry = getservbyname (service, (LPSTR)"tcp");
    
    if (serventry)
      our_port = serventry->s_port;
    else {
      retval = WSAGetLastError();
      // Chicago beta is throwing a WSANO_RECOVERY here...
      if ((retval == WSANO_DATA) || (retval == WSANO_RECOVERY)) {
    return (ERR_CANT_RESOLVE_SERVICE);
      } else {
    return (retval - 5000);
      }
    }
  }

  // --------------------------------------------------
  // resolve the hostname/ipaddress
  //

  if ((ip_address = inet_addr (hostname)) != INADDR_NONE) {
    sa_in.sin_addr.s_addr = ip_address;
  }
  else {
    if ((hostentry = gethostbyname(hostname)) == NULL) {
      return (ERR_CANT_RESOLVE_HOSTNAME);
    }
    sa_in.sin_addr.s_addr = *(long far *)hostentry->h_addr;
#if 0
	{
		OutputDebugString(inet_ntoa(sa_in.sin_addr));
	}
#endif
  }


  // --------------------------------------------------
  // get a socket
  //

  if ((the_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
    return (ERR_CANT_GET_SOCKET);
  }

  sa_in.sin_family = AF_INET;
  sa_in.sin_port = our_port;

  // set socket options.  DONTLINGER will give us a more graceful disconnect
    
    // MDS yes, and also terminate our connections before the last command
    // has been completely sent causing abnormal terminations on the receiving
    // side! 
    // From www.lcg.org/sock-faq
    // "... SO_LINGER seems to be required to guarantee delivery of all data."
    //20000410  
#if 0  
    setsockopt(the_socket,
         SOL_SOCKET,
         SO_DONTLINGER,
         (char *) &not, sizeof(not));
#endif
  // get a connection

  if ((retval = connect (the_socket,
             (struct sockaddr *)&sa_in,
             sizeof(struct sockaddr_in))==SOCKET_ERROR)) {
    switch ((err_code = WSAGetLastError())) {
      /* twiddle our thumbs until the connect succeeds */
    case WSAEWOULDBLOCK:
      break;
    case WSAECONNREFUSED:
      return (ERR_CONNECTION_REFUSED);
      break;
    default:
      wsprintf(message, "unexpected error %d from winsock\n", err_code);
      complain(message);
      return (ERR_CANT_CONNECT);
      break;
    }
  }

  owner_task = GET_CURRENT_TASK;

  // Make this a non-blocking socket
  ioctlsocket (the_socket, FIONBIO, &ioctl_blocking);

  // make the FD_SET and timeout structures for later operations...

  FD_ZERO (&fds);
  FD_SET  (the_socket, &fds);

  // normal timeout, can be changed by the wait option.
  timeout.tv_sec = 30;
  timeout.tv_usec = 0;

  return (0);
}


//
//---------------------------------------------------------------------------
//
// The 'wait' parameter, if set, says to return WAIT_A_BIT
// if there's no data waiting to be read.

int
connection::get_buffer(int wait)
{
  int retval;
  int bytes_read = 0;
  unsigned long ready_to_read = 0;

  // Use select to see if data is waiting...

  FD_ZERO (&fds);
  FD_SET  (the_socket, &fds);

  // if wait is set, we are polling, return immediately
  if (wait) {
    timeout.tv_sec = 0;
  }
  else {
    timeout.tv_sec = 30;
  }

  if ((retval = select (0, &fds, NULL, NULL, &timeout))
      == SOCKET_ERROR) {
    char what_error[256];
    int error_code = WSAGetLastError();

    if (error_code == WSAEINPROGRESS && wait) {
      return (WAIT_A_BIT);
    }

    wsprintf (what_error,
          "connection::get_buffer() unexpected error from select: %d",
          error_code);
    complain (what_error);
  }

  // if we don't want to wait
  if (!retval && wait) {
    return (WAIT_A_BIT);
  }

  // we have data waiting...
  bytes_read = recv (the_socket,
             in_buffer,
             SOCKET_BUFFER_SIZE,
             0);

  // just in case.
  if (bytes_read == 0) {
    // connection terminated (semi-) gracefully by the other side
    return (ERR_NOT_CONNECTED);
  }

  if (bytes_read == SOCKET_ERROR) {
    char what_error[256];
    int ws_error = WSAGetLastError();
    switch (ws_error) {
      // all these indicate loss of connection (are there more?)
    case WSAENOTCONN:
    case WSAENETDOWN:
    case WSAENETUNREACH:
    case WSAENETRESET:
    case WSAECONNABORTED:
    case WSAECONNRESET:
      return (ERR_NOT_CONNECTED);
      break;

    case WSAEWOULDBLOCK:
      return (WAIT_A_BIT);
      break;

    default:
      wsprintf (what_error,
        "connection::get_buffer() unexpected error: %d",
        ws_error);
      complain (what_error);
    }
  }

  // reset buffer indices.
  in_buffer_total = bytes_read;
  in_index = 0;
  return (0);

}

//
//---------------------------------------------------------------------------
// get a character from this connection.
//

int
connection::getchar(int wait, char FAR * ch)
{
  int retval;

  if (in_index >= in_buffer_total) {
    if ((retval = get_buffer(wait)))
      return (retval);
  }
  *ch = in_buffer[in_index++];
  return (0);
}


//
//---------------------------------------------------------------------------
// FIXME: should try to handle the fact that send can only take
// an int, not an unsigned long.

int
connection::put_data (char far* data, unsigned long length)
{
  int num_sent;
  int retval;

  FD_ZERO (&fds);
  FD_SET  (the_socket, &fds);

  timeout.tv_sec = 30;

  while (length > 0) {
    if ((retval = select (0, NULL, &fds, NULL, &timeout)) == SOCKET_ERROR) {
      char what_error[256];
      int error_code = WSAGetLastError();
      
      wsprintf (what_error,
        "connection::put_data() unexpected error from select: %d",
        error_code);
      complain (what_error);
    }

    num_sent = send (the_socket,
             data,
             length > 1024 ? 1024 : (int)length,
             0);

    if (num_sent == SOCKET_ERROR) {
      char what_error[256];
      int ws_error = WSAGetLastError();
      switch (ws_error) {
    // this is the only error we really expect to see.
      case WSAENOTCONN:
    return (ERR_NOT_CONNECTED);
    break;

    // seems that we can still get a block
      case WSAEWOULDBLOCK:
      case WSAEINPROGRESS:
    break;

      default:
    wsprintf (what_error,
          "connection::put_data() unexpected error from send(): %d",
          ws_error);
    complain (what_error);
    return (ERR_SENDING_DATA);
      }
    }
    else {
      length -= num_sent;
      data += num_sent;
    }
  }

  return (0);
}

//
//
// buffered output
//

int
connection::put_data_buffered (char far* data, unsigned long length)
{
  unsigned int sorta_sent = 0;
  int retval;

  while (length) {
    if ((out_index + length) < SOCKET_BUFFER_SIZE) {
      // we won't overflow, simply copy into the buffer
      memcpy (out_buffer + out_index, data, (size_t) length);
      out_index += (unsigned int) length;
      length = 0;
    }
    else {
      unsigned int orphaned_chunk = SOCKET_BUFFER_SIZE - out_index;
      // we will overflow, handle it
      memcpy (out_buffer + out_index, data, orphaned_chunk);
      // send this buffer...
      if ((retval = put_data (out_buffer, SOCKET_BUFFER_SIZE))) {
    return (retval);
      }
      length -= orphaned_chunk;
      out_index = 0;
      data += orphaned_chunk;
    }
  }

  return (0);
}

int
connection::put_data_flush (void)
{
  int retval;

  if ((retval = put_data (out_buffer, out_index)))
    return (retval);
  else
    out_index = 0;

  return(0);
}

//
//---------------------------------------------------------------------------
//

int
connection::close (void)
{
  if (closesocket(the_socket) == SOCKET_ERROR)
    return (ERR_CLOSING);
  else
    return (0);
}


//
//---------------------------------------------------------------------------
// we keep lists of connections in this class

class connection_list
{
private:
  connection *      data;
  connection_list *     next;

public:
  connection_list   (void);
  ~connection_list  (void);
  void push         (connection & conn);

  // should really use pointer-to-memberfun for these
  connection * find (SOCKET sock);
  int how_many_are_mine (void);

  void remove       (socktag sock);
};

connection_list::connection_list (void)
{
  next = 0;
}

connection_list::~connection_list(void)
{
  delete data;
}

// add a new connection to the list

void
connection_list::push (connection & conn)
{
  connection_list * new_conn;

  new_conn = new connection_list();

  new_conn->data = data;
  new_conn->next = next;

  data = &conn;
  next = new_conn;

}

int
connection_list::how_many_are_mine(void)
{
  TASK_HANDLE_TYPE  current_task = GET_CURRENT_TASK;
  connection_list * iter = this;
  int num = 0;

  while (iter->data) {
    if (iter->data->get_owner_task() == current_task)
      num++;
    iter = iter->next;
  }
  return (num);
}

// find a particular socket's connection object.

connection *
connection_list::find (SOCKET sock)
{
  connection_list * iter = this;

  while (iter->data) {
    if (iter->data->get_socket() == sock)
      return (iter->data);
    iter = iter->next;
  }
  return (0);
}

void
connection_list::remove (socktag sock)
{
  // at the end
  if (!data)
    return;

  // we can assume next is valid because
  // the last node is always {0,0}
  if (data == sock) {
    delete data;
    data = next->data;
    next = next->next;  // 8^)
    return;
  }

  // recurse
  next->remove(sock);
}

//
// ---------------------------------------------------------------------------
// global variables (shared by all DLL users)

connection_list global_socket_list;
int network_initialized;

//
//---------------------------------------------------------------------------
//

#ifndef _WIN32

// the DLL entry routine
int FAR PASCAL LibMain (HINSTANCE hinstance,
            WPARAM data_seg,
            LPARAM heap_size,
            LPSTR command_line)
{
  network_initialized = 0;
  dll_module_handle = hinstance;
  return (1);
}

#else

extern "C" {
  INT APIENTRY
    LibMain (HANDLE     hInst,
         ULONG      reason_called,
         LPVOID     reserved)
      {

    switch (reason_called) {
    case DLL_PROCESS_ATTACH:
      /* init */
      dll_module_handle = hInst;
      break;
    case DLL_THREAD_ATTACH:
      break;
    case DLL_THREAD_DETACH:
      break;
    case DLL_PROCESS_DETACH:
      break;

    default:
      break;
    }
    return (1);
      }

  /*
   * This wrapper is the actual entry point for the DLL.  It ensures
   * that the C RTL is correctly [de]initialized.
   */

BOOL WINAPI _CRT_INIT (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);

BOOL WINAPI
dll_entry_point (HINSTANCE hinstDLL,
         DWORD fdwReason,
         LPVOID lpReserved)
{
  /* Init the C run-time before calling any of your code */

  switch (fdwReason) {
  case DLL_PROCESS_ATTACH:
  case DLL_THREAD_ATTACH:
    if (!_CRT_INIT (hinstDLL, fdwReason, lpReserved))
      return (FALSE);
    else
      LibMain (hinstDLL, fdwReason, lpReserved);
    break;

  case DLL_PROCESS_DETACH:
  case DLL_THREAD_DETACH:
    if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
      return(FALSE);
    break;
  }
  return (TRUE);
}

}
#endif

// ---------------------------------------------------------------------------
// C/DLL interface
//

int FAR PASCAL GENSOCK_EXPORT
gensock_connect (char FAR * hostname,
         char FAR * service,
         socktag FAR * pst)
{
  int retval;
  connection * conn = new connection;

  if (!conn)
    return (ERR_INITIALIZING);

  // if this task hasn't opened any sockets yet, then
  // call WSAStartup()

  if (global_socket_list.how_many_are_mine() < 1)
    init_winsock();

  global_socket_list.push(*conn);

  if ((retval = conn->get_connected (hostname, service))) {
    gensock_close(conn);
    *pst = 0;
    return (retval);
  }
  *pst = (void FAR *) conn;

  return (0);
}

//
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_getchar (socktag st, int wait, char FAR * ch)
{
  connection * conn;
  int retval = 0;

  conn = (connection *) st;
  if (!conn)
    return (ERR_NOT_A_SOCKET);

  if ((retval = conn->getchar(wait, ch)))
    return (retval);
  else
    return (0);
}


//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_put_data (socktag st, char FAR * data, unsigned long length)
{
  connection * conn;
  int retval = 0;

  conn = (connection *) st;

  if (!conn)
    return (ERR_NOT_A_SOCKET);

  if ((retval = conn->put_data(data, length)))
    return (retval);

  return (0);
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_put_data_buffered (socktag st, char FAR * data, unsigned long length)
{
  connection * conn;
  int retval = 0;

  conn = (connection *) st;

  if (!conn)
    return (ERR_NOT_A_SOCKET);

  if ((retval = conn->put_data_buffered (data, length)))
    return (retval);

  return (0);
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_put_data_flush (socktag st)
{
  connection * conn;
  int retval = 0;

  conn = (connection *) st;

  if (!conn)
    return (ERR_NOT_A_SOCKET);

  if ((retval = conn->put_data_flush() ))
    return (retval);

  return (0);
}

//---------------------------------------------------------------------------
//
//
int FAR PASCAL GENSOCK_EXPORT
gensock_gethostname (char FAR * name, int namelen)
{
  int retval;
  if ((retval = gethostname(name, namelen))) {
    return (retval - 5000);
  }
  else return (0);
}

/*
int FAR PASCAL GENSOCK_EXPORT
gensock_myhostname (char FAR * name, int namelen, socktag FAR * pst)
{

  connection * pconn = (connection*) *pst;

    int retval;
  if ((retval = gethostname(name, namelen))) {
    return (retval - 5000);
  }
  else return (0);

                //Try using socket name instead
                int sock, sent, addrlen = sizeof(struct sockaddr_in);
                struct sockaddr_in someAddr;
                    
                StatusAdd( this, IDS_WINSOCK_SOCKETNAME, IDC_EB_LRESULTS); //socket address lookup
                
                
                memset(&someAddr, 0, sizeof(someAddr));
                someAddr.sin_family = AF_INET;
                someAddr.sin_port = htons(9); // 9 is discard
                someAddr.sin_addr.s_addr = inet_addr("141.142.103.20");
                 // something at NCSA, it doesn't matter much though
                    
                if( ((sock = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET) 
                    && (connect( sock, (struct sockaddr*)&someAddr, sizeof(someAddr) ) != SOCKET_ERROR) 
                    && (getsockname(sock, (struct sockaddr*)&myAddr, &addrlen) != SOCKET_ERROR) 
                    && (myAddr.sin_addr.s_addr != htonl(INADDR_ANY)) 
                    && (myAddr.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) )
                { 
                    //getsockname worked
                    nHostMode = USESOCKETNAME;
                    TRACE("socket(SOCK_DRAM)/connect()/getsockname() worked\n");
                }
                else if( ((sent = send(sock, (char*)&sock, sizeof(sock), 0)) != SOCKET_ERROR) 
                    && (sent > 0) && (addrlen = sizeof(struct sockaddr_in)) 
                    && (getsockname(sock, (struct sockaddr*)&myAddr, &addrlen) != SOCKET_ERROR)
                    && (myAddr.sin_addr.s_addr != htonl(INADDR_ANY)) 
                    && (myAddr.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) ) 
                {
                    //getsockname after send worked
                    nHostMode = USESOCKETNAME;
                    TRACE("socket(SOCK_DRAM)/connect()/send()/getsockname() worked\n");
                } 
                else 
                {
                    //nothing worked
                    StatusAppend( this, IDS_FAILED, IDC_EB_LRESULTS); //Failed
                    TRACE("nothing worked\n");
                }
            }
                  
            //Found IP Address via socket name, print out the stats
            if (nHostMode == USESOCKETNAME)
            {
                StatusAppend( this, IDS_OK, IDC_EB_LRESULTS); //OK

                //Print TCP/IP address
                StatusAdd( this, IDS_WINSOCK_ADDR, IDC_EB_LRESULTS);
                csTemp = inet_ntoa( myAddr.sin_addr );
                StatusAppend( this, csTemp, IDC_EB_LRESULTS)

}
*/
//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_close (socktag st)
{
  connection * conn;
  int retval;

  conn = (connection *) st;

  if (!conn)
    return (ERR_NOT_A_SOCKET);

  if ((retval = conn->close()))
    return (retval);

  global_socket_list.remove((connection *)st);

  if (global_socket_list.how_many_are_mine() < 1) {
    deinit_winsock();
  }

  return (0);
}

//---------------------------------------------------------------------------
//
//

int
init_winsock(void)
{
  int retval;
  WSADATA winsock_data;
  WORD version_required = 0x0101; /* Version 1.1 */

  retval = WSAStartup (version_required, &winsock_data);

  switch (retval) {
  case 0:
    /* successful */
    break;
  case WSASYSNOTREADY:
    return (ERR_SYS_NOT_READY);
    break;
  case WSAEINVAL:
    return (ERR_EINVAL);
    break;
  case WSAVERNOTSUPPORTED:
    return (ERR_VER_NOT_SUPPORTED);
    break;
  }
  network_initialized = 1;
  return (0);
}

void
deinit_winsock(void)
{
  network_initialized = 0;
  WSACleanup();
}
