static char dsh_rcsid[]="$Id: dsh.c,v 1.2 1997/08/08 19:51:23 decker Exp $";

/*----------------------------------------------------
 * dsh.c Tzong-Yow Hwu, 08/05/1993
 *       Tom Green Mon Jan 31 10:43:11 1994
 *
 * Copyright 1993
 *
 * SUPER COMPUTER COMPUTATIONS RESEARCH INSTITUTE
 *            FLORIDA STATE UNIVERSITY
 *
 *
 * SCRI representatives make no claims about the
 * suitability of this software for any purpose.
 * It is provided "as is" without express or
 * implied warranty.
 *
 * $Log: dsh.c,v $
 * Revision 1.2  1997/08/08 19:51:23  decker
 * Patch by Janssen for Linux
 *
 * Revision 1.1.1.1  1997/04/10 15:10:35  green
 * DQS 3.1.3.4.1 Distribution
 *
 * Revision 3.15  1997/01/03 23:42:03  nrl
 * another dummy checkin
 *
 * Revision 3.14  1997/01/03 23:35:14  nrl
 * dummy checkin
 *
 * Revision 3.13  1996/11/20 23:04:52  nrl
 * Several fixes submitted by or as a result of investigations by
 * Ron Lee, Bodo Bechenback, Guntram Wolski and Frank Dwyyer.
 *
 * Revision 3.12  1996/06/27  01:56:12  nrl
 * changes to accomodate osf gcc
 *
 * Revision 3.11  1994/06/23  20:01:41  green
 * Solaris porting mods...
 *
 * Revision 3.10  1994/06/16  23:23:55  green
 * killed some more WAPS
 *
 * Revision 3.9  1994/06/16  20:24:54  green
 * somebody forgot gethostbyX() wasn't re-entrant on all boxes...
 *
 * Revision 3.8  1994/06/15  15:29:25  green
 * support for using DQS trusted host list for dshd
 *
 *     	passing of Host_head -n dqs_c_dqs_execd.c
 * 	time stamp Host_head on deletion in dqs_c_qconf.c
 * 	ck trusted host list in dqs_dshd.c
 * 	grab Host_head at startup in dqs_execd.c
 * 	rebuild Host_head/Host_hash in dqs_execd_rebuild_host_hash.c
 * 	dqs_free_hash in dqs_hash.c
 * 	grab new Host_head in dqs_load_avg.c
 * 	error log/printing in dsh.c
 *
 * Bug in my syslog code(or certain vendors required nullifying use
 * of syslogd until I can track it down...
 *
 * Revision 3.7  1994/06/12  23:36:45  green
 * added "setup()" for dsh
 *
 * Revision 3.6  1994/06/12  02:48:42  green
 * minor optimization to dqs_dshd.c to speed return to the "dqs_execd"
 *
 * cleaned up dsh.c somewhat
 *
 * Revision 3.5  1994/06/12  02:32:40  green
 * removed "dshd" and moved the functionality in the dqs_execd - this
 * will allow for the dqs_execd to set queue/job resources and to
 * reap/report rusage
 *
 * some really strange timing errors in the "dshd" connecting to the
 * stderr port on "dsh" under Linux forced introduction of a sleep(1);
 * this needs to be followed up on.
 *
 * dqs_dshd_service no longer needed
 *
 * allowed SIGCHLD to interrupt io_mask
 *
 * Revision 3.4  1994/06/11  19:20:06  green
 * moved the execl(dshd) out of /etc/inetd.conf and into the dqs_execd.
 * (not nearly as easy as I had thought...)
 *
 * no longer need DQS_DSHD_SERVICE
 *
 * mods were required to dqs_send_receive_list() which need to be
 * propogated to ALL ancillaries...
 *
 * Revision 3.3  1994/06/05  15:27:31  green
 * forced death of children(local and remote) at death of "master"
 * foresee a lot of confusion here when combined with "-notify"...
 *
 * Revision 3.2  1994/05/31  12:03:48  green
 * some OSes define MAXNAMELEN - some don't
 * handled with an ifndef in def.h
 *
 * removed "#include "h.h"" in defined.c
 *
 * yanked ANSI prototyping in dsh.c/dshd.c as some(sic) C compilers can't
 * swallow them
 *
 * #ifdef'ed fcntl(F_SETOWN) out for HPUX in dsh.c as HPUX doesn't define
 * it
 *
 * #ifdef'ed "#include <sys/select.h>" out of h.h #ifdefed
 * __hpux/bsd_4_2/SVR3/__osf__.(taken care of in types.h in most of
 * these.)
 *
 * Revision 3.1  1994/05/31  00:52:42  green
 * added dsh.c dshd.c to Makefile.proto
 *
 * added MAXNAMELEN and NCARGS to def.h
 *
 * type casting in dqs_reauth.c, dqs_sec.c, dqs_setup.c to hush gcc
 * warnings
 *
 * added jmp_buf to globals.h, changed Revision number in globals.h
 *
 * added dsh and dshd tp prognames.h
 *
 *
 *--------------------------------------------------*/

