/* Most of the routines in this file are just quick hacks to get things
 * running. The general idea is that term is only used when the local
 * gethostbyname fails to find anything.  Many of these routines are just
 * quick hacks and can be greatly improved.
 */

/* Everything that follows is an attempt to allow term to easier porting
 * of software to term.  The functions term_connect, term_rcmd, and 
 * term_gethostbyname are written such that ported routines will work
 * even with ordinary network connections.  Unfortunately, I haven't
 * figured out how to make term_bind network invisable yet.
 */

#define I_IOCTL
#define I_SYS
#define I_GETOPT
#define I_SOCKET
#include "includes.h"
#ifdef NO_VSPRINTF
#define va_alist a1,a2,a3,a4,a5,a6,a7,a8,a9
#define va_dcl long a1,a2,a3,a4,a5,a6,a7,a8,a9;
#define vsprintf(buf,fmt,v) sprintf((buf),(fmt),a1,a2,a3,a4,a5,a6,a7,a8,a9)
#define va_list int
#define va_start(v)
#define va_end(v)
#define USE_VARARGS
#else
#ifdef USE_VARARGS
#include <varargs.h>
#else
#include <stdarg.h>
#endif
#endif
#include <string.h>
#include <sys/stat.h>
#include <arpa/inet.h>

#include "client.h"
#include <signal.h>

int term_debug = -1;

/* This is used for term_gethostbyname */
static int remote_connect=0;

/* This is needed for term_getsockname. */
struct sockinfo_type {
  int sock;
  int client;
};
static struct sockinfo_type sockinfo[MAX_CLIENTS];

/* To keep the lookups accurate, I need this.
 */

static void close_fd(int fd){
  int i;

  if(fd>=0)
    for(i=0;i<MAX_CLIENTS;i++)
      if(fd==sockinfo[i].sock)
    sockinfo[i].sock = -1;
}

/* Convert a string to a hostname address 
 */

static struct hostent *str_to_hostent(char *addr){
  static unsigned long int laddr=0;
  static struct hostent hs;
  static char host[258];
  static char *caddr[2]={NULL,NULL};
  static char *host_aliases[2]={NULL,NULL};

  host_aliases[0]=(char *)hs.h_name;

  hs.h_name=host;

  sscanf(addr,"%lu %s",&laddr,hs.h_name);
  if(! laddr) return NULL;

  laddr = htonl(laddr);
  caddr[0]=(char *) &laddr;

/* I'm using 0 as remote host. */

  hs.h_aliases=host_aliases;
  hs.h_addr_list = caddr;
  hs.h_length=sizeof(unsigned long int);
  hs.h_addrtype=AF_INET; 
  return &hs;
}

/* Convert a string to a socket address 
 */

static struct sockaddr *str_to_sockaddr(char *addr){
  static struct sockaddr_in name_in;
  int j;
  char port[59], *u, *v;

  strcpy(port,addr);
  for(j=0;j<3;j++) *strchr(port,',')='.';
  u=strchr(port,',');
  *u='\0';
  v=strchr(++u,',');
  *v='\0';

  name_in.sin_family = AF_INET;
  name_in.sin_port = htons((atoi(u)<<8)+atoi(++v));
  name_in.sin_addr.s_addr = inet_addr(port);
  return (struct sockaddr *)&name_in;
}   


  
/* This is a client lookup for sockets neccissary for term_accept 
 * and term_sendto
 */

static int store_sockinfo(int s, int remclient) {
  static int count = -1;
  int i;

  if(count<0){
    count=0;
    for(i=0;i<MAX_CLIENTS;i++) {
      sockinfo[i].sock = -1;
    };
  };
  for(i=0;i<MAX_CLIENTS;i++)
    if(sockinfo[i].sock<0||s == sockinfo[i].sock) break;
  if(i==MAX_CLIENTS){
    i = ++count % MAX_CLIENTS;
    count = i;
  };
  sockinfo[i].sock=s;
  sockinfo[i].client=remclient;
  return 0;
}


/* This is a lookup routine so I can return the remote peer name instead
 * of the local name.
 */

