//
// Socket  File: socket.cpp
//
// Copyright 1998 Paul S. Hethmon
//

#include "socket.hpp"

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

Socket::Socket()
{
  iSock = -1;
  iLen = sizeof(siUs);
  iErr = 0;
  iEol = FALSE;
  szOutBuf = new char[MAX_SOCK_BUFFER];
  szBuf1 = new char[MAX_SOCK_BUFFER];
  szBuf2 = new char[MAX_SOCK_BUFFER];
  iBeg1 = iEnd1 = iBeg2 = iEnd2 = 0;
  iBuf = 1;
  szPeerIp = NULL;
  szPeerName = NULL;
  ulTimeout = 5 * 60;  // 5 minutes default.
}

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

Socket::~Socket()
{
  if (iSock > -1) soclose(iSock);
  delete [] szOutBuf;
  delete [] szBuf1;
  delete [] szBuf2;
  if (szPeerIp) delete [] szPeerIp;
  if (szPeerName) delete [] szPeerName;
}

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

int
Socket::Create()                     // Allocate a socket for use
{
  iSock = socket(AF_INET, SOCK_STREAM, 0);
  return iSock;
}

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

int
Socket::SetOob()  // Set SO_OOBINLINE
{
  int optval = 1;

  iErr = setsockopt(iSock, SOL_SOCKET, SO_OOBINLINE, (char *) &optval, sizeof(int));

  return iErr;
}

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

int
Socket::SetLinger(int iTime)  // Set the SO_LINGER option
{
  struct linger L;

  L.l_onoff = 1;
  L.l_linger = 20; // linger for 20 seconds to make sure buffer flushed
  iErr = setsockopt(iSock, SOL_SOCKET, SO_LINGER, (char *) &L, sizeof(L));

  return iErr;
}

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

int
Socket::SetKeepAlive()  // Set SO_KEEPALIVE
{
  int optval = 1;

  // Tell the stack to ping the other host to make sure the
  // connection doesn't drop.
  iErr = setsockopt(iSock, SOL_SOCKET, SO_KEEPALIVE, (char *) &optval, sizeof(int));

  return iErr;
}

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

int
Socket::SetReusePort(int iReuse)  // Set SO_REUSEADDR
{
  int optval;

  if (iReuse == REUSE_PORT)
    {
      optval = 1;
    }
  else
    {
      optval = 0;
    }

  iErr = setsockopt(iSock, SOL_SOCKET, SO_REUSEADDR, (char *) &optval, sizeof(int));

  return iErr;
}

// ------------------------------------------------------------------
//
// Passive
//
// Place the socket into passive mode.
//

int
Socket::Passive(u_short usPort, int iReuse, u_long ulAddr)
{
  int optval = 1;

  if (iReuse > 0)  // Force reuse of the address.
    {
      setsockopt(iSock, SOL_SOCKET, SO_REUSEADDR, (char *) &optval, sizeof(int));
    }

  usPortUs = usPort;

  bzero((void *)&siUs, iLen);              // make sure everything zero
  siUs.sin_family = AF_INET;
  siUs.sin_port = htons(usPortUs);
  siUs.sin_addr.s_addr = ulAddr;

  // Bind to the given port
  iErr = bind(iSock, (struct sockaddr *) &siUs, iLen);
  if (iErr < 0)
    {
      return iErr;
    }

  // change to passive socket
  iErr = listen(iSock, MY_SOMAXCONN);
  if (iErr < 0)
    {
      return iErr;
    }

  if (usPortUs == 0)  // Look up the port we got.
    {
      bzero((void *)&siUs, iLen);              // make sure everything zero
      iErr = getsockname(iSock, (sockaddr *)&siUs, &iLen);
      if (iErr < 0)
        {
          return iErr;
        }
      usPortUs = ntohs(siUs.sin_port);
    }

  return 0;
}

// ------------------------------------------------------------------
//
// Connect
//
// Connect to the specified remote host.
//