#define MAINPROGRAM
#include "h.h"
#include "def.h"
#include "dqs.h"
#include "struct.h"
#include "func.h"
#include "globals.h"
#include "dqs_errno.h"

void remote();
char *getcom();
void communicate();
void timeout();
void writesignum();
int drain();

/***************************************************************************/
main(argc,argv)
int argc;
char *argv[];

{
     char *rlogin = "rlogin";

     dqs_setup(DSH,argv[0]);
     dqs_setup_sig_handlers();
     me.who=DSH;

     switch (argc)
     {
	case 1:
	  /* wrong usage */
	  fprintf(stderr, "Usage: dsh hostname [command]\n");
	  return 1;
	case 2: 
	  /* exec as rlogin hostname */
          fprintf(stderr, "Usage: dsh hostname [command]\n");
	default:
	  /* dsh host command */
	  remote(argv[1], getcom(argv + 2));
	  break;
     }
     return 0;
}

/***************************************************************************/
char *getcom(argv)
char **argv;

/*
   Get command to be remotely exectuted from command line arguments.
   Concatenate the strings in argv to a single string.
   */

{
     char *command, *com;
     int length;
     char **parg = argv;

     if (!*argv)
     return (char *)0;
     
     /* calculate the length of command */
     for (length = 0; *argv; argv++)
     {
	  length += strlen(*argv) + 1;
     }
     
     /* Allocate memory for command */
     if (!(command = (char *)malloc((size_t) length)))
     {
	  perror("dsh: ");
	  exit(1);
     }
     
     /* copy the arguments from argv to command */
     for (com = command; *parg; parg++)
     {
	  for (strcpy(com, *parg); *com; com++)
	  ;
	  if (parg[1])
	  {
	       *com++ = ' ';
	  }
     }
     return command;
}

/***************************************************************************/

/* 
   File descriptor for the secodary socket.
   Used to send singal number and receives error messages.
   */
int fd2;

/***************************************************************************/
void remote(host,command)
char *host;
char *command;

/* executes command command on host host */

{
     struct servent *pservent;    /* pointer to the servent structure */
     struct passwd  *ppasswd;      /* pointer to passwd structure */
     string         pw_name;
     int fd1;                     /* file descriptor for the primary socket */
     int uid;                     /* real user id of the process */
     int gid;

     /* Check the real user id within local /etc/passwd file. */
     uid = getuid();
     gid = getgid();

     if (!(ppasswd = getpwuid(uid)))
     {
	  fprintf(stderr, "dsh: User does not exist.\n");
	  exit(1);
     }
     sprintf(pw_name,"%s",ppasswd->pw_name);

     fd1 = dsh(host, pw_name, command, &fd2);
     if (fd1 < 0)
     {
	  /* 
	     dsh fails 
	     fprintf(stderr, "Unable to dsh, fd1 is %d\n", fd1);
	     */
	  exit(1);
     }
     
     /* Take the real user id of the user to carry out the rest of the task. */

     setuid(uid); /* set r and e uid to the real user id */
     setgid(gid);

     /* starts communication between local client and remote server */
     communicate(fd1);
     return;
}