int term_getpeername(int S, struct sockaddr *name, int *namelen){
  int i,s;

  if(S<0) return -1;

  for(i=0;i<MAX_CLIENTS;i++)
    if(S==sockinfo[i].sock){
    if(namelen != NULL)
      *namelen=(*namelen>sizeof(struct sockaddr_in)) ? sizeof(struct sockaddr_in) : *namelen;
    /* Only open the connection once for a client! */
    if((s = connect_server(0)) < 0) {
      fprintf(stderr, "Can't connect to term server \n");
      return getpeername(S,name,namelen);
    }
    if(send_command(s, C_GETPEERNAME, 0, "%d", sockinfo[i].client) < 0) {
      fprintf(stderr, "C_GETPEERNAME: %s\n", command_result);
      close(s);
      return getpeername(S,name,namelen);
    }
    close(s);  
    if(name != NULL && namelen != NULL){
      *namelen = ( *namelen < sizeof(struct sockaddr) ) ? *namelen : sizeof(struct sockaddr);
      memcpy(name,str_to_sockaddr(command_result),*namelen);
      return 0; 
    }
  };
  return getpeername(S,name,namelen);
}    


/* This is a lookup routine so I can return the remote socket name instead
 * of the local name.
 */

int term_getsockname(int S, struct sockaddr *name, int *namelen){
  int i,s;

  if(S<0) return -1;

  for(i=0;i<MAX_CLIENTS;i++)
    if(S==sockinfo[i].sock){
    /* Only open the connection once for a client! */
    if ((s = connect_server(0)) < 0) {
      fprintf(stderr, "Can't connect to term server \n");
      return getsockname(S,name,namelen);
    }
    if (send_command(s, C_GETSOCKNAME, 0, "%d", sockinfo[i].client) < 0) {
      fprintf(stderr, "C_GETSOCKNAME: %d %d : %s\n", S, sockinfo[i].client, command_result);
      close(s);
      return getsockname(S,name,namelen);
    }
    close(s);  
    if(name != NULL && namelen != NULL){
      *namelen = (*namelen>sizeof(struct sockaddr_in)) ? sizeof(struct sockaddr_in) : *namelen;
      memcpy(name,str_to_sockaddr(command_result),*namelen);
      return 0; 
    };
  };
  return getsockname(S,name,namelen);
}    


/* For term gethostbyname() is executed on the remote machine when
 * the connection is established.  So here, I just list the hostname
 * in a table.  I list the index as 0.0.0.index.  Since I doubt that
 * 0.0.0.index is used by any host in the world as an IP # this should
 * allow routines to work with and without term.  (NOTE: This limits
 * use to 255 term hosts listed in each program.  If you access more
 * than that, the original hostnames will be replaced.)
 */

struct hostent *term_gethostbyname(const char *host){
  static char hostname[259];
  static struct hostent *hp;
  int s = -1;

  if((hp=gethostbyname(host))!=NULL)
    return hp;

  /* Copy the passed-in name, to make sure it doesn't get munged */
  if (host != NULL){
    if(!strcmp(host,"remotehost"))
      hostname[0] = '\0';
    else
      strncpy(hostname,host,256);
  }else
    hostname[0] = '\0';
  
  /* Only open the connection once for a client! */
  if ((s = connect_server(0)) < 0) {
    fprintf(stderr, "Can't connect to term server \n");
    return NULL;
  }
  if (send_command(s, C_GETHOSTNAME, 0, "%s", hostname) < 0) {
    fprintf(stderr, "C_GETHOSTNAME: %s\n", command_result);
    close(s);
    return NULL;
  }
  close(s);  
  return str_to_hostent(command_result);
}

struct hostent *term_gethostbyaddr(char *addr, int len, int type){
  struct hostent *hs;
  char host[59];

  if((hs=gethostbyaddr(addr, len, type))!=NULL)
    return hs;

  if(type != AF_INET) return NULL;

  if(!addr || len!=sizeof(unsigned long int)){
    host[0]='\0';
  }else{
    sprintf(host,"%u.%u.%u.%u",addr[0],addr[1],addr[2],addr[3]);
  };
  return term_gethostbyname(host);
}

int term_shutdown(int fd,int how){

  close_fd(fd);
  return shutdown(fd,how);
}

int term_close(int fd){

  close_fd(fd);
  return close(fd);
}   