int
Socket::Connect(char *szRemoteIp, u_short usRemotePort, 
                u_short usLocalPort, u_long ulAddr)
{
  struct hostent *heHost;
  u_long ulClientAddr;

  heHost = NULL;
  heHost = gethostbyname(szRemoteIp);
  if (heHost == NULL)
    {
      ulClientAddr = inet_addr(szRemoteIp);
      if (ulClientAddr == -1)
        {
          iErr = -1;
          return (iErr);
        }
    }
  else
    {
      ulClientAddr = *((u_long *)heHost->h_addr);
    }

  bzero((void *)&siUs, iLen);              // make sure everything zero
  siThem.sin_family = AF_INET;
  siThem.sin_port = htons(usRemotePort);
  siThem.sin_addr.s_addr = ulClientAddr;

  bzero((void *)&siUs, sizeof(siUs));
  siUs.sin_family = AF_INET;
  // Use the specified domain name address
  siUs.sin_addr.s_addr = ulAddr;
  // Always use the user defined port
  usPortUs = usLocalPort;
  siUs.sin_port = htons(usPortUs);

  iErr = bind(iSock, (struct sockaddr *)&siUs, iLen);
  if (iErr != 0)
    {
      return iErr;
    }

  iErr = connect(iSock, (sockaddr *)&siThem, iLen);
  if (usPortUs == 0)  // Look up the port we got.
    {
      bzero((void *)&siUs, iLen);              // make sure everything zero
      iErr = getsockname(iSock, (sockaddr *)&siUs, &iLen);
      if (iErr < 0)
        {
          return iErr;
        }
      usPortUs = ntohs(siUs.sin_port);
    }
  return (iErr);
}

// ------------------------------------------------------------------
//
// Accept
//
// Accept an incoming connection on the socket.
//

Socket *
Socket::Accept()
{
  Socket *sSock;

  sSock = new Socket();

  bzero(&siThem, iLen);
  sSock->iSock = accept(iSock, (struct sockaddr *)&(sSock->siThem), &iLen);
  if (sSock->iSock < 0)
    {
      iErr = sSock->iSock;
      delete sSock;
      return NULL;
    }

  sSock->szPeerIp = new char[128];
  strncpy(sSock->szPeerIp, inet_ntoa(sSock->siThem.sin_addr), 128);

  return sSock;
}

// ------------------------------------------------------------------
//
// Send
//
// Send the specified buffer across the socket.
//

int 
Socket::Send(char *szBuf, int iLen)
{
#ifdef __OS2__
  int fdsSocks[1];
#elif __WINDOWS__
  fd_set fdsSocks;
  struct timeval stTimeout;
#endif

#ifdef __OS2__
  fdsSocks[0] = iSock;
  iErr = os2_select(fdsSocks, 0, 1, 0, ulTimeout * 1000);
  if (iErr < 1) // Error occured.
    {
      return iErr;
    }
#endif
  iErr = send(iSock, szBuf, iLen, 0);
  return iErr;
}

// ------------------------------------------------------------------
//
// SendText
//
// Send the specified file across the socket. This assumes
// a text file.
//

int
Socket::SendText(char *szFileName, u_long ulOffset)
{
#ifdef __OS2__
  int fdsSocks[1];
#elif __WINDOWS__
  fd_set fdsSocks;
  struct timeval stTimeout;
#endif
  ifstream ifIn;
  char *szBuf;

  ifIn.open(szFileName);
  if (! ifIn)
    {
      return -1;
    }

  if (ulOffset > 0)
    {
      // Start at the position in the class they asked for
      ifIn.seekg(ulOffset);
    }

  szBuf = new char[SOCK_BUFSIZE];
  iErr = 0;
  do
    {
      memset(szBuf, 0, SOCK_BUFSIZE);
      ifIn.getline(szBuf, SOCK_BUFSIZE, '\n');
#ifdef __OS2__
      fdsSocks[0] = iSock;
      iErr = os2_select(fdsSocks, 0, 1, 0, ulTimeout * 1000);
      if (iErr < 1) // Error occured.
        {
          ifIn.close();
          delete [] szBuf;
          return iErr;
        }
#endif
      iErr += send(iSock, szBuf, strlen(szBuf), 0);    // The line.
      if ( ifIn.eof() ) break;                         // The last line
                                                       // doesn't get an
                                                       // eol appended.
      iErr += send(iSock, "\r\n", strlen("\r\n"), 0);  // The eol.
    }
  while ( ! ifIn.eof() );

  ifIn.close();
  delete [] szBuf;
  return iErr;
}