/***************************************************************************/
void communicate(fd1)
int fd1;

/* 
   communicate between client and server using primary and secondary sockets
   What communicate does is write whatever read from local stdin to fd1,
   write whatever read from fd1 to local stdout, and
   write whatever read from fd2 to local stderr. 
*/

{
     sigset_t omask, nmask;
     struct sigaction naction;
#if defined (__linux__) && defined(__GLIBC__) && __GLIBC__ >= 2      
      fd_set readfds;
#else     
      struct fd_set readfds;
#endif      
     int maxfd;
     int isstdin, isfd1, isfd2;
     
     /* Set signal handler for SIGINT, SIGQUIT and SIGTERM if not ignored. */
     /* Block these signals while setting up */
     sigemptyset(&nmask);
     sigaddset(&nmask, SIGINT);
     sigaddset(&nmask, SIGQUIT);
     sigaddset(&nmask, SIGTERM);
     sigprocmask(SIG_BLOCK, &nmask, &omask);
     sigaction(SIGINT, (struct sigaction *)0, &naction);
     if (naction.sa_handler != SIG_IGN)
     {
	  sigaddset(&naction.sa_mask, SIGINT);
	  sigaddset(&naction.sa_mask, SIGQUIT);
	  sigaddset(&naction.sa_mask, SIGTERM);
	  naction.sa_handler = writesignum;
	  sigaction(SIGINT, &naction, (struct sigaction *)0);
     }
     sigaction(SIGQUIT, (struct sigaction *)0, &naction);
     if (naction.sa_handler != SIG_IGN)
     {
	  sigaddset(&naction.sa_mask, SIGINT);
	  sigaddset(&naction.sa_mask, SIGQUIT);
	  sigaddset(&naction.sa_mask, SIGTERM);
	  naction.sa_handler = writesignum;
	  sigaction(SIGQUIT, &naction, (struct sigaction *)0);
     }
     sigaction(SIGTERM, (struct sigaction *)0, &naction);
     if (naction.sa_handler != SIG_IGN)
     {
	  sigaddset(&naction.sa_mask, SIGINT);
	  sigaddset(&naction.sa_mask, SIGQUIT);
	  sigaddset(&naction.sa_mask, SIGTERM);
	  naction.sa_handler = writesignum;
	  sigaction(SIGTERM, &naction, (struct sigaction *)0);
     }

     /* reset signal behavior */
     sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
     
     /* 
	Multiplex input from 0, fd1, and fd2: 
	Write input from 0 to fd1,
	input from fd1 to 1, and
	input from fd2 to 2.
	*/
     FD_ZERO(&readfds);
     FD_SET(0, &readfds);
     FD_SET(fd1, &readfds);
     FD_SET(fd2, &readfds);
     maxfd = (fd1 > fd2 ? fd1 : fd2) + 1;
     isstdin = isfd1 = isfd2 = 1;
     
     while (isfd1 || isfd2)
     {
	  if (-1 == select(maxfd, &readfds, 0, 0, 0)) /* block on select */
	  {
	       if (errno == EINTR)
	       {
		    /* select system call being interrupted */
		    continue;
	       }
	       perror("dsh: ");
	       exit(1);
	  }
	  
	  if (FD_ISSET(0, &readfds))
	  {
	       if (!drain(0, fd1))
	       {
		    /* eof indicator on local stdin */
		    FD_CLR(0, &readfds);
		    isstdin = 0;
		    shutdown(fd1, 1); /* send an eof over the socket too */
	       }
	  }
	  else
	  {
	       if (isstdin)
	       {
		    /* the local stdin is still available */
		    FD_SET(0, &readfds);
	       }
	  }
	  
	  if (FD_ISSET(fd1, &readfds))
	  {
	       if (!drain(fd1, 1))
	       {
		    /* eof indicator on local stdin */
		    FD_CLR(fd1, &readfds);
		    isfd1 = 0;
	       }
	  }
	  else
	  {
	       if (isfd1)
	       {
		    FD_SET(fd1, &readfds);
	       }
	  }
	  
	  if (FD_ISSET(fd2, &readfds))
	  {
	       if (!drain(fd2, 2))
	       {
		    /* eof indicator on local stdin */
		    FD_CLR(fd2, &readfds);
		    isfd2 = 0;
	       }
	  }
	  else
	  {
	       if (isfd2)
	       {
		    FD_SET(fd2, &readfds);
	       }
	  }
     }
     return;
}

