static char dqs_dshd_rcsid[]="$Id: dqs_dshd.c,v 1.1.1.1 1997/04/10 15:10:32 green Exp $";

/*----------------------------------------------------
 * dqs_dshd.c Tzong-Yow Hwu, 08/12/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: dqs_dshd.c,v $
 * Revision 1.1.1.1  1997/04/10 15:10:32  green
 * DQS 3.1.3.4.1 Distribution
 *
 * Revision 3.9  1996/11/20 23:03:34  nrl
 * Several fixes submitted by or as a result of investigations by
 * Ron Lee, Bodo Bechenback, Guntram Wolski and Frank Dwyyer.
 *
 * Revision 3.8  1996/06/27  01:55:48  nrl
 * changes to accomodate osf gcc
 *
 * Revision 3.7  1994/06/18  14:00:04  green
 * added "conf.dqs_bin" to default path_list
 *
 * set default umask
 *
 * Revision 3.6  1994/06/15  20:42:39  green
 * use dqs_set_uid_gid() to localize functionality
 *
 * Revision 3.5  1994/06/15  15:29:07  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.4  1994/06/12  22:05:43  green
 * yanked a SETPGRP and setsid which were holdovers from firing from
 * inetd
 *
 * Revision 3.3  1994/06/12  21:04:57  green
 * expanded DQS_MAXNAMELEN to MAX_STRING_SIZE in def.h
 *
 * utilize DQS_MAXNAMELEN for command size in dqs_dshd.c
 *
 * Revision 3.2  1994/06/12  02:48:41  green
 * minor optimization to dqs_dshd.c to speed return to the "dqs_execd"
 *
 * cleaned up dsh.c somewhat
 *
 * Revision 3.1  1994/06/12  02:34:48  green
 * removed dshd.c
 *
 * added dqs_dshd.c
 *
 *
 *--------------------------------------------------*/

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

/***************************************************************************/
int dqs_dshd(sfd)
int sfd;

