/*
 * newstartd.c
 *
 *   Please read before use:
 *
 *   There's a really bad hack in newstartd.c that refuses more than 1
 *   connection from any hostname.  It's bad because it uses /usr/ucb/netstat
 *   to figure out who's connected.
 *
 *   At this point, only the hostname of the caller is available.  The
 *   daemon in theory has access to both user name and (partial) hostname,
 *   so that's where multiple logins should be denied.
 *
 *   To bypass this check, create the file defined by NoCount_File in
 *   newaccess.c.
 *
 *   Also, you may want to forget newstartd and use listen.c, which is part
 *   of the galaxy server distribution.
 *
 *   4/13/92
 *
 *   Derived from distribution startd.c and xtrekII.sock.c (as of ~1/14/91).
 *
 *   Installation:  update the Prog and LogFile variables below, and
 *     set the default port appropriately (presently 2592).  Set debug=1
 *     to run this under dbx.  Link this with access.o and subnet.o.
 *  
 *   Summary of changes:
 *   - subsumes xtrekII.sock into startd (newstartd calls ntserv directly to
 *       reduce OS overhead)
 *   - some debugger support code added
 *   - uses reaper() to keep process table clean (removes <exiting> procs)
 *
 *   8/16/91 Terence Chang
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>

#include "../ntserv/defs.h"
#include INC_STRINGS
#include INC_FCNTL
#include "../ntserv/data.h"

int debug = 0;
int newsock = -1;
extern int errno;
int connectionAttemptDetected(), read_portfile();
void reaper();
 
/* xtrek.sock global stuff: (TC) */

#define SP_BADVERSION 21
struct badversion_spacket {
    char type;
    char why;
    char pad2;
    char pad3;
};

#define PORTFILE LIBDIR"/.ports"
#define MAXPROG                 16
static
struct progrecord
  {
    unsigned short port;
    int sock;
    int nargs;
    char prog[512], progname[512],      /* any special arguments */
      arg[4][64];
  }
prog[MAXPROG];

extern char peerhostname[];	/* Defined in access.c (TC) */
int fd;

main(argc, argv)
int argc;
char **argv;
{
    int tries=0;
    int forked;
    char startprog[255];
    char sockstr[10];
    
    /* xtrekII.sock main() declarations (TC) */
    
    FILE *fp;
    struct progrecord *pr;
    char *portfile = PORTFILE;
    int port_idx = -1;
    int num_progs=0;
    time_t curtime;
    char logname[BUFSIZ];

    struct badversion_spacket packet;
    int	game;
    char version[81];

    getpath();
    if(argc == 2)
	portfile = argv[1];
    num_progs = read_portfile(portfile);
    if (!num_progs) {
	strcpy(prog[0].prog,Prog);
	strcpy(prog[0].progname,Prog);
	prog[0].port= PORT;
	prog[0].nargs= 0;
	prog[0].sock= -1;
	num_progs =1;
	}

    SIGNAL(SIGCHLD, reaper);
    if (!debug)	DETACH

    fd = open(LogFile, O_CREAT | O_WRONLY | O_APPEND, 0600);

  while (1) {
    if ((port_idx=connectionAttemptDetected(num_progs))<0) {
	fprintf(stderr, "Possible Problem.\n");
    }

    else if (host_access(NULL)==0) {
	packet.type=SP_BADVERSION;
	packet.why=1;
	write(0, (char *) &packet, sizeof(packet));
	time(&curtime);
	sprintf(logname, "Denied %-32.32s %s",
		peerhostname,
		ctime(&curtime));
	if (fd!=-1) write(fd, logname, strlen(logname));
	sleep(2);
	close(0);
    }
    else if (fork() == 0) { /* we are a clone */
	time(&curtime);
	sprintf(logname, "       %-32.32s %s",
		peerhostname,
		ctime(&curtime));
	if (fd!=-1) write(fd, logname, strlen(logname));
	if (fd!=-1) close(fd);

	pr=&(prog[port_idx]);
	switch (pr->nargs) {
	  case 0: execl(pr->prog,pr->progname, peerhostname, 0);
		  break;
	  case 1: execl(pr->prog,pr->progname, pr->arg[0], peerhostname, 0);
		  break;
	  case 2: execl(pr->prog,pr->progname, pr->arg[0], pr->arg[1], 
			peerhostname, 0);
		  break;
	  case 3: execl(pr->prog,pr->progname, pr->arg[0], pr->arg[1], 
			pr->arg[2], peerhostname, 0);
		  break;
	  case 4: execl(pr->prog,pr->progname, pr->arg[0], pr->arg[1], 
			pr->arg[2], pr->arg[3], peerhostname, 0);
		  break;
	  default: ;
	}
	fprintf(stderr,"Error in execl! -- %s\n",pr->prog);
	reporterror();
    	close(0);
    }
    else close(0);
  }
}