/* 
   Write the signal number of the signal caught on the local machine to a
   remote server through the secondary socket.  When the remote control 
   process receives a signal number, it sends a corresponding signal to its 
   shell child process group.  If the signal kills the shell process then 
   the control process will terminate, in which case the socket connection 
   on fd1 and fd2 will be closed on its end and the local process
   will receive eof indicator from both sockets.  It then exits.  If the
   signal does not kill the child, then the local process will be still there
   to handle the communication.  
   */ 
/***************************************************************************/
void writesignum(signum)
int signum; 

{ 
     extern int fd2;
     char mysig;
     
     mysig = (char) signum;
     if (-1 == write(fd2, &mysig, 1))
     {
	  perror("dsh: ");
	  exit(1);
     }
}

/***************************************************************************/
int drain(in,out)
int in;
int out;

{
     int wsofar, written, readen;
     char buf[BUFSIZ];
     
     /* read from in and write to out */
     if (-1 == (readen = read(in, buf, BUFSIZ)))
     {
	  perror("dsh: ");
	  exit(1);
     }
     else if (!readen)
     {
	  return 0;
     }
     else
     {
	  /* write to out */
	  wsofar = 0;
	  while (wsofar != readen)
	  {
	       if (-1 == (written = write(out, buf+wsofar, readen-wsofar)))
	       {
		    perror("dsh: ");
		    exit(1);
	       }
	       wsofar += written;
	  }
	  return readen;
     }
}

/***************************************************************************/
int dsh(pphost,username,command,psfd2)
char *pphost;
char *username;
char *command;
int *psfd2;