/* For term connections, listen doesn't need to do anything.
 */

int term_listen(int fd, int backlog){
  int i;

  if(fd>=0)
    for(i=0;i<MAX_CLIENTS;i++)
      if(fd==sockinfo[i].sock){
    return 0;
  };
  return listen(fd,backlog);
}


/* OK now lets try redirecting socket binding.  I'm at a lost in decided
 * how to make this work for both term and non-term connections.  Here is
 * my current algorithm to redirect AF_INET ports:
 *
 * - If a non-zero port # the service must be listed as both "tcp"
 *   and "term" in /etc/service.
 * - If it is a zero port #, then the port is only redirected if the same
 *   program has already opened a term connection to a remote host.
 *
 */

int term_bind(int S, struct sockaddr *my_addr, int addrlen){
  int i,s,iport,rclient=-1;
  struct sockaddr_in *name_in;
  char port[59];
  struct servent *service;

  name_in=(struct sockaddr_in *) my_addr;
  if(S<0
    || (!remote_connect&&!name_in->sin_port)
    || name_in->sin_family!=AF_INET){
    return bind(S,my_addr,addrlen);
  };
  if((iport=name_in->sin_port)!=0){
    if((service=getservbyport(name_in->sin_port,"tcp"))==NULL){
      return bind(S,my_addr,addrlen);
    };
    if((service=getservbyname(service->s_name,"term"))==NULL){
      return bind(S,my_addr,addrlen);
    };
    iport=ntohs(service->s_port);
  }; 
  if ((s = socket_connect_server(S,term_server)) <0) {
    if(term_debug>=0) perror("Term: Couldn't open term");
    i = -1;
  }else if((i=send_command(s, C_STATS, 0, "%d", -6)) <0) {
    perror("Term: C_STATS failed");
  }else{
    rclient=atoi(command_result);
    if((i=send_command(s,C_BINDN,0,"%d",iport)) <0) { 
      perror("Term: C_BINDN");
    }else if((i=read(s, port, 58))<0){
      perror("Term: read port");
    }else
      store_sockinfo(s, rclient);
  };
  if(i<0){
    if(s >= 0) close(s);
    return i;
  };
  return 0;
}


/* Finially for term connections, accept simply continues where
 * bind left off.
 */

int term_accept(int pending, struct sockaddr *addr, int *addrlen){
  int j=MAX_CLIENTS,s,i = -1;
  char port[59];
  int size_in;
  struct sockaddr_in sock_in;

  if(pending>=0)
    for(j=0;j<MAX_CLIENTS;j++)
      if(pending==sockinfo[j].sock) break;

  if(j==MAX_CLIENTS || addr == NULL || addrlen == NULL )
    return accept(pending, addr, addrlen);

  if(read(pending,port,10)<0){
    perror("Term: read port");
    return -1;
  };
 
  size_in = sizeof(sock_in);
  if (getsockname(pending, (struct sockaddr *) &sock_in, &size_in) < 0) {
      perror("Term: socket: getsockname");
      term_shutdown(pending,2);
      return -1;
  };

  if ((s = socket(AF_INET, SOCK_STREAM, 0))<0) {
    perror("Term: term_bind socket:");
    i = -1;
  }else{
    sockinfo[j].client=atoi(port);
    if((i = socket_connect_server(s,term_server)) <0) {
      perror("Term: Couldn't open term");
    }else if((i=send_command(s, C_STATS, 0, "%d", -6))<0) {
      fprintf(stderr, "Term: Failed to get remote client number.\n");
      fprintf(stderr, "Reason given: %s\n", command_result);
    }else{
      store_sockinfo(s, atoi(command_result));
      if((i=send_command(s, C_ACCEPT, 0, "%d", sockinfo[j].client))<0){
        fprintf(stderr,"Term: C_ACCEPT: %d %s\n",sockinfo[j].client,command_result);
      }else{
        *addrlen = (*addrlen>sizeof(struct sockaddr)) ? sizeof(struct sockaddr) : *addrlen;
        memcpy(addr,str_to_sockaddr(command_result),*addrlen); 
  
        send_command(s, C_DUMB, 1, 0);
        return s;
      };
    };
  };
  if(s >= 0) close(s);
  perror("Term: unknown");
  return -1; 
}