int connectionAttemptDetected(num_progs)
int num_progs;
{
    struct sockaddr_in addr;
    struct sockaddr_in naddr;
    struct timeval timeout;
    int readfds;
    fd_set accept_fds;
    int len,i,st;

    char *opt;
    int foo = 1;

   /* check all ports */
   for (i = 0; i < num_progs; i++) {
    sock = prog[i].sock;
    if(sock < 0){
	if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){
	    fprintf(stderr,"Hey! I can't make a socket!\n");
	    fprintf(stderr,"I'll try again \n");
	    return(-1);
	}
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(prog[i].port);

/* newstuff*/
	if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR,
		(char *) &foo, sizeof (int)) < 0)
	{
		perror ("setsockopt REUSEADDR");
		return (-1);
	}
/*end newstuff*/
	
	if(bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0){
	    if(errno == ENOTSOCK){
		    fprintf(stderr,"bogus! socket is unbindable!\n");
		    fprintf(stderr,"Used Port %d \n", prog[i].port);
		    reporterror();
		    close(sock);
		    prog[i].sock = -1;
		    fprintf(stderr,"closing socket!");
		    continue;
		}
	    if (debug)
		fprintf(stderr, "Taking 10...\n");
	    sleep(10);
	    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
		if (debug)
		    fprintf(stderr, "Taking 10...\n");
		sleep(10);
		if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
		    fprintf(stderr,"Bogus! I can't bind to the socket!\n");
		    fprintf(stderr,"Error: %d\n", errno);
		    close(sock);
		    prog[i].sock = -1;
		    continue;
		}
	    }
	}
	if(listen(sock, 1)<0) {
	    fprintf(stderr,"Listen failed: ");
	    reporterror();
	    sock = -1;
	}
	prog[i].sock=sock;
	/* close(0); */
      fprintf (stderr,"listening on %d, connection will start %s \"%s\" %s %s %s %s\n",
              prog[i].port, prog[i].prog, prog[i].progname,
            prog[i].arg[0], prog[i].arg[1], prog[i].arg[2], prog[i].arg[3]);
      fflush (stderr);
    }
   }
   
  while (1)                      /* Wait for a connection */
    {
    again:;
      FD_ZERO (&accept_fds);
      for (i = 0; i < num_progs; i++)
        {
	  sock = prog[i].sock;
          if (sock < 0)
            continue;
          FD_SET (sock, &accept_fds);
        }

      /* block until select indicates accept will complete */
      if ((st = select (32, &accept_fds, 0, 0, 0)))
        {
          if (st <= 0 && errno == EINTR)
            goto again;
          for (i = 0; i < num_progs; i++)
            {
	      sock = prog[i].sock;
              if (sock < 0)
                continue;
              if (FD_ISSET (sock, &accept_fds))
                  /* found a connection, procede */
		goto found;
	    }
         }
     }