{

     string str;
     int on=1;

     struct sockaddr_in fromaddr;  /* Address where connection request is from. */
     int addrlen;                  /* Length of address. */
     short fromport;
     struct linger lngr;
     int i,pid,one = 1;
     int errs;                     /* Socket for stderr. */
     char c;
     struct passwd *pwd;           /* passwd entry */
     char command[DQS_MAXNAMELEN + 1], username[DQS_MAXNAMELEN + 1];
     char errmsg[BUFSIZ];
     struct sigaction naction, oaction;
     dqs_list_type listel;
     dqs_job_type  job;

     pid=fork();

     if (pid)
     {
          return(pid);
     }

     alarm(0);
     bzero((char *)&listel,sizeof(listel));
     bzero((char *)&job,sizeof(job));

     listel.status=DQS_ACK;

     if (dqs_trusted_host(sfd))
     {
          listel.str0=dqs_string_insert(NULL,
		"\nYou are not a DQS trusted host...\n");
	  listel.status=DQS_NAK;
	  (void)dqs_send_list(NULL,NULL,sfd,&listel);
          exit(-1);
     }

     if (dqs_send_list(NULL,NULL,sfd,&listel)<0)
     {
	  syslog(LOG_WARNING,"dqs_send_list() FAILED");
	  exit(-1);
     }

     alarm(0);
     if(dup2(sfd,0)!=0)
     syslog(LOG_ERR, "dup2(sfd,0) failed");
     close(sfd); 
     if (dup2(0,1)!=1)
     syslog(LOG_ERR, "dup2(sfd,1) failed");
     if (dup2(0,2)!=2)
     syslog(LOG_ERR, "dup2(sfd,2) failed");

     sleep(1); /* Hmm, why? Linux is giving me problems if not here */
               /* more than likely my code - not Linux though...   */
     /* 0, 1, and 2 are all redirected to socket */

     addrlen = sizeof(fromaddr);
     if (-1 == getpeername(0, (struct sockaddr *)&fromaddr, &addrlen))
     {
          syslog(LOG_ERR, "getpeername: %m");
          exit(1);
     }

     /* make sure it's an internet socket */
     if (fromaddr.sin_family != AF_INET)
     {
          syslog(LOG_ERR, "Non-Internet address format.\n");
          exit(1);
     }

     /* set socket options: SO_KEEPALIVE and SO_LINGER */
     lngr.l_linger = 30;  /* linger time */
     lngr.l_onoff = 1;
     if (-1 == setsockopt(0, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) ||
         -1 == setsockopt(0, SOL_SOCKET, SO_LINGER, (char *)&lngr, sizeof(lngr)) ||
	 -1 == setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one)))
     {
	  syslog(LOG_WARNING, "setsockopt: %m");
     }
     
     /***** At this point, we know that the client must be a legal dsh client. */ 
     
     /* Create a second socket for error message. */
     fromport = ntohs((u_short) fromaddr.sin_port);
     errs = -1;
     /* Use a reserved port if connection is originated from a reserved port. */
     if (fromport >= IPPORT_RESERVED/2 && fromport < IPPORT_RESERVED)
     {
	  int myport = IPPORT_RESERVED - 1;
	  
	  if (0 > (errs = rresvport(&myport)))
	  {
	       syslog(LOG_NOTICE, "Reserved ports unavailable, use nonreserved.\n");
	  }
     }
     
     if (errs < 0)
     {
	  if (-1 == (errs = socket(AF_INET, SOCK_STREAM, 0)))
	  {
	       syslog(LOG_ERR, "stderr socket: %m");
	       exit(1);
	  }
	  /* Let connect take care of port binding. */ 
     }
     
     /* read a port number for stderr connection */
     for(fromport = 0; ;)
     {
	  if (-1 == read(0, &c, 1))
	  {
	       syslog(LOG_ERR, "read: %m");
	       exit(1);
	  }
	  if (c == '\0')
	  {
	       break;
	  }
	  else if (!isdigit(c))
	  {
	       syslog(LOG_NOTICE, "Illegal port number.");
	       exit(1);
	  }
	  else
	  {
	       fromport = fromport * 10 + c - '0';
	  }
     }
     
     fromaddr.sin_port = htons((u_short) fromport);
     /* Establish the connection. */
     
     if (-1 == connect(errs, (struct sockaddr *)&fromaddr, sizeof(fromaddr)))
     {
	  syslog(LOG_EMERG, "connect stderr port: %m");
	  exit(1);
     }
     
     errmsg[0] = 1;
     /* Read user name and command. */
     if (!readnchar(0, username, sizeof(username)))
     {
	  sprintf(errmsg + 1, "Malformed username\n");
	  write(errs, errmsg, strlen(errmsg)); 
	  exit(1);
     }

     job.owner=dqs_string_insert(NULL,username);

     if (!readnchar(0, command, sizeof(command)))
     {
	  sprintf(errmsg + 1, "Malformed command\n");
	  write(errs, errmsg, strlen(errmsg)); 
	  exit(1);
     }
     
     /* Verify the user name on local machine. */
     /* Note, the trusted user is not checked here.  It's assumed that
	DQS does the validation. */
     
     if (!(pwd = getpwnam(username)))
     {
	  sprintf(errmsg + 1, "Invalid username.\n");
	  write(errs, errmsg, strlen(errmsg)); 
	  exit(1);
     }
     else if (-1 == chdir(pwd->pw_dir))
     {
	  sprintf(errmsg + 1, "No remote directory\n");
	  write(errs, errmsg, strlen(errmsg)); 
	  exit(1);
     }
     else if (pwd->pw_uid != 0 && !access("/etc/nologin", F_OK))
     {
	  sprintf(errmsg + 1, "Logins disabled.\n");
	  write(errs, errmsg, strlen(errmsg)); 
	  exit(1);
     }
     else
     {
	  /* Everything is OK, send an confirmed null char. */
	  if (1 != write(errs, "", 1))
	  {
	       exit(1);
	  }
     }
     
     /* make sure the signal behavior of SIGINT, SIGQUIT, and SIGTERM is default */
     sigaction(SIGINT, (struct sigaction *)0, &naction);
     naction.sa_handler = SIG_DFL;
     sigaction(SIGINT, &naction, (struct sigaction *)0);
     
     sigaction(SIGTERM, (struct sigaction *)0, &naction);
     naction.sa_handler = SIG_DFL;
     sigaction(SIGTERM, &naction, (struct sigaction *)0);
     
     sigaction(SIGQUIT, (struct sigaction *)0, &naction);
     naction.sa_handler = SIG_DFL;
     sigaction(SIGQUIT, &naction, (struct sigaction *)0);
     
     myexec(pwd->pw_shell, command, errs, pwd, &job);
}

/***************************************************************************/
int readnchar(fd,buf,len)
int fd;
char *buf;
int len;

{
     int i;
     
     for (i = 0; i < len; i++)
     {
	  if (1 != read(fd, buf + i, 1))
	  {
	       exit(1);
	  }
	  else if (buf[i] == '\0')
	  {
	       return i;
	  }
     }
     return 0;
}

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

{
     longjmp(env, 1);
}