// ------------------------------------------------------------------
//
// SendDotText
//
// Send the specified file across the socket. This assumes
// a text file. The text file is byte stuffed as necessary
// to allow transfer in an SMTP or POP3 session.
//

int
Socket::SendDotText(char *szFileName)
{
#ifdef __OS2__
  int fdsSocks[1];
#elif __WINDOWS__
  fd_set fdsSocks;
  struct timeval stTimeout;
#endif
  ifstream ifIn;
  char *szBuf,
       *szSendBuf;


  ifIn.open(szFileName);
  if (! ifIn)
    {
      return -1;
    }

  szBuf = new char[SOCK_BUFSIZE];
  szSendBuf = new char[MAX_SEND_BUFFER];
  szSendBuf[0] = NULL;
  iErr = 0;
  do
    {
      memset(szBuf, 0, SOCK_BUFSIZE);
      ifIn.getline(szBuf, SOCK_BUFSIZE, '\n');
#ifdef __OS2__
      fdsSocks[0] = iSock;
      iErr = os2_select(fdsSocks, 0, 1, 0, ulTimeout * 1000);
      if (iErr < 1) // Error occured.
        {
          ifIn.close();
          delete [] szBuf;
          delete [] szSendBuf;
          return iErr;
        }
#endif
      if ( (strlen(szSendBuf) + strlen(szBuf) + 32) < MAX_SEND_BUFFER)
        {
          if (szBuf[0] == '.')
            {
              strcat(szSendBuf, ".");
            }
          strcat(szSendBuf, szBuf);
          strcat(szSendBuf, "\r\n");
        }
      else // Buffer full, send now
        {
          iErr += send(iSock, szSendBuf, strlen(szSendBuf), 0);
          szSendBuf[0] = NULL; // empty buffer
          // Add last line read to send buffer
          if (szBuf[0] == '.')
            {
              strcat(szSendBuf, ".");
            }
          strcat(szSendBuf, szBuf);
          strcat(szSendBuf, "\r\n");
        }
    }
  while ( ! ifIn.eof() );

  if (strlen(szSendBuf) > 0)
    {
      iErr += send(iSock, szSendBuf, strlen(szSendBuf), 0);
    }
  iErr += send(iSock, ".\r\n", strlen(".\r\n"), 0);

  ifIn.close();
  delete [] szBuf;
  delete [] szSendBuf;
  return iErr;
}

// ------------------------------------------------------------------
//
// SendBinary
//
// Send the specified file across the socket. This assumes
// a binary file.
//

int
Socket::SendBinary(char *szFileName, u_long ulOffset)
{
#ifdef __OS2__
  int fdsSocks[1];
#elif __WINDOWS__
  fd_set fdsSocks;
  struct timeval stTimeout;
#endif
  ifstream ifIn;
  char *szBuf;
  iErr = 0;

  ifIn.open(szFileName, ios::binary);
  if (! ifIn)
    {
      return -1;
    }

  if (ulOffset > 0)
    {
      // Start at the position in the class they asked for
      ifIn.seekg(ulOffset);
    }

  szBuf = new char[SOCK_BUFSIZE];

  while ( ! ifIn.eof() )
    {
      ifIn.read(szBuf, SOCK_BUFSIZE);
#ifdef __OS2__
      fdsSocks[0] = iSock;
      iErr = os2_select(fdsSocks, 0, 1, 0, ulTimeout * 1000);
      if (iErr < 1) // Error occured.
        {
          ifIn.close();
          delete [] szBuf;
          return iErr;
        }
#endif
      iErr = send(iSock, szBuf, ifIn.gcount(), 0);    // The line
    }

  ifIn.close();
  delete [] szBuf;
  return iErr;
}

// ------------------------------------------------------------------
//
// Recv
//
// Receive up to iBytes on this socket.
//