found:
    len = sizeof(naddr);
    
    /* hack: to support reaper(), must ignore EINTR errors (TC) */

  nointr:
    newsock = accept(sock, (struct sockaddr*)&naddr, &len);
    if ((newsock < 0) && (errno == ECONNRESET)){
	fprintf(stderr,"Connection Reset!  Socket # is %d (%d)\n", sock, newsock);
	sleep(1);
	goto nointr;
    }
    if ((newsock < 0) && (errno == ETIMEDOUT)){
	fprintf(stderr,"Connection Timed Out!  Socket # is %d (%d)\n", sock, newsock);
	sleep(1);
	goto nointr;
    }
    if ((newsock < 0) && (errno == EINTR))
	goto nointr;

    if(newsock < 0){
	fprintf(stderr,"accept error!");
	reporterror();
	fprintf(stderr,"No one calling!\n");
	shutdown(sock, 2);
	close(sock);
	prog[i].sock = -1;
	return(-1);
    } else {
	if (newsock != 0) {
	    if (dup2(newsock, 0) == -1) {
		fprintf(stderr, "failed dup2\n");
		reporterror();
	    }
	    close(newsock);
	}
	return(i);
    }
}

reporterror()
{
    switch(errno){
      case EBADF:
	fprintf(stderr,"EBADF!\n");
	break;
      case ENOTSOCK:
	fprintf(stderr,"ENOTSOCK!\n");
	break;
      case EOPNOTSUPP:
	fprintf(stderr,"EOPNOTSUPP!\n");
	break;
      case EFAULT:
	fprintf(stderr,"EFAULT!\n");
	break;
      case EWOULDBLOCK:
	fprintf(stderr,"EWOULDBLOCK!\n");
	break;
      case ENOTDIR:
	fprintf(stderr,"ENOTDIR!\n");
	break;
      case EINVAL:
	fprintf(stderr,"EEINVAL!\n");
	break;
      case ENOENT:
	fprintf(stderr,"ENOENT!\n");
	break;
      case ELOOP:
	fprintf(stderr,"ELOOP!\n");
	break;
      case EACCES:
	fprintf(stderr,"EACCES!\n");
	break;
      case ENOEXEC:
	fprintf(stderr,"ENOEXEC!\n");
	break;
      case ENOMEM:
	fprintf(stderr,"ENOMEM!\n");
	break;
      case EADDRNOTAVAIL:
	fprintf(stderr,"EADDRNOTAVAIL!\n");
	break;
      case EADDRINUSE:
	fprintf(stderr,"EADDRINUSE!\n");
	break;
      case EINTR:
	fprintf(stderr,"Interrupted system call.\n");
	break;
      default:
	perror("newstartd: reporterror");
	fprintf(stderr,"reporterror: unknown: %d\n",errno);
    }
}

void reaper(sig)
{
    int stat;
    if (debug) fprintf(stderr,"Reaping...\n");
    while (WAIT3(&stat, WNOHANG, 0) > 0)
	;
    HANDLE_SIG(SIGCHLD,reaper);
}

int read_portfile (portfile)
char *portfile;
{
  FILE *fi;
  char buf[BUFSIZ];
  int i = 0, p, n;
  struct stat sbuf;

  fi = fopen (portfile, "r");
  if (!fi)
    {
      perror (portfile);
      return(0);
    }

  while (fgets (buf, BUFSIZ - 1, fi))
    {
      if ((n=sscanf (buf, "%d %s \"%[^\"]\" %s %s %s %s", &p,
                  prog[i].prog,
                  prog[i].progname,
                  prog[i].arg[0],
                  prog[i].arg[1],
                  prog[i].arg[2],
                  prog[i].arg[3])) >= 3)
        {
	  prog[i].nargs = n-3;
          prog[i].port = (unsigned short) p;
          prog[i].sock = -1;
          if (stat (prog[i].prog, &sbuf) < 0 || !(sbuf.st_mode & S_IRWXU))
            {
              fprintf (stderr, "\"%s\" not accessible or not executable.\n",
                       prog[i].prog);
              fflush (stderr);
            }
          else
            {
              i++;
            }
        }
    }
  fclose (fi);

  return(i);
}
