static char dqs_dshd_rcsid[]="$Id: dqs_dshd.c,v 1.2 1998/10/21 14:44:05 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.2  1998/10/21 14:44:05  green
 * Red Hat Alpha port - ADDRLEN incorporated
 *
 * Revision 1.1.1.1  1998/08/18 14:39:11  green
 * DQS 3.2.0.5 WIP Import
 *
 * 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. */
  ADDRLEN 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);
    }
}