int
Socket::Recv(int iBytes)
{
#ifdef __OS2__
  int fdsSocks[1];
#elif __WINDOWS__
  fd_set fdsSocks;
  struct timeval stTimeout;
#endif

  memset(szOutBuf, 0, MAX_SOCK_BUFFER);
  iErr = 0;

  if ((iBuf == 1) && (iEnd1 != 0))  // Copy the contents of buf 1.
    {
      if (iBytes >= (iEnd1 - iBeg1))  // Copy all the bytes.
        {
          memcpy(szOutBuf, szBuf1 + iBeg1, iEnd1 - iBeg1);
          iErr = iEnd1 - iBeg1;
          iBeg1 = iEnd1 = 0;
          iBuf = 2;
        }
      else  // Only copy the requested number.
        {
          memcpy(szOutBuf, szBuf1 + iBeg1, iBytes);
          iErr = iBytes;    // This many bytes sent back.
          iBeg1 += iBytes;  // Advance to this location.
        }
    }      
  else if ((iBuf == 2) && (iEnd2 != 0))  // Copy the contents of buf 2.
    {
      if (iBytes >= (iEnd2 - iBeg2))
        {
          memcpy(szOutBuf, szBuf2 + iBeg2, iEnd2 - iBeg2);
          iErr = iEnd2 - iBeg2;
          iBeg2 = iEnd2 = 0;
          iBuf = 1;
        }
      else
        {
          memcpy(szOutBuf, szBuf2 + iBeg2, iBytes);
          iErr = iBytes;
          iBeg1 += iBytes;
        }
    }      
  else
    {
#ifdef __OS2__
      fdsSocks[0] = iSock;
      iErr = os2_select(fdsSocks, 1, 0, 0, ulTimeout * 1000);
      if (iErr < 1) // Error occured.
        {
          return -1;
        }
#elif __WINDOWS__
      FD_ZERO(&fdsSocks);
      FD_SET(iSock, &fdsSocks);
      stTimeout.tv_sec = ulTimeout;
      iErr = select(1, &fdsSocks, 0, 0, &stTimeout);
      if (iErr < 1) // Error occured.
        {
          return -1;
        }
#endif
      iErr = recv(iSock, szOutBuf, iBytes, 0);
      if (iErr == 0) return -1;
    }

  return iErr;
}

// ------------------------------------------------------------------
//
// RecvTeol
//
// Receive a line delimited nominally by the telnet end-of-line
// sequence -- CRLF. This one also accepts just CR or just LF
// also.
//
// A return value of less than 0 indicates an error with the connection.
//