/* 
   pphost:        pointer to a string of remote hostname,
   remport:       port number of remote host to contact to initiate the circuit,
   username:      string of username (on both the local and remote hosts,)
   command:       string contains command to executed remotely,
   psfd2:         pointer to an integer for storing secondary socket. 
   
   return:        a file descriptor for the primary socket of the circuit.
   */
{
     
     struct hostent *prhent;                           /* Remote hostent pointer */
     struct sockaddr_in mysaddr, fromsaddr, servsaddr; /* Socket address */
     sigset_t omask,                                   /* Old signal mask */
     nmask;                                   /* New signal mask */
     struct sigaction naction, oaction;
     int pid;                                          /* Process id */
     int fd1, fd2, fd3;                                /* Socket file descriptors */
     int myport;                                       /* Socket port number */
     int addrlen;                                      /* Length of socket address */
     char portnum[8];                                  /* Port number in ASCII form. */
     int euid;
     dqs_list_type listel,*resp;
     string remote_host,remote_host2;

     /* Check if the remote host name is known to the local host. */

     sprintf(remote_host,"%s",pphost);
     if (!(prhent = gethostbyname(remote_host)))
     {
	  perror("gethostbyname failed");
	  return -1;
     }

     
     /* establish a socket for connection with the remote server. */
     /* Create a socket. Use reserved port if possible, otherwise let 
	connect take care of the port number assignment. */
     euid = geteuid();
     fd1 = -1;

     bzero((char *)&listel,sizeof(listel));
     fd1=dqs_send_receive_list(remote_host,conf.dqs_execd_service,&listel,&resp);
     if (fd1<0)
     {
          exit(DQS_EAGAIN);
     }
     
     if (resp->status==DQS_NAK)
     {
	  fprintf(stderr,"%s",resp->str0);
	  exit(-1);
     }

     /* block SIGURG signal for now */
     pid = getpid(); 
     sigemptyset(&nmask);
     sigaddset(&nmask, SIGURG);
     if (-1 == sigprocmask(SIG_BLOCK, &nmask, &omask))
     {
	  perror("connect1");
	  close(fd1);
	  return -1;
     }

#ifdef F_SETOWN
     /* HPUX don't understand this concept - surprized? */
     fcntl(fd1, F_SETOWN, pid);
#endif

/*     
     if (-1 == connect(fd1, (struct sockaddr *)&servsaddr, sizeof(servsaddr))) 
     {
	  perror("connect2");
	  close(fd1);
	  sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	  return -1;
     }
*/     
     /* Create 2nd socket connection.  Bind the 2nd socket to a reserved 
	port if fd1 is bound to a reserved one, otherwise use a nonreserved port.  
	Send over the port number through 1st socket connection
	*/
     
     fd2 = -1;
     if (myport < IPPORT_RESERVED)
     {
	  /* try to use reserved port for second socket too. */
	  myport--;
	  if (-1 == (fd2 = rresvport(&myport)))
	  {
	       fprintf(stderr, "Unable to obtain a reserved port for second socket, try non-reserved port now.\n");
	  }
     }
     if (fd2 == -1)  
     {
	  if (-1 == (fd2 = socket(AF_INET, SOCK_STREAM, 0)))
	  {
	       perror("socket");
	       close(fd1);
	       sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	       return -1;
	  }
	  
	  /* get the socket address initialized */
	  /* Let bind find a suitable port number */
	  bzero((char *) &mysaddr, sizeof(mysaddr));
	  mysaddr.sin_family = AF_INET;
	  mysaddr.sin_port = 0;
	  mysaddr.sin_addr.s_addr = INADDR_ANY;
	  if (-1 == bind(fd2, (struct sockaddr *) &mysaddr, sizeof(mysaddr)))
	  {
	       perror("bind");
	       close(fd1);
	       close(fd2);
	       sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	       return -1;
	  } 
     }
     
     /* get port number of fd2 */
     addrlen = sizeof(mysaddr);
     getsockname(fd2, (struct sockaddr *) &mysaddr, &addrlen);
     sprintf(portnum, "%d", ntohs(mysaddr.sin_port));
     
     /* send over through fd1 to the server the port number of fd2.*/
     if (strlen(portnum) + 1 != write(fd1, portnum, strlen(portnum) + 1))
     {
	  perror("write");
	  close(fd1);
	  close(fd2);
	  sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	  return -1;
     }
     
     /* listen and accept on fd2 for an connect request */
     listen(fd2, 1);
     addrlen = sizeof(fromsaddr);
     if (-1 == (fd3 = accept(fd2, (struct sockaddr *) &fromsaddr, &addrlen)))
     {
	  perror("accept");
	  close(fd1);
	  close(fd2);
	  sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	  return -1;
     }
     
     /* Have no more use of fd2 */
     close(fd2);
     
     /* send over the username for checking and command for execution. */
     if ((strlen(username) + 1 != write(fd1, username, strlen(username) + 1)) ||
	 (strlen(command) + 1 != write(fd1, command, strlen(command) + 1)))
     {
	  perror("write");
	  close(fd1);
	  close(fd3);
	  sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	  return -1;
     }
     
     /* read an 0 from fd3 to make sure everything is OK. */
     {
	  char c;
	  
	  if (1 != read(fd3, &c, 1))
	  {
	       perror("read");
	       close(fd1);
	       close(fd3);
	       sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	       return -1;
	  }
	  if (c != '\0')
	  {
	       /* error message coming. */
	       while (1 == read(fd3, &c, 1))
	       {
		    write(2, &c, 1);
		    if (c == '\n')
		    break; 
	       }
	       close(fd1);
	       close(fd3);
	       sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
	       return -1;
	  }
     }
     
     *psfd2 = fd3;
     sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
     return fd1;
}

/***************************************************************************/
void timeout(sig)
int sig;

{
     longjmp(env, 1);
}