char pathvar[MAX_STRING_SIZE] = "PATH=/usr/ucb:/usr/bin:/bin";  /* path environment */
char uservar[MAX_STRING_SIZE] = "USER=";
char homevar[MAX_STRING_SIZE] = "HOME=";
char shellvar[MAX_STRING_SIZE] = "SHELL=";
char *envars[] = {
     pathvar,
     uservar,
     homevar,
     shellvar, 
     (char *) 0
};

/***************************************************************************/
void myexec(shell,command,errfd,pwd,job)
char *shell;
char *command;
int errfd;
struct passwd *pwd;
dqs_job_type *job;

{
     int pfd[2];
     char errmsg[BUFSIZ];
     int chpid;
     
     
     if (-1 == pipe(pfd))
     {
	  sprintf(errmsg, "pipe: %s\n", sys_errlist[errno]);  
	  write(errfd, errmsg, strlen(errmsg)); 
	  exit(1);
     }
     
     if (-1 == (chpid = fork()))
     {
	  sprintf(errmsg, "fork: %s\n", sys_errlist[errno]);  
	  write(errfd, errmsg, strlen(errmsg)); 
	  exit(1);
     }
     
     if (chpid)
     /* This is the parent, the control process. */
     {
	  int one = 1;
	  char sig;
	  char buf[BUFSIZ];
	  fd_set readfds;
	  int maxfd;
	  int ispfd, iserrfd;
	  
	  /* Close all unneeded fd's, only needs pfd[0] and errfd */
	  close(0); 
	  close(1); 
	  close(2); 
	  close(pfd[1]);
	  
	  /*
	     ioctl(pfd[0], FIONBIO, (char *) &one);
	     ioctl(errfd, FIONBIO, (char *) &one);
	     */
	  
	  FD_ZERO(&readfds);
	  FD_SET(pfd[0], &readfds);
	  FD_SET(errfd, &readfds);
	  maxfd = (errfd > pfd[0] ? errfd : pfd[0]) + 1;
	  ispfd = iserrfd = 1;
	  
	  while (ispfd || iserrfd)
	  {
	       if (-1 == select(maxfd, &readfds, 0, 0, 0)) /* block on select */
	       {
		    break;
	       }
	       
	       if (FD_ISSET(pfd[0], &readfds))
	       {
		    int cc;
		    
		    if (0 >= (cc = read(pfd[0], buf, sizeof(buf))))
		    {
			 ispfd = 0; 
			 shutdown(errfd, 1);
			 FD_CLR(pfd[0], &readfds);
		    }
		    else
		    {
			 write(errfd, buf, cc); 
		    }
	       }
	       else
	       {
		    if (ispfd)
		    {
			 /* the pipe read end is still available */
			 FD_SET(pfd[0], &readfds);
		    }
	       }
	       
	       if (FD_ISSET(errfd, &readfds))
	       {
		    if (1 != read(errfd, &sig, 1))
		    {
			 iserrfd = 0; 
			 FD_CLR(errfd, &readfds);
		    }
		    else
		    {
			 kill(-(int)chpid, sig);
		    }
	       }
	       else
	       {
		    if (iserrfd)
		    {
			 /* the pipe read end is still available */
			 FD_SET(errfd, &readfds);
		    }
	       }
	  } /* while */
	  
	  exit(0);
     }
     else
     /* This is the child, the slave process. */
     {
	  extern char **environ;   /* environment variable */
	  extern char *envars[];
	  char *psh;
	  
	  /* close unneeded fd's */
	  close(errfd);
	  close(pfd[0]);
	  
	  /* redirect stderr to pfd[1]. */
	  close(2);
	  dup(pfd[1]);
	  close(pfd[1]);
	  
	  /* Set process group in order for killpg to work. */ 
	  SETPGRP; 
	  
	  if (shell[0] == '\0')
	  {
	       shell = "/bin/sh";
	  }
	  
	  /* Set process uid, gid.

	  initgroups(pwd->pw_name, pwd->pw_gid);
	  setuid((uid_t) pwd->pw_uid);
	  setgid((gid_t) pwd->pw_gid);
	  */

	  dqs_set_uid_gid(job);

	  /* Look for shell name. */
	  if (!(psh = strrchr(shell, '/')))
	  {
	       psh = shell;
	  } 
	  else
	  {
	       psh++;
	  }

	  strcat(pathvar,":");
	  strcat(pathvar,conf.dqs_bin);
	  strncat(uservar, pwd->pw_name, sizeof(uservar) - 6);
	  strncat(homevar, pwd->pw_dir, sizeof(homevar) - 6);
	  strncat(shellvar, shell, sizeof(shellvar) - 7);
	  
	  umask(022);

	  /* executes command */
	  execl(shell, psh, "-c", command, (char *)0);
	  
	  /* error only if exec failed */
	  perror(shell);
	  exit(1);
     }
}