int
Socket::RecvTeol(int iToast)
{
  int i;
  int iState = 1,
      idx = 0;
#ifdef __OS2__
  int fdsSocks[1];
#elif __WINDOWS__
  fd_set fdsSocks;
  struct timeval stTimeout;
#endif

  memset(szOutBuf, 0, MAX_SOCK_BUFFER);
  iEol = FALSE;
  iErr = 0;

  while (iState != 0)
    {
      switch (iState)
        {
          case 1:  // Figure out where to start.
            {
              if ((iEnd1 == 0) && (iEnd2 == 0)) // Both buffers empty.
                {
                  iState = 2;
                }
              else
                {
                  iState = 3;
                }
              break;
            }
          case 2:  // Fill the buffers with data.
            {
#ifdef __OS2__
              fdsSocks[0] = iSock;
              iErr = os2_select(fdsSocks, 1, 0, 0, ulTimeout * 1000);
              if (iErr < 1) // Error occured.
                {
                  iState = 0;
                  iErr = -1;
                  break;
                }
#elif __WINDOWS__
              FD_ZERO(&fdsSocks);
              FD_SET(iSock, &fdsSocks);
              stTimeout.tv_sec = ulTimeout;
              iErr = select(1, &fdsSocks, 0, 0, &stTimeout);
              if (iErr < 1) // Error occured.
                {
                  iState = 0;
                  iErr = -1;
                  break;
                }
#endif
              iErr = recv(iSock, szBuf1, MAX_SOCK_BUFFER - 1, 0);
              if (iErr < 1)
                {
                  iState = 0;
                  iErr = -1;
                  break;
                }
              iBeg1 = 0;
              iEnd1 = iErr;
              if (iErr == (MAX_SOCK_BUFFER - 1))  // Filled up Buffer 1.
                {
#ifdef __OS2__
                  fdsSocks[0] = iSock;
                  iErr = os2_select(fdsSocks, 1, 0, 0, ulTimeout * 1000);
                  if (iErr < 1) // Error occured.
                    {
                      iState = 0;
                      iErr = -1;
                      break;
                    }
#elif __WINDOWS__
                  FD_ZERO(&fdsSocks);
                  FD_SET(iSock, &fdsSocks);
                  stTimeout.tv_sec = ulTimeout;
                  iErr = select(1, &fdsSocks, 0, 0, &stTimeout);
                  if (iErr < 1) // Error occured.
                    {
                      iState = 0;
                      iErr = -1;
                      break;
                    }
#endif
                  iErr = recv(iSock, szBuf2, MAX_SOCK_BUFFER - 1, 0);
                  if (iErr < 1)
                    {
                      iState = 0;
                      iErr = -1;
                      break;
                    }
                  iBeg2 = 0;
                  iEnd2 = iErr;
                }
              iBuf = 1;
              iState = 3;  // Advance to the next state.
              break;
            }
          case 3:  // Look for the EOL sequence.
            {
              if ((iBuf == 1) && (iEnd1 != 0))  // Use Buffer 1 first.
                {
                  for ( ; iBeg1 < iEnd1; iBeg1++)
                    {
                      szOutBuf[idx] = szBuf1[iBeg1];   // Copy.
                      if ((szOutBuf[idx - 1] == '\r') && (szOutBuf[idx] == '\n'))
                        {
                          iBeg1++;                     // Count the char just read.
                          if (iBeg1 == iEnd1)
                            {
                              iBeg1 = iEnd1 = 0;   // Reset.
                              iBuf = 2;
                            }
                          szOutBuf[idx + 1] = '\0';    // Done. Null line.
                          iState = 4;                  // Goto cleanup & exit.
                          iEol = TRUE;
                          break;                       // Break from for loop.
                        }
                      else if (szOutBuf[idx - 1] == '\r')
                        {
                          // A real bad implementation found, bare CRs in the stream
                          szOutBuf[idx-1] = ' ';
                        }
                      else if (szOutBuf[idx] == '\n')
                        {
                          iBeg1++;                     // Count the char just read.
                          if (iBeg1 == iEnd1)
                            {
                              iBeg1 = iEnd1 = 0;   // Reset.
                              iBuf = 2;
                            }
                          szOutBuf[idx + 1] = '\0';    // Done. Null line.
                          iState = 4;                  // Goto cleanup & exit.
                          iEol = TRUE;
                          break;                       // Break from for loop.
                        }
                      idx++;                           // Advance to next spot.
                      if ((idx+1) == MAX_SOCK_BUFFER)     // Out of room.
                        {
                          if (szOutBuf[idx - 1] == '\r')
                            {
                              // push it back onto the buffer.
                              idx--;
                            }
                          else
                            {
                              iBeg1++;
                            }
                          if (iBeg1 == iEnd1)
                            {
                              iBeg1 = iEnd1 = 0;   // Reset.
                              iBuf = 2;
                            }
                          szOutBuf[idx] = '\0';
                          iState = 4;
                          break;
                        }
                    }
                  if (iBeg1 == iEnd1) iBeg1 = iEnd1 = 0;   // Reset.
                  if (iState == 3)    iBuf = 2;            // EOL not found yet.
                }
              else if ((iBuf == 2) && (iEnd2 != 0))    // Use Buffer 2.
                {
                  for ( ; iBeg2 < iEnd2; iBeg2++)
                    {
                      szOutBuf[idx] = szBuf2[iBeg2];   // Copy.
                      if ((szOutBuf[idx - 1] == '\r') && (szOutBuf[idx] == '\n'))
                        {
                          iBeg2++;                     // Count the char just read
                          if (iBeg2 == iEnd2)
                            {
                              iBeg2 = iEnd2 = 0;   // Reset.
                              iBuf = 1;
                            }
                          szOutBuf[idx + 1] = '\0';    // Done. Null line.
                          iState = 4;                  // Goto cleanup & exit.
                          iEol = TRUE;
                          break;                       // Break from for loop.
                        }
                      else if (szOutBuf[idx - 1] == '\r')
                        {
                          // A real bad implementation found, bare CRs in the stream
                          szOutBuf[idx-1] = ' ';
                        }
                      else if (szOutBuf[idx] == '\n')
                        {
                          iBeg2++;
                          if (iBeg2 == iEnd2)
                            {
                              iBeg2 = iEnd2 = 0;   // Reset.
                              iBuf = 1;
                            }
                          szOutBuf[idx + 1] = '\0';    // Done. Null line.
                          iState = 4;                  // Goto cleanup & exit.
                          iEol = TRUE;
                          break;                       // Break from for loop.
                        }
                      idx++;                           // Advance to next spot.
                      if ((idx+1) == MAX_SOCK_BUFFER)     // Out of room.
                        {
                          if (szOutBuf[idx - 1] == '\r')
                            {
                              // push it back onto the buffer.
                              idx--;
                            }
                          else
                            {
                              iBeg2++;
                            }
                          if (iBeg2 == iEnd2)
                            {
                              iBeg2 = iEnd2 = 0;   // Reset.
                              iBuf = 1;
                            }
                          szOutBuf[idx] = '\0';
                          iState = 4;
                          break;
                        }
                    }
                  if (iBeg2 == iEnd2) iBeg2 = iEnd2 = 0;   // Reset.
                  if (iState == 3)    iBuf = 1;            // EOL not found yet.
                }
              else  // Both buffers empty and still no eol.
                {
                  if (idx < MAX_SOCK_BUFFER)
                    {
                      iState = 2;  // Still room. Refill the buffers.
                    }
                  else
                    {
                      iState = 4;  // Out of room. Return.
                    }
                }
              break;
            }
          case 4:  // Cleanup and exit.
            {
              iState = 0;
              break;
            }
        } // End of switch statement.
    } // End of while loop.

  if (iToast > 0)  // Remove the telnet end of line before returning.
    {
      while (( (szOutBuf[idx] == '\r') || (szOutBuf[idx] == '\n') ) && (idx > -1))
        {
          szOutBuf[idx] = '\0';
          idx--;
        }
    }

  if (iErr < 0) return iErr;
  
  // Must add 1 to idx since idx is an index into the actual
  // array, not the number of bytes returned.
  return (idx + 1);
}
              
