#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <stdlib.h>
#include <sys/select.h>

#ifndef __GNU_LIBRARY
# include "fixes.h"
#endif

#define BUFLEN 16384

void usage();
void debug();

unsigned short DEBUG_LEVEL=0;

int main(argc, argv, envp)
int argc;
char **argv;
char **envp;
{
  int rval, sockfd;
  struct hostent *host_ent;
  struct sockaddr_in dest_sin;
  struct servent *srvp;
  int e_save;
  int success;
  char **addrlist;
  fd_set read_fds, write_fds, except_fds;
  char buf[BUFLEN];
  char s[BUFLEN];
  int mlen;

  if (argc == 4)
  {
    if (strncmp(argv[2], "-d\0", 3) !=0) usage(argv[0], "Invalid option.");
    rval=(int) strtol(argv[3], NULL, 0);
    if (errno != 0)
    {
      fprintf(stderr, "%s: Invalid debug level %s\n", argv[0], argv[3]);
      exit(2);
    }
    else
    {
      DEBUG_LEVEL=rval;
    }
  }
  else if (argc != 2)
  {
    usage(argv[0], "Incorrect arguments.");
  }
  /* Try as host name */
  if (host_ent = gethostbyname(argv[1])) {
    dest_sin.sin_family = host_ent->h_addrtype;
    if (host_ent->h_length > sizeof( dest_sin.sin_addr)) {
      fprintf(stderr, "%s: address length wrong.\n", argv[0]);
      exit(2);
    }
    memcpy(&dest_sin.sin_addr, host_ent->h_addr_list[0], host_ent->h_length);
  /* Try as IP address */
  } else {
    if(dest_sin.sin_addr.s_addr = inet_addr(argv[1])) {
      fprintf(stderr, "%s: cannot find address for '%s'.\n", argv[0], argv[1]);
      exit(2);
    }
  }

  /* Get service information */
  if ((srvp = getservbyname("demo", "tcp")) == 0) {
    fprintf(stderr, "%s: cannot find port number for demo service.\n", argv[0]);
    exit(3);
  } else {
    dest_sin.sin_port = srvp->s_port;
  }

  /* Iterate over address list for host and try to connect */
  success=0;
  for(addrlist = host_ent->h_addr_list; *addrlist != NULL; addrlist++)
  {
    memcpy((caddr_t)&dest_sin.sin_addr, (caddr_t)*addrlist, sizeof(dest_sin.sin_addr));
    if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    {
      fprintf(stderr, "%s: Could not create socket.\n", argv[0]);
      exit(4);
    }
    if (connect(sockfd, (struct sockaddr *)&dest_sin, sizeof(dest_sin)) < 0)
    {
      e_save = errno;
      (void) close(sockfd);
      errno = e_save;
      fprintf(stderr, "%s: Failed attempt to %s.\n", argv[0],
		inet_ntoa(dest_sin.sin_addr));
      perror("Socket error");
    } else {
      snprintf(s, BUFLEN, "%s: Succeeded to %s (%d).", argv[0],
		inet_ntoa(dest_sin.sin_addr), dest_sin.sin_addr);
      debug(5, argv[0], s);
      success++;
      break;
    }
  }
  if (success == 0)
  {
    fprintf(stderr, "%s: Failed to connect to %s.\n", argv[0], argv[1]);
    exit(5);
  }
  printf("%s: Successfully connected to %s at %s on FD %d.\n", argv[0], argv[1],
	inet_ntoa(dest_sin.sin_addr), sockfd);
  while(1)
  {
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    FD_ZERO(&except_fds);
    FD_SET(fileno(stdin), &read_fds);
    FD_SET(fileno(stdin), &except_fds);
    FD_SET(sockfd, &read_fds);
    FD_SET(sockfd, &except_fds);
    select(fileno(stdin) > sockfd ? fileno(stdin)+1 : sockfd+1,
	&read_fds, &write_fds, &except_fds, NULL);
    if (FD_ISSET(fileno(stdin), &except_fds))
    {
      if (feof(stdin))
      {
        close(sockfd);
        printf("End of file detected, exiting.\n");
        exit(0);
      }
      else
      {
        perror("Exception on STDIN");
        printf("Exiting.\n");
        exit(6);
      }
    }
    if (FD_ISSET(sockfd, &except_fds))
    {
      perror("Exception on socket.");
      printf("Exiting.\n");
      exit(7);
    }
    if (FD_ISSET(sockfd, &read_fds))
    {
      /* Read from socket and display to user */
      mlen = recv(sockfd, (void *)buf, BUFLEN-1, MSG_DONTWAIT);
      buf[mlen]=0;
      if (mlen == 0)
      {
        fprintf(stderr, "Remote site has apparently hung up on us.\n");
	exit(0);
      }
      else
      {
        printf("Received %d bytes: %s", mlen, buf);
      }
    }
    if (FD_ISSET(fileno(stdin), &read_fds))
    {
      fgets(buf, BUFLEN, stdin);
      snprintf(s, BUFLEN, "Sent %d octets to server.", 
        send(sockfd, (void *)buf, (size_t) strnlen(buf, BUFLEN), 0));
      debug(5,argv[0],  s);
    }
  } 
  exit(0);
}

void usage(prog, string)
char *prog;
char *string;
{
  fprintf(stderr, "Error: %s\n", string);
  fprintf(stderr, "Usage: %s <server>\n", prog);
  exit(1);
}

void debug(lvl, prog, string)
unsigned short lvl;
char *prog;
char *string;
{
  if (lvl <= DEBUG_LEVEL)
  {
    fprintf(stderr, "%s: %s\n", prog, string);
  }
}