int term_connect(int s, struct sockaddr *addr, int addrlen){
  int i = -1;
  struct sockaddr_in *addr_in;
  char host[59],*ihost;
  
  addr_in=(struct sockaddr_in *) addr;

  if(addr_in->sin_family != AF_INET)
    return connect(s,addr,addrlen);

  ihost=(char *) &addr_in->sin_addr.s_addr;
  if(gethostbyaddr(ihost,sizeof(long unsigned),addr_in->sin_family)!=NULL)
    return connect(s,addr,addrlen);

  if(! *ihost){
    ihost="\0";
  }else{
#define UC(a) (unsigned char) ((a) & 0xff)
    sprintf(host,"%u.%u.%u.%u",UC(ihost[0]),UC(ihost[1]),UC(ihost[2]),UC(ihost[3]));
    ihost=host;
  };

  if ((i = socket_connect_server(s,term_server)) <0) {
    if(term_debug>=0) perror("Term: Couldn't open term");
  }else if ((i=send_command(s,C_PORT,0,"%s:%d",ihost,ntohs(addr_in->sin_port)))< 0) {
    perror("Term: C_PORT");
  }else{
    remote_connect++;
    send_command(s, C_STATS, 0, "%d", -6);
    store_sockinfo(s, atoi(command_result));
    send_command(s, C_DUMB, 1, 0);
  };

  return ((i<0) ? -1 : 0);
}


/* This simmulates rcmd().  locuser is ignored!  Also stderr
 * is piped with stdout.  For stderr I send a closed pipe descriptor.  This
 * seems to keep "rsh" happy.  (Otherwise you'll create lots of zombies.)
 * So far I've only defined "shell", "cmd", "login", and "who".
 */

int term_rcmd(char **ahost,unsigned short inport, const char *locuser,
  const char *remuser, const char *cmd, int *fd2p){
  int i = -1,s = -1;
  char * rcommand;

/* If the host is listed by gethostbyname(), don't use term */

  if(gethostbyname(*ahost)!=NULL)
    return rcmd(ahost,inport,locuser,remuser,cmd,fd2p);

/* These values will need to be passed to the rd_exec function */

  if(fd2p!=NULL) *fd2p = -1;

  {  
    struct servent *sp;
    sp=getservbyport(inport,"tcp");
    if(!strcmp(sp->s_name,"shell")||!strcmp(sp->s_name,"cmd")){
      rcommand=(char *)cmd;
    }else if(!strcmp(sp->s_name,"login")){
      rcommand=NULL;
    }else if(!strcmp(sp->s_name,"who")){
      rcommand="rwho";
    }else{
      fprintf(stderr,"%s is not understood by term yet.\n",sp->s_name);
      return -1;
    };
  };

#ifdef SHAREDIR
  set_share_mode();
  if(getegid()!=0){
    setegid(getgid());
    seteuid(getuid());
  };
#endif
  s = socket(AF_INET, SOCK_STREAM, 0);
  if ((s = socket_connect_server(s,term_server)) <0) {
    if(term_debug>=0) perror("Term: Couldn't open term");
    i = -1;
  }else{
    if(rcommand==NULL){
      if ((i=send_command(s,C_EXEC,0,"rlogin %s -l %s",
        *ahost,remuser)<0)){
        perror("Term: C_EXEC");
      };
      remote_connect++;
    }else{
      if ((i=send_command(s,C_EXEC,0,"rsh %s -l %s %s",
        *ahost,remuser,rcommand))<0){
        perror("Term: C_EXEC");
      }else{
        send_command(s, C_STATS, 0, "%d", -6);
        store_sockinfo(s, atoi(command_result));
        send_command(s, C_DUMB, 1, 0);
      };
      remote_connect++;
    };
  };

  if(i<0){
    if(s>=0){
      close(s);
      s = -1;
    }
  }else if(fd2p!=NULL){
    int stat_pipe[2];
    if((pipe(stat_pipe))>=0){ /* open a pipe for passing the connect status */
        *fd2p=stat_pipe[0];
        close(stat_pipe[1]);
    };
  };
  return s;
}