// ------------------------------------------------------------------
//
// ResolveName
//
// Look up the name of the peer connected to this socket.
//

int
Socket::ResolveName()
{
  struct hostent *hePeer;

  if (szPeerIp == NULL)  // Only if we don't have it already.
    {
      szPeerIp = new char[128];
      strncpy(szPeerIp, inet_ntoa(siThem.sin_addr), 128);
    }
  szPeerName = new char[128];
  hePeer = gethostbyaddr((char *)&(siThem.sin_addr),
                          sizeof(struct in_addr), AF_INET);
  if (hePeer != NULL)  // We found the ip name.
    {
      strncpy(szPeerName, hePeer->h_name, 128);
      iErr = 0;        // Good return.
    }
  else                 // No name available for this host.
    {
      strncpy(szPeerName, szPeerIp, 128);
      iErr = -1;       // Bad return.
    }

  return iErr;
}          

// ------------------------------------------------------------------
//
// SetPeerName
//
// Set the name by using the ip address instead of dns lookup.
//

int
Socket::SetPeerName()
{
  if (szPeerIp == NULL)  // Only if we don't have it already.
    {
      szPeerIp = new char[128];
      strncpy(szPeerIp, inet_ntoa(siThem.sin_addr), 128);
    }
  szPeerName = new char[128];
  sprintf(szPeerName, "[%s]", szPeerIp);
  iErr = 0;

  return iErr;
}          

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

int
Socket::Close()                      // Close this socket
{
  iBeg1 = iEnd1 = iBeg2 = iEnd2 = 0;
  iBuf = 1;
  memset(szOutBuf, 0, MAX_SOCK_BUFFER);
  memset(szBuf1, 0, MAX_SOCK_BUFFER/2);
  memset(szBuf2, 0, MAX_SOCK_BUFFER/2);
  if (szPeerIp) delete [] szPeerIp;
  if (szPeerName) delete [] szPeerName;
  szPeerIp = NULL;
  szPeerName = NULL;
  ulTimeout = 5 * 60;  // 5 minutes default.
  usPortUs = 0;
  usPortThem = 0;

  iErr = soclose(iSock);
  iSock = -1;
  return iErr;
}

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