Logo Search packages:      
Sourcecode: papaya version File versions  Download package

Socket.cpp

#ifdef WIN32
#include <winsock2.h>
#include <time.h>
#include <io.h>
#endif

#include <stdio.h> // perror, sprintf
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include <errno.h> // errno
#include <fcntl.h> // fcntl()
#include <ctype.h> // isalpha()

#ifndef WIN32
#include <sys/ioctl.h>
#include <unistd.h> // close(), fcntl()
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h> // gethostbyname()
#include <sys/time.h>
#endif

#include "Prefs.h"
#include "FilterHandler.h"
#include "mudclient.h"
#include "Socket.h"
#include "Connection.h"

#include "EOLFilter.h"
#include "TeloptFilter.h"
#include "PromptFilter.h"

#define BUFFER_SIZE 32768


Socket::Socket(Connection * c)
{
    fd = 0;
    conn = c;

    inputFilters.addFilter(new EOLFilter(c));
    inputFilters.addFilter(new TeloptFilter(c));
    inputFilters.addFilter(new PromptFilter(c));
        
    stats_written = 0;
    stats_read_raw = 0;
    stats_read_filtered = 0;
    time(&stats_connected);

    is_connected = false;
}

Socket::Socket(Connection * co, int f, int c, int d)
{
    fd = f;
    conn = co;

    inputFilters.addFilter(new EOLFilter(co));
    inputFilters.addFilter(new TeloptFilter(co));
    
    stats_written = 0;
    stats_read_raw = 0;
    stats_read_filtered = 0;
    time(&stats_connected);
}

Socket::~Socket()
{    
}

/**
 * Resolves the given hostname/IP address, placing the appropriate data into
 * the right structure.
 *
 * Return: -1 on failure
 *          0 is IPv4
 *          1 is IPv6
 */



bool is_ip_address(char * addr) {



      char * ptr = addr;



      while (*ptr != '\0') {

            if (!isdigit(*ptr) && *ptr != '.')

                  return false;

            ptr++;

      }



      return true;

}


#ifdef IPV4
int Socket::resolveAddress(char * host, struct sockaddr_in * ip4)
#else
int Socket::resolveAddress(char * host, struct sockaddr_in * ip4, struct sockaddr_in6 * ip6)
#endif
{
#ifdef IPV4
  struct in_addr inet_address;
  struct hostent *hp;


  if (!is_ip_address(host)) {

    hp = gethostbyname(host);
    if (!hp)
      return -1;

    memcpy ((char *)&inet_address, hp->h_addr, sizeof(struct in_addr));
  }
  else
    inet_address.s_addr = inet_addr(host);

  ip4->sin_addr = inet_address;
  return 0;

#else

  struct addrinfo req, *ai;
  const char * service = "telnet";
  
  int i = 0;
  memset(&req, 0, sizeof(struct addrinfo));
  
  if ((i = getaddrinfo(host, service, &req, &ai))) {
    perror("getaddrinfo");
    return -1;
  }
  
  if (ai->ai_family == AF_INET6) {
    memcpy(ip6, ai->ai_addr, sizeof(struct sockaddr_in6));
    freeaddrinfo(ai);
    return 1;
  }
  
  if (ai->ai_family == AF_INET) {
    memcpy(ip4, ai->ai_addr, sizeof(struct sockaddr_in));
    freeaddrinfo(ai);
    return 0;
  }

  freeaddrinfo(ai);
  return -1;
#endif
}

int Socket::create(char * host, int port)
{
  struct sockaddr_in sock4;
#ifndef IPV4
  struct sockaddr_in6 sock6;
#endif
  int type;

#ifdef IPV4
  type = resolveAddress(host, &sock4);
#else
  type = resolveAddress(host, &sock4, &sock6);
#endif

  if (type == -1)
    return -1;

#ifdef IPV4
  fd = socket(AF_INET, SOCK_STREAM, 0);
#else
  if (type == 0) { // IPv4
        fd = socket(AF_INET, SOCK_STREAM, 0);
  } else {
    fd = socket(AF_INET6, SOCK_STREAM, 0);
  }
#endif

  if (fd == -1)
    return -1;

#ifdef IPV4
  sock4.sin_family = AF_INET;
  sock4.sin_port = htons(port);
#else
  if (type == 0) { // IPv4
    sock4.sin_family=AF_INET;
    sock4.sin_port = htons(port);
  } else { // IPv6
    sock6.sin6_family = AF_INET6;
    sock6.sin6_port = htons(port);
  }
#endif

  /* Non-blocking socket. */
#ifdef WIN32
  unsigned long enable = 1;
  int res = ioctlsocket(fd, FIONBIO, &enable);
#else
  int res = fcntl(fd, F_SETFL, O_NONBLOCK);
#endif
  if (res == -1) {
    perror("Cache::connectToHost(char *, int)");
    exit(1);
  }

  // This is a non-blocking connect.  Should be interesting to handle.

#ifdef IPV4
  if (connect(fd, (struct sockaddr *)&sock4, sizeof(sock4)) == -1) {
    
#ifdef WIN32
    int newerr = WSAGetLastError();
    
    if (newerr == WSAEINPROGRESS || newerr == WSAEWOULDBLOCK)

        return -WSAEINPROGRESS;


#else
    if (errno == EINPROGRESS)
      return -EINPROGRESS;
#endif
  
    close(fd);
    return -1;
  }
#else

  if (type == 0) { // IPv4
    if (connect(fd, (struct sockaddr *)&sock4, sizeof(sock4)) == -1) {
      if (errno == EINPROGRESS) {
      return -EINPROGRESS;
      }
      
      close(fd);
      return -1;
    }
  } else { // IPv6
    if (connect(fd, (struct sockaddr *)&sock6, sizeof(sock6)) == -1) {
      if (errno == EINPROGRESS)
          return -EINPROGRESS;
      
      close(fd);
      return -1;
    }
  }
#endif


  is_connected = true;
  return fd;
}

/**
 * Retval of -1 indicates error.
 * 0 indicates still connecting.
 * 1 indicates connected.
 */

int Socket::connected()
{    
    if (is_connected)
      return 1;

    fd_set rfds, wfds, efds;
    struct timeval tv;
    int retval;
    
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);

      FD_ZERO(&efds);

    

      FD_SET(fd, &rfds);

      FD_SET(fd, &wfds);

      FD_SET(fd, &efds);
    
    // Wait up to 200us.
    tv.tv_sec = 0;
    tv.tv_usec = 200;
    
    retval = select(fd+1, &rfds, &wfds, &efds, &tv);
    
    if (retval >= 1)
      is_connected = true;

    return retval;
}

/**
 * This function reads from the remote host into a Buffer.  It's called by
 * read, and readLine when it's detected that the buffer is empty.
 */

int Socket::internal_read()
{
  int retval = 0;
  int result = 0;
  
  int success_read = 0;
  
#ifdef WIN32
  WSASetLastError(0);
#endif
  
#ifdef SOCKET_DEBUG
  debug("In internal_read.\n");
#endif
  
  // We only use this internally, so we can make do with one global buffer..
  static Buffer raw_input;
  raw_input.reset();
  
  while (1)
    {
      char *dest = raw_input.prepare_space(BUFFER_SIZE);
      result = recv(fd, dest, BUFFER_SIZE, 0);
      if (result > 0)
      raw_input.consume_space(result);

#ifdef WIN32
      errno = WSAGetLastError();
      if (result == -1 &&  errno == WSAEWOULDBLOCK)
      {
        // We may have read some data the first time around.
#ifdef SOCKET_DEBUG
        debug("Nothing to read yet.\n");
#endif
        return retval;
      }
#else
      if (result == -1 && errno == EAGAIN)
      {
        // We may have read some data the first time around.
#ifdef SOCKET_DEBUG
        debug("Nothing to read yet.\n");
#endif
        return retval;
      }
#endif
      
    if (result == -1)
      {
            perror("internal_read");
        if (!success_read)
          return -1;

        return retval;
      }
        
#ifdef WIN32
      WSASetLastError(0);
#endif
      
      if (result == 0)
      {
        // Return retval if we read any data this time, and leave detecting
        // dead connection for next read run.
        
#ifdef SOCKET_DEBUG
        debug("Result was 0.\n");
#endif
        if (success_read)
          return retval;
        
        return -1;
      }
      
      success_read = 1;

      // Filter input
      stats_read_raw += result;
      int old_len = inbuf.getLength();
      inputFilters.processFilters(raw_input, inbuf);
      stats_read_filtered += inbuf.getLength() - old_len;
    }
  
#ifdef SOCKET_DEBUG
  debug("Returning %d.\n", result);
#endif
  return result;
}

/**
 * Reads a line of data from the socket into the array pointed at by dest,
 * up to a maximum of b characters.  The array must be large enough to
 * hold b characters, plus the terminating nul character.
 */

