
#include <stdio.h>
#include <sys/types.h> 
#include <sys/time.h>
#include <signal.h>

#include <fcntl.h>

#include "other.h"
#include "telnet.h"
#include "history.h"
#include "ttyexec.h"


// 
// class Telnet    is a simple interface to telnet.
//
// A connect attept is Open'ed, and eventually fails, or is Close'ed by the
// user or by Telnet::~Telnet.
// Open'ing closes any current connection.
// 
// Telnet keeps track of whats going on by watching for telnet messages,
// and timing the delay between writes and input from the host.
// 
// Telnet message watching:
//   Messages are watched for by  red , which is called by  read .
//   TELNET STATUS MAY NOT BE CURRENT,
//     as read is called only by the user, and there may be a buffered
//     message from telnet which the user hasnt read up to yet.
//     Thus, if the user stops read'ing, Telnet could consider itself
//     connected forever, despite the connection failure or even telnet
//     process termination.
//     Also, if the user never starts reading, Telnet will never notice
//     that it has connected.
// 
// Response timeout:
//   Some systems, such as BARTON, have a failure mode where connection
//     succeeds, but all input is ignored.
//   The time between a response_expected(), and the next succesfull read,
//     is checked.  If this is too large, Telnet considers itself failed.
//   A successful read does not mean that the preceeding write has not
//     been ignored because the read characters may have been sent previously.
//
// Potential Problems:
//   There is no check that the host has dealt with particular characters.
//   Thus if the user write's characters faster than the host is willing
//   to deal with them, or if it flushes its input buffer at some point,
//   the user loses characters.
// 
// FUTURE:
//  + If telnet had its own input buffer, it monitor telnet messages in
//    a more timely and robust way.
//  + Telnet should catch SIGCHILD signals, to watch for a telnet hanging
//    or dieing.
// 

static int hist_size =100;
extern int gpid;

Telnet::Telnet (char* host, int rsptimeout)
                : tno(*(new History(hist_size)))
{
  tno.Initialize();
  Open(host, rsptimeout);
}

Telnet::~Telnet() {
  delete &tno;
  Close();
};


void Telnet::Open (char* host, int rsptimeout)
{
  Close();
  open_tfex();

  response_timeout = rsptimeout;
  char *foo[3];
  foo[0] = "/bin/telnet";
  foo[1] = host;
  foo[2] = NULL;
  int res = tfex->ForkExecv(foo[0],foo);
  if(res==-1) { result = _Bug;
		fprintf(stderr,"\n\nERROR:Telnet::Open FAILED (%s)\n\n",
			BugMessage()); fflush(stderr);
		return; }
  pid = res;
  gpid = res;
  state  = _Connecting;
  result = _none;
  time_of_last_responseless_write = current_time();
}
void Telnet::Close ()
{
  if(pid) {
    int res = kill(pid,SIGKILL);
    pid = 0;

    if(res==-1) {
      if(errno==ESRCH) errno = 0;
      else perror("Telnet::Close bug");
    }
  }

  state = _Closed;
  close_tfex();
}

bool Telnet::isClosed()        { update(); return state ==_Closed; }
bool Telnet::isConnecting()    { update(); return state ==_Connecting; }
bool Telnet::isConnected()     { update(); return state ==_Connected; }
bool Telnet::wasUnknown()      { update(); return result==_UnknownHost; }
bool Telnet::wasUnable()       { update(); return result==_Unable; }
bool Telnet::wasTimedOut()     { update(); return result==_TimedOut; }
bool Telnet::wasRefused()      { update(); return result==_Refused; }
bool Telnet::wasClosed()       { update(); return result==_ClosedByHost; }
bool Telnet::wasNotResponding(){ update(); return result==_ResponseTimeOut; }
bool Telnet::hadBug()          { update(); return result==_Bug; }

char* default_BugMessage = "\n<No bug description at hand>\n";
char* Telnet::BugMessage ()
{
  if(tfex) return  tfex->ErrorMessage;
  else     return  default_BugMessage;
}

int Telnet::theReadFileDescriptor ()
{
  if(state!=_Closed) return tfex->our_filedescriptor();
  else   return -1;
}

int Telnet::read (char* buf, int nchars)
{
  if(state==_Closed)     return -1;

  int cnt = tfex->read(buf,nchars);
  if(cnt==0) time_check();
  if(cnt >0) time_of_last_responseless_write =0;

  red(buf,nchars,cnt);

  if(cnt <0) {
    if(errno==EWOULDBLOCK) { errno =0; return 0; }
    else { Close(); result = _Bug; return -1; }
  }

  return cnt;
}

int Telnet::write (char* buf, int nchars)
{
  if(time_of_last_responseless_write>0) time_of_last_responseless_write =0;

  if(state!=_Connected) return -1;

  time_check(); if(state!=_Connected) return -1;

  int cnt = tfex->write(buf,nchars);
  wrote(buf,nchars,cnt);
  return cnt;
}


bool Telnet::read_possible ()
{
  if(state==_Closed) return 0;
  return 1;
}


int Telnet::IncrementalWrite (char* buf, int nchars)
{
  int ccount =0;
  for(int i=0;i<nchars;i++) {
    int res = write(&buf[i],1);
    if(res==-1) return -1;
    if(res!=1)  return -1;
    ccount += res;
  }
  return ccount;
}
int Telnet::IncrementalRead (char* buf, int nchars)
{
  int ccount =0;
  for(int i=0;i<nchars;i++) {
    int res = read(&buf[ccount],1);
    if(res==-1) return -1;
    if(res!= 1)  break;
    ccount += res;
  }
  return ccount;
}


void Telnet::response_expected ()
{
  if(!time_of_last_responseless_write)
    time_of_last_responseless_write = current_time();
}

char red_buf[1000];
void Telnet::red (char* buf, int nchars, int cnt)
{
  if(state==_Closed) return;

  if(cnt>hist_size-55) {
    int blksz = hist_size-55;
    strncpy(red_buf,buf,cnt);
    for(int i=0;i<cnt;i+=blksz)
      red(&red_buf[i],nchars,(blksz+i<cnt)?blksz:cnt-i);
    return;
  }

  if(cnt <1) return;

  History& hist = tno;
  hist.Append(buf,cnt);
  if( hist.Match("Escape character is '^]'.",cnt) )
    { state = _Connected; return; }
  if( hist.Match(": unknown host",cnt) )
    { Close(); result = _UnknownHost; return; }
  if( hist.Match("connect: Connection refused",cnt) )
    { Close(); result = _Refused; return; }
  if( hist.Match("telnet: Unable to connect to remote host",cnt) )
    { Close(); result = _Unable; return; }
  if( hist.Match("connect: Connection timed out",cnt) )
    { Close(); result = _TimedOut; return; }
  if( hist.Match("Connection closed by foreign host.",cnt) )
    { Close(); result = _ClosedByHost; return; }
  if( hist.Match("telnet>",cnt) )
    { Close(); result = _Bug; return; }
}

void Telnet::update ()
{
}


long Telnet::current_time()
{
    struct timeval tval;
    struct timezone_hack tzone;
    gettimeofday(&tval,(struct timezone*)&tzone);
    return tval.tv_sec;
}
void Telnet::time_check ()
{
  if(time_of_last_responseless_write != 0 &&
     (current_time() - time_of_last_responseless_write) > response_timeout)
    {
      Telnet::Close(); this->result = _ResponseTimeOut;
    }
}

void Telnet::open_tfex ()
{
  tfex = new TtyForkExec;
  fcntl(tfex->our_filedescriptor(),F_SETFL,FNDELAY);
}
void Telnet::close_tfex ()
{
  if(tfex) delete tfex;
  tfex = NULL;
}

void telnet_kill_child()
{
  kill(gpid,SIGKILL);
  exit(1);
}