int Socket::readLine(int b, char * dest)
{
    char * ptr = NULL;
    char * pc = NULL;
    int c = 0;

#ifdef SOCKET_DEBUG
    debug("Entered readLine\n");
#endif
    
    if (internal_read() == -1) {
      if (inbuf.getLength() == 0) {
      return -1;
      }
    }
    
#ifdef SOCKET_DEBUG
    debug("Checking inbuf length.\n");
#endif
    
    if (inbuf.getLength() == 0)
      return 0;

    ptr = inbuf.getText();

    if (!ptr)
      printf ("CRITICAL: inbuf.getText() is NULL, whilst inbuf.getLength() != 0\n");


    pc = (char *)memchr(ptr, '\n', inbuf.getLength());

    char * pc2 = (char *)memchr(ptr, '\r', inbuf.getLength());
    if (!pc) {
      pc = (char *)memchr(ptr, MAGIC_PROMPT, inbuf.getLength()); // look for input to the next GA
    }
    if (pc2 && pc2 < pc)
      pc = pc2;

    if (pc)
      c = pc - ptr + 1;
    else {
      c = inbuf.getLength();
    }

    if (b < c)
      return read(b, dest);

    return read(c, dest);
}

/**
 * Reads b bytes from the socket into the array pointed to by dest.
 * The array must be large enough to hold b bytes, plus a terminating
 * NUL character.
 *
 * This isn't guaranteed to read that many bytes, but it'll do its best.
 *
 * Returns the number of bytes read, or -1 if some error occured.
 */

int Socket::read(int b, char * dest) {

  int len = 0;

  // Ignore the return.
  internal_read();

  len = inbuf.getLength();

  if (b > len) {
    b = len;
  }

  // Copy b bytes from the buffer.
  memcpy(dest, inbuf.getText(), b);
  // Strip b bytes off the front of the buffer.
  inbuf.strip(b);

  // Stick a NUL on the end of dest.

  dest[b] = '\0';

  // Return how many bytes we've gotten out
  return b;
}

int Socket::write(Buffer &b)
{
    return write(b.getText(), b.getLength());
}

int Socket::write(char * src)
{
    return write(src, strlen(src));
}

int Socket::write(char * src, int len)
{
#ifdef WIN32
    WSASetLastError(0);
#endif

    Buffer tmp;
    tmp.append(src, len);
    outputFilters.processFilters(tmp, outbuf);
    return flush();
}

/**
 * Sends whatever is in the output buffer down the socket.
 *
 * Returns the number of bytes written or -1 if some error occured.
 */

int Socket::flush() {

  if (conn->isClosed())
        return 0;

#ifdef WIN32
      WSASetLastError(0);
#endif


  char * text = outbuf.getText();

  int len = outbuf.getLength();



  int result = send(fd, outbuf.getText(), outbuf.getLength(), 0);

  if (result == -1) {

    perror("Socket::flush()");
    return -1;
  }

  if (result < outbuf.getLength())
    outbuf.strip(result);
  else
    outbuf.reset();

  stats_written += result;

  return result;
}

/**
 * Closes our connection to the remote host.
 */

int Socket::dispose() {
#ifdef WIN32
  WSASetLastError(0);
#endif
  int result = close(fd);
  return result;
}

Buffer * Socket::getOutbuf()
{
    return &outbuf;
}

/**
 * Is the other end of the connection echoing for us?
 */

bool Socket::echo() {

  TeloptFilter * telnetFilter =
    static_cast<TeloptFilter*>(inputFilters.findFilter("TelnetOptions"));

  if (!telnetFilter)
    return false;

  if (telnetFilter->get_remote(Telnet::ECHO))
    return true;

  return false;
}

int Socket::compression()
{

    Filter *compressFilter = inputFilters.findFilter("Compression");

    if (!compressFilter) {
      return 0;
    }

    TeloptFilter *telnetFilter = 
      static_cast<TeloptFilter*>(inputFilters.findFilter("TelnetOptions"));

    if (telnetFilter->get_local(Telnet::COMPRESS)) {
      return 1;
    }

    if (telnetFilter->get_local(Telnet::COMPRESS2)) {
      return 2;
    }

    return 0; // eh?
}
 
bool Socket::getTelnetOption(Telnet::option_t option) {

  TeloptFilter * telnetFilter =
      static_cast<TeloptFilter*>(inputFilters.findFilter("TelnetOptions"));

  return telnetFilter->get_local(option);
}

void Socket::getStats(int * wr, int * read_raw, int * read_filtered, int * connected) {
  *wr = stats_written;
  *read_raw = stats_read_raw;
  *read_filtered = stats_read_filtered;


  time_t current_time;
  time(&current_time);

  *connected = (int) (current_time - stats_connected);
}

int Socket::getCookie() {
   return -1;
}

int Socket::restart(char *, int) {
  return 0;
}


Generated by  Doxygen 1.6.0   Back to index