/* Originally derived from an 
 * Example of server using TCP protocol
 * pp 284-5 Stevens UNIX Network Programming
 */


#include "gopherd.h"
void LOGGopher();

static	int	uid = -2;

extern char *getdesc();

#ifdef LOADRESTRICT

#ifndef MAXLOAD
#define MAXLOAD 10.0
#endif
double atof();
double maxload = MAXLOAD;
double sysload = 0.0;
#include <nlist.h>
#include <kvm.h>
int nproc;
long avenrun[3];
kvm_t * kd;
char prog[1024];
struct   nlist nl[] = {
    {"_avenrun" },  /* SunOS 4.1.1, your milage may vary... */
#define X_AVENRUN 0
    {""},
};

#endif /*LOADRESTRICT */


/*
 * This routine finds out the hostname of the server machine.
 * It uses a couple of methods to find the fully qualified 
 * Domain name
 */

char *
GetDNSname(backupdomain)
  char *backupdomain;
{
     static char DNSname[MAXHOSTNAMELEN];
     struct hostent *hp;


     /* Work out our fully-qualified name, for later use */
     
     if (gethostname(DNSname, MAXHOSTNAMELEN) != 0) {
	  fprintf(stderr, "Cannot determine the name of this host\n");
	  exit(-1);

     }

     /* Now, use gethostbyname to (hopefully) do a nameserver lookup */
     hp = aethostbyname( DNSname);

     /*
      ** If we got something, and the name is longer than hostname, then
      ** assume that it must the the fully-qualified hostname
      */
     if ( hp!=NULL && strlen(hp->h_name) > strlen(DNSname) ) 
	  strncpy( DNSname, hp->h_name, MAXHOSTNAMELEN );
     else
	  strcat(DNSname, backupdomain);

     return(DNSname);
}


/*
 * Tries to figure out what the currently connected port is.
 * 
 * If it's a socket then it will return the port of the socket, 
 * if it isn't a socket then it returns -1.
 */

int GetPort(fd)
  int fd;
{
     struct sockaddr_in serv_addr;

     int length = sizeof(serv_addr);
     
     /** Try to figure out the port we're running on. **/
     
     if (getsockname(fd, (struct sockaddr *) &serv_addr,&length) == 0)
	  return(ntohs(serv_addr.sin_port));
     else
	  return(-1);

}



void
main(argc, argv)
  int 	argc;
  char 	*argv[];
{
     int sockfd;
     int newsockfd;
     int clilen;
     int childpid;
     int i;

     struct sockaddr_in serv_addr;
     struct sockaddr_in cli_addr;

     /*** for getopt processing ***/
     int c;
     extern char *optarg;
     extern int optind;
     int errflag =0;


     pname = argv[0];
     strcpy(Data_Dir, DATA_DIRECTORY);
     err_init();	/* openlog() early - before we chroot() of course */

     /*** Check argv[0], see if we're running as gopherls, etc. ***/

     RunServer = RunLS = RunIndex = FALSE;

     if (strstr(argv[0], "gopherls") != NULL) {
	  RunLS = TRUE;
     } else if (strstr(argv[0], "gindexd") != NULL) {
	  RunIndex = TRUE;
	  dochroot = FALSE;
     } else 
	  RunServer = TRUE;  /** Run the server by default **/

 
     while ((c = getopt(argc, argv, "mCDIcL:l:s:u:U:")) != -1)
	  switch (c) {
	  case 'D':
	       DEBUG = TRUE;
	       break;

	  case 'I':
	       RunFromInetd = TRUE;
	       break;

	  case 'C':
	       Caching = FALSE;
	       break;

	  case 'm':
		if (RunIndex)
			MacIndex = TRUE;
		break;

	  case 'c':
	       dochroot = FALSE;
	       if (!RunFromInetd) {
		    printf("Not using chroot() - be careful\n");
		    if ( getuid() == 0 || geteuid() == 0 )
			 printf("You should run without root perms\n");
	       }
	       break;

	  case 'L':  /** Load average at which to restrict usage **/
#ifdef LOADRESTRICT
	       maxload = atof(optarg);
#endif
	       break;

	  case 'l':  /** logfile name **/
	       if (*optarg == '/')
		    strcpy(LOGFile, optarg);
	       else {
		    getwd(LOGFile);
		    strcat(LOGFile, "/");
		    strcat(LOGFile, optarg);
	       }
	       break;
	       
	  case 's': /** security file name **/
	       if (*optarg == '/')
		    strcpy(SecurityFile, optarg);
	       else {
		    getwd(SecurityFile);
		    strcat(SecurityFile, "/");
		    strcat(SecurityFile, optarg);
	       }
	       break;

	  case 'u':
	       {
		    struct passwd *pw = getpwnam( optarg );
		    if ( !pw ) {
			 fprintf(stderr,
			      "Could not find user '%s' in passwd file\n",
			      optarg);
			 errflag++;
		    } else {
			 uid = pw->pw_uid;
			 if (!RunFromInetd) {
			      printf("Running as user '%s' (%d)\n",
				   optarg, uid);
			 }
		    }
	       }
	       break;

	  case 'U':	/* set uid to use */
	       uid = atoi( optarg );
	       if (!RunFromInetd) {
		    printf("Running using uid %d\n", uid);
	       }
	       break;
	  case '?':
	  case 'h':
	       errflag++;
	       break;
	  }


     if (errflag) {
	  fprintf(stderr, "Usage: %s [-CDIc] [-u userid] [-U uid] [-s securityfile] [-l logfile] <datadirectory> <port>\n", argv[0]);
	  fprintf(stderr, "   -C  turns caching off\n");
	  fprintf(stderr, "   -D  enables copious debugging info\n");
	  fprintf(stderr, "   -I  enable \"inetd\" mode\n");
	  fprintf(stderr, "   -c  disable chroot(), use secure open routines instead\n");
	  fprintf(stderr, "   -u  specifies the username for use with -c\n");
	  fprintf(stderr, "   -U  specifies the UID for use with -c\n");
	  fprintf(stderr, "   -s  specifies the name of a security file\n");
	  fprintf(stderr, "   -l  specifies the name of a logfile\n");
		  
	  exit(-1);
     }

     if (uid == -2) 
	  uid = getuid();  /** Run as current user... **/

     if ( uid == 0 && !RunFromInetd )
	  printf("Hope you know what you're doing ...\n");


     if (optind < argc) {
	  strcpy(Data_Dir, argv[optind]);
	  optind++;
     } else if (RunLS)
	  strcpy(Data_Dir, "/");

     if (optind < argc) {
	  GopherPort = atoi(argv[optind]);
	  optind++;
     }

     if (RunLS) {
	  Zehostname ="";
	  Caching = FALSE;

	  fflush(stdout);
	  uchdir(Data_Dir);

	  listdir(fileno(stdout), "/");
	  exit(0);
     }

     if (!RunFromInetd) {
	  printf("Data directory is %s\n", Data_Dir);
	  printf("Port is %d\n", GopherPort);
     }

     if (*LOGFile != '\0' && !RunFromInetd)
	  printf("Logging to File %s\n", LOGFile);

     if (*SecurityFile != '\0' && !RunFromInetd)
	  printf("Using Security file %s\n", SecurityFile);

     /*
      * Would like to setuid() here, but have to wait until after the
      * bind() in case we're going to be running on a privileged port.
      */

     if (uchdir(Data_Dir)) {
	  if (!RunIndex) {
	       fprintf(stderr, "Cannot change to data directory!! %s \n",Data_Dir);
	       exit(-1);
	  }
     }

     if (dochroot && getuid() != 0) {
	  fprintf(stderr, "Gopherd uses the privileged call chroot().  Please become root.\n");
	  exit(-1);
     }


     /** Open up the SecurityFile
         **Warning!** we have to do this over because the daemon_start
         function closes all fd's

	 This part just checks to see if the file exists...
      **/


     if (*SecurityFile != '\0') {
	  SECFileHandle = ufopen(SecurityFile, "r");

	  if (SECFileHandle == NULL) {
	       printf("Can't open the security file: %s\n", SecurityFile);
	       exit(-1);
	  }
	  fclose(SECFileHandle);
     }
     

#ifdef LOADRESTRICT

     if(RunFromInetd) {
	  if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, prog)) == NULL) 
	       exit(-1);
	  if (kvm_nlist(kd, nl) != 0) 
	       exit(-1);
	  if(nl[X_AVENRUN].n_type == 0) 
	       exit(-1);
	  if(kvm_read(kd,nl[X_AVENRUN].n_value,avenrun,sizeof avenrun) 
	     != sizeof avenrun) 
	       exit(-1);
	  if((sysload = (((double) avenrun[2]) / FSCALE)) > maxload) {
	       printf("- System Load exceeded (%4.2f > %4.2f). ",sysload,maxload);
	       printf("Please Retry Request later.\r\n.\r\n");
	       exit(1);
	  }
     }
#endif /*LOADRESTRICT*/     

     fflush(stderr);
     fflush(stdout);

     if (DEBUG == FALSE && RunFromInetd==FALSE)
	  daemon_start(0);


     /*** Hmmm, does this look familiar? :-) ***/


     if (*SecurityFile != '\0') {
	  SECFileHandle = ufopen(SecurityFile, "r");

	  if (SECFileHandle == NULL) {
	       printf("Can't open the security file: %s\n", SecurityFile);
	       exit(-1);
	  }
     }

     err_init();	/* does this look familiar too?? :-) */

     /** Ask the system what host we're running on **/

     Zehostname = GetDNSname(DOMAIN_NAME);


     if (RunFromInetd) {
	  /** Ask the system which port we're running on **/
	  int newport=0;
	  if ((newport =GetPort(0)) !=0)
	       GopherPort=newport;

	  /*** Do the stuff for inetd ***/

	  while(do_command(0)!=0);	/* process the request */
	  exit(0);
     }

     /** Open a TCP socket (an internet stream socket **/
     
     if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	  err_dump("server: can't open stream socket");
     
     /** Bind our local address so that the client can send to us **/
     
     bzero((char *) &serv_addr, sizeof(serv_addr));
     serv_addr.sin_family 		= AF_INET;
     serv_addr.sin_addr.s_addr 	= htonl(INADDR_ANY);
     serv_addr.sin_port		= htons(GopherPort);
     
     if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <0)
	  err_dump("server: can't bind local address");
     
     /* have to setuid() here, in case we're using a privileged port */
     if ( setuid(uid) != 0 )
	  err_sys("Cannot setuid(%d): ",uid);

     listen(sockfd, 5);
     
     for ( ; ; ) {
	  /*
	   * Wait for a connection from a client process.
	   * This is an example of a concurrent server.
	   */
	  
	  clilen = sizeof(cli_addr);
	  newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr,
			     &clilen);

	  /*** weird.. with this thing here our gethostaddrs work. 
            without it, it fails..... why?
           ***/
	  
	  if (newsockfd < 0)
	       err_dump("server: accept error");
	  
	  if ( (childpid = fork()) < 0)
	       err_dump("server: fork error");
	  
	  else if (childpid == 0) {	/* Child process */
	       close(sockfd);		/* close original socket */

	       while(do_command(newsockfd)!=0);	/* process the request */
	       exit(0);
	  }
	  /** clean up any zombie children **/
	  sig_child();

	  close(newsockfd); 		/* parent process */
     }
}


/*
 *
 *  Code stolen from nntp.....
 *
 * inet_netnames -- return the network, subnet, and host names of
 * our peer process for the Internet domain.
 *
 *      Parameters:     "sock" is our socket, which we don't need.
 *                      "sin" is a pointer to the result of
 *                      a getpeername() call.
 *                      "host_name"
 *                      is filled in by this routine with the
 *                      corresponding ASCII names of our peer.
 *      Returns:        Nothing.
 *      Side effects:   None.
 */

inet_netnames(sock, sin, host_name)
        int                     sock;
        struct sockaddr_in      *sin;
        char                    *host_name;
{
        u_long                  net_addr;
        struct hostent          *hp;
        struct netent           *np;

        net_addr = inet_netof(sin->sin_addr);   /* net_addr in host order */
        np = getnetbyaddr(net_addr, AF_INET);

        hp = gethostbyaddr((char *) &sin->sin_addr.s_addr,
                sizeof (sin->sin_addr.s_addr), AF_INET);

        if (hp != NULL)
                (void) strcpy(host_name, hp->h_name);
        else
                (void) strcpy(host_name, inet_ntoa(sin->sin_addr));
}



/*
 * This finds the current peer and the time and  jams it into the
 * logfile (if any) and adds the message at the end
 */

void
LOGGopher(sockfd, message)
  int sockfd;
  char *message;
{
     struct sockaddr sa;
     int             length;
     static char     host_name[256];
     time_t          Now;
     char            *cp;
                     /* cp + ' ' + host_name + ' : ' + MAXLINE + '\n' + '\0' */
     char            buf[286+MAXLINE];
     struct flock    lock;
   

     host_name[0] = '\0';

     if (LOGFileDesc != -1) {
	  
	  if (sockfd > -1) {
	       length = sizeof (sa);
	       getpeername(sockfd, &sa, &length);
	       inet_netnames(sockfd, &sa, host_name);
	  }

	  lock.l_type = F_WRLCK;
	  lock.l_whence = SEEK_SET;
          lock.l_start = 0L;
          lock.l_len = 0L;
          fcntl(LOGFileDesc, F_SETLKW, &lock);

	  time(&Now);         /* Include this in the lock to make sure */
	  cp = ctime(&Now);   /*  log entries are chronological */
	  ZapCRLF(cp);

          /* someone else may have written to the file since we opened it */
          lseek(LOGFileDesc, 0L, SEEK_END);
  
          sprintf(buf, "%s %d %s : %s\n", cp, getpid(), host_name, message);
          write(LOGFileDesc, buf, strlen(buf));
          
          /* unlock the file */
          lock.l_type = F_UNLCK;
          fcntl(LOGFileDesc, F_SETLKW, &lock);
	  
	  if (DEBUG)
	       printf("%s %d %s : %s\n", cp, getpid(), host_name, message);
	  
     }
}


process_mailfile(sockfd, Mailfname)
  int sockfd;
  char *Mailfname;
{
     FILE *Mailfile;
     char Zeline[MAXLINE];
     char outputline[MAXLINE];
     char Title[MAXLINE];
     long Startbyte=0, Endbyte=0, Bytecount=0;
     boolean flagged = 0;

     Mailfile = rfopen(Mailfname, "r");

     if (Mailfile == NULL) {
	  Abortoutput(sockfd, "Cannot access file");
	  return;
     }

     while (fgets(Zeline, MAXLINE, Mailfile) != NULL) {
	  if (strncmp(Zeline, "Subject: ", 9)==0 && (!flagged)) {
	       flagged =1;
	       strcpy(Title, Zeline + 9);
	       ZapCRLF(Title);
	       if (DEBUG)
		    fprintf(stderr, "Found title %s", Title);
	  }
	  
	  if (is_mail_from_line(Zeline)==0) {
	       Endbyte = Bytecount;
	       flagged =0;

	       if (Endbyte != 0) {
		    sprintf(outputline, "0%s\tR%d-%d-%s\t%s\t%d\r\n", 
			    Title, Startbyte, Bytecount, Mailfname,
			    Zehostname, GopherPort);
		    if (writestring(sockfd, outputline) < 0)
			 LOGGopher(sockfd, "Client went away"), exit(-1);
		    Startbyte=Bytecount;
		    *Title = '\0';
	       }
	  }

	  Bytecount += strlen(Zeline);
     }

     if (*Title != '\0') {
	  sprintf(outputline, "0%s\tR%d-%d-%s\t%s\t%d\r\n", 
		  Title, Startbyte, Bytecount, Mailfname, 
		  Zehostname, GopherPort);
	  if (writestring(sockfd, outputline)<0)
	       LOGGopher(sockfd, "Client went away"),exit(-1);
     }	  


     if (writestring(sockfd, ".\r\n")<0)
	  LOGGopher(sockfd, "Client went away"),exit(-1);
}



boolean
Can_Read(sockfd)
  int sockfd;
{
     struct sockaddr sa;
     int             length;
     char            host_name[256];
     char *cp;
     boolean         MatchReturn;
     char            inputline[MAXLINE];

     if (*SecurityFile == '\0')
	  return(TRUE);

     length = sizeof (sa);
     getpeername(sockfd, &sa, &length);
     inet_netnames(sockfd, &sa, host_name);

     fseek(SECFileHandle, 0, 0);  /** Go to beginning of Security File **/
     
     while (fgets(inputline, MAXLINE, SECFileHandle) != NULL) {
	  ZapCRLF(inputline);

	  MatchReturn = TRUE;
	  cp = inputline;

	  if (*cp == '!') {
	       MatchReturn = FALSE;
	       cp++;
	  }
	  
	  if (isdigit(*cp)) {
	       /** Internet address x.x.x.x ..***/
	       /** Check for a match from the beginning **/
	       if (strstr(host_name,cp) == host_name)
		    return(MatchReturn);
		    
	  }
	  
	  else if (*cp == '*') {
	       return(MatchReturn);
	  }

	  else if (*cp != '#') {
	       /*** Not a comment, must be a host name **/
	       if ((strstr(host_name, cp) + strlen(cp)) == (host_name + strlen(host_name))) {
		    return(MatchReturn);
	       }
	  }
     }

     /*** Hmmm, didn't find a match...  Let em have it***/
	  
     return(TRUE);
}



int
do_command(sockfd)
  int sockfd;
{
     char inputline[MAXLINE];
     int length;		/* Length of the command line */
     char logline[MAXLINE];
     char *selstr;

     /*** Reopen the security file ***/

     if (*SecurityFile != '\0') {
	  SECFileHandle = ufopen(SecurityFile, "r");

	  if (SECFileHandle == NULL) {
	       LOGGopher(sockfd, "Security File dissappeared!");
	       exit(-1);
	  }
     }
     
     /*** Reopen the log file ***/

     if (LOGFile[0] != '\0') {
	  LOGFileDesc = uopen(LOGFile, O_WRONLY | O_APPEND |O_CREAT, 0755);
	  
	  if (LOGFileDesc == -1) {
	       printf("Can't open the logfile: %s\n", LOGFile);
	       exit(-1);
	  }
     }


     /** Change our root directory **/
     
     if ( dochroot ) {
	  if (chroot(Data_Dir)) {
	       Abortoutput(sockfd, "Data_Dir dissappeared!");
	       exit(-1);
	  }
	  uchdir("/");	/* needed after chroot */
     }

     if (setuid(uid)) {
	  LOGGopher(sockfd, "Can't set UID!");
	  Abortoutput(sockfd, "Can't set UID!");
	  exit(-1);
     }
	  
     length = readline(sockfd, inputline, MAXLINE); /** Get the line **/

     if (length <= 0) {
	  close(sockfd);
	  err_quit("getcommand: readline error");
     }
     
     ZapCRLF(inputline);


     /*
      * Decide if we're an HTML server or not...
      */

     if (strncmp(inputline, "GET /", 5) == 0) {
	  char *htmlcpin;
	  char *htmlcpout;

	  UsingHTML = TRUE;
	  selstr = inputline+5;

	  /** Convert the hex things back to text... ***/
	  Fromhexstr(selstr, selstr);

     } else
	  selstr = inputline;

     if (RunIndex) {
	  /*** Run like the old gindexd thing. ***/
	  
	  char tempstr[512];
	  
	  uchdir("/");

	  strcpy(tempstr, Data_Dir);
	  strcat(tempstr, "\t");
	  if (*selstr == '\t')
	       strcat(tempstr, selstr+1);
	  else
	       strcat(tempstr, selstr);

	  strcpy(Data_Dir, "/");
	  
	  if (DEBUG)
	       writestring(sockfd, tempstr);


	  Do_IndexTrans(sockfd, tempstr);
	  return(0);
     }

     /*** With the funky new capability system we can just check the
          first letter, end decide what the object refers to. ***/

     switch (*selstr) {
     case '\0':
     case '\t':

	  /*** The null capability, so it's not a file, probably wants
	       to talk to a directory server ***/

	  /*** we'll just do a "list" of the root directory, with no user
	       capability.  ***/

	  listdir(sockfd, "/");
	  LOGGopher(sockfd, "Root Connection");
	  break;

     case 'h':
	  /*** A raw html file ***/
	  /*** Turn off html'ing and just dump the file ***/
	  UsingHTML = FALSE;

     case '0':
	  /*** It's a generic file capability ***/
	  printfile(sockfd, selstr+1, 0, -1);

	  /*** Log it ***/
	  strcpy(logline, "retrieved file ");
	  strcat(logline, selstr+1);
	  LOGGopher(sockfd, logline);
	  break;

     case '1':
	  /*** It's a directory capability ***/
	  listdir(sockfd, selstr+1);

	  /** Log it **/
	  strcpy(logline, "retrieved directory ");
	  strcat(logline, selstr+1);
	  LOGGopher(sockfd, logline);

	  break;

     case '7':
	  /*** It's an index capability ***/
	  Do_IndexTrans(sockfd, selstr+1);

	  break;

     case '9':
	  /*** It's a binary thingie... ***/
	  /*** Okay, it's not a sound, but what the heck.... ***/
	  echosound(sockfd, selstr+1);
	  
	  /* Log it */
	  strcpy(logline, "retrieved binary ");
	  strcat(logline, selstr+1);
	  LOGGopher(sockfd, logline);
	  break;

     case 's':
	  /*** It's a sound capability ***/
	  echosound(sockfd, selstr+1);

	  /* Log it */
	  strcpy(logline, "retrieved sound ");
	  strcat(logline, selstr+1);
	  LOGGopher(sockfd, logline);
	  break;

     case 'm':
	  /*** This is an internal identifier ***/
	  /*** The m paired with an Objtype of 1 makes a mail spool file
	       into a directory.
	  ***/

	  process_mailfile(sockfd, selstr + 1);

	  /** Log it **/
	  strcpy(logline, "retrieved maildir ");
	  strcat(logline, selstr+1);
	  LOGGopher(sockfd, logline);

	  break;

     case 'R':
	  /*** This is an internal identifier ***/
	  /*** The R defines a range  ****/
	  /*** The format is R<startbyte>-<endbyte>-<filename> **/
     {
	  int startbyte, endbyte;
	  char *cp, *oldcp;

	  cp = strchr(selstr+1, '-');
	  
	  if (cp == NULL) {
	       Abortoutput(sockfd, "Range specifier error");
	       break;
	  }
	  
	  *cp = '\0';
	  startbyte = atoi(selstr+1);
	  oldcp = cp+1;

	  cp = strchr(oldcp, '-');
	  
	  if (cp == NULL) {
	       Abortoutput(sockfd, "Range specifier error");
	       exit(-1);
	  }

	  *cp = '\0';
	  endbyte = atoi(oldcp);
	  oldcp = cp + 1;
	  if (DEBUG)
	       fprintf(stderr, "Start: %d, End: %d  File: %s\n", startbyte, endbyte, oldcp);

	  writestring(sockfd, "This section is from the document '");
	  writestring(sockfd, oldcp);
	  writestring(sockfd, "'.\r\n\r\n");
	  printfile(sockfd, oldcp, startbyte, endbyte);

	  /*** Log it ***/
	  sprintf(logline, "retrieved range %d - %d of file %s", startbyte, endbyte, oldcp);
	  LOGGopher(sockfd, logline);
	  break;
     }

     case 'f':
	  if (strncmp(selstr, "ftp:",4)==0){
	       SendFtpQuery(selstr+4);
	       TranslateResults(sockfd);
	       break;
	  }
	  sprintf(logline, "retrieved %s", selstr);
	  LOGGopher(sockfd, logline);
	  break;

     case 'e':
	  if (strncmp(selstr, "exec:", 5)==0) {
	       /* args are between colons */
	       char *args, *command;
	       
	       command = strrchr(selstr + 5, ':');
	       if ((command == NULL) || (command == selstr + 5))
		    break;
	       
	       args = selstr+5;
	       *command = '\0';
	       command++;
	       
	       EXECargs = selstr+5;

	       printfile(sockfd, command, 0, -1);
	  }
	  break;

     case 'w':
     {
	  if (strncmp(selstr, "waissrc:", 8) == 0) {
	       SearchRemoteWAIS(sockfd, selstr+8);
	       break;
	  }
	  else if (strncmp(selstr, "waisdocid:", 10) == 0) {
	       Fetchdocid(sockfd, selstr+10);
	       break;
	  }
     }


     default:
	  /*** Hmmm, must be an old link... Let's see if it exists ***/

	  switch (isadir(selstr)) {
	  case -1:
	       /* no such file */
	       sprintf(logline, "'%s' does not exist", selstr);
	       LOGGopher(sockfd, logline);
	       Abortoutput(sockfd, logline);
	       break;

	  case 0:
	       /* it's a file */
	       printfile(sockfd, selstr, 0, -1);
	       
	       /* Log it... */
	       strcpy(logline, "retrieved file ");
	       strcat(logline, selstr);
	       LOGGopher(sockfd, logline);

	       break;

	  case 1:
	       /* it's a directory */
	       listdir(sockfd, selstr);

	       /* Log it */
	       strcpy(logline, "retrieved directory ");
	       strcat(logline, inputline);
	       LOGGopher(sockfd, logline);

	       break;
	  }
     }

     return(0);
}

/*
 * Cache timeout value.
 *   If cache is less than secs seconds old, it's ok.
 *   Otherwise, compare time of cache to dir and all files in dir and dir/.cap.
 *   If cache is newest, it's ok, otherwise it must be rebuilt.
 * 
 * Not really great for big directories, but better in general for smaller
 * directories..
 */

boolean
Cachetimedout(cache, secs, dir)
  char *cache;
  int secs;
  char *dir;
{
     STATSTR       buf;
     int           result;
     time_t        now;
     int           cachetime;
     char          cappath[512];
     DIR           *ZeDir;
     struct dirent *dp;

     result = rstat(cache, &buf);

     if (result != 0)
	  return(-1);

     time(&now);
     
     if (DEBUG) 
	  printf("Cache now: %d, cache file: %d", now,buf.st_mtime);
     
     if ( now < (buf.st_mtime + secs))
	  return(FALSE);

          cachetime = buf.st_mtime;

     
     /** Check the directory, see if it's been modified... **/
     result = rstat(dir, &buf);

     if (result == 0 && cachetime < buf.st_mtime)
	  return(TRUE);
     
     if ((ZeDir = ropendir(dir)) == NULL)
          return(TRUE);
 
     for (dp = readdir(ZeDir); dp != NULL; dp = readdir(ZeDir)) {
	  result = rstat(dp->d_name, &buf);
	  if (result == 0 && cachetime < buf.st_mtime) {
	       closedir(ZeDir);
	       return(TRUE);
	  }
     }
     closedir(ZeDir);

     sprintf(cappath, "%s/.cap", dir);
     if ((ZeDir = ropendir(cappath)) == NULL)
          return(FALSE);
     
     for (dp = readdir(ZeDir); dp != NULL; dp = readdir(ZeDir)) {
	  result = rstat(dp->d_name, &buf);
	  if (result == 0 && cachetime < buf.st_mtime) {
	       closedir(ZeDir);
	       return(TRUE);
	  }
     }
     closedir(ZeDir);
     
     return(FALSE);
}

/*
 * Returns true (1) for a directory
 *         false (0) for a file
 *         -1 for anything else
 */

boolean
isadir(path)
  char *path;
{
     STATSTR buf;
     int result;

     result = rstat(path, &buf);

     if (result != 0)
	  return(-1);
     
     if (S_ISDIR(buf.st_mode)) {
	  if (! access(path, F_OK))
	       return(1);
	  else
	       return(-1);
     }
     else if (S_ISREG(buf.st_mode))
	  return(0);
     else
	  return(-1);
}


/*
 * This function tries to find out what type of file a pathname is.
 * It then fills in the VAR type variables ObjType & ServerPath with
 * corresponding info.
 */

Getfiletypes(newpath, filename, ObjType, ServerPath)
  char *newpath;
  char *filename;
  char **ObjType;
  char **ServerPath;
{
     boolean dirresult;
     int Zefilefd;
     static char Zebuf[64];
     char *cp;
     static char Selstr[512];

     
     if (ServerPath != NULL)	     /* Don't overwrite existing path if any */
	  *ServerPath = Selstr; 


     dirresult = isadir(filename);

     if (dirresult == -1) {             /** Symlink or Special **/
	  *ObjType = "3";
	  return;
     }

     if (dirresult == 1) {
	  *ObjType = "1";
	  *Selstr = '1';
	  strcpy(Selstr +1, newpath);
	  return;
     }
		 
     
     else {	      /** Some kind of data file.... */

	  /** Macintosh HQX files **/
	  
	  if ((strcmp(filename+strlen(filename)-4, ".hqx")) == 0) {
	       *ObjType = "4";
	       *Selstr = '0';
	       strcpy(Selstr + 1, newpath);
	       return;
	  }

	  /** SGML files end in .html **/

	  if ((strcmp(filename+strlen(filename)-5, ".html")) ==0) {
	       *ObjType = "h";
	       strcpy(Selstr, "GET /h");
	       strcat(Selstr, newpath);
	       return;
	  }

#ifdef WAISSEARCH
	  if ((strcmp(filename+strlen(filename)-4, ".src")) == 0) {
	       *ObjType = "7";
	       strcpy(Selstr, "waissrc:");
	       strcat(Selstr, newpath);
	       return;
	  }
#endif

	  if ((strcmp((char*)filename+strlen(filename)-6,".tar.Z"))== 0){
	       *ObjType = "9";
	       *Selstr = '9';
	       strcpy(Selstr + 1, newpath);
	       return;
	  }

	  /*** Test and see if the thing exists... and is readable ***/
	  
	  if ((Zefilefd = ropen(filename, O_RDONLY)) < 0) {
	       *ObjType = "3";
	       return;
	  }
	  
	  bzero(Zebuf, 64);
	  read(Zefilefd, Zebuf, 64);
	  close(Zefilefd);
	  
	  /*** Check the first few bytes for sound data ***/
	  
	  cp = Zebuf;

	  if (*cp++ == '.' && *cp++ == 's' && *cp++ == 'n' && *cp++ == 'd') {
	       *ObjType = "s";
	       *Selstr = 's';
	       strcpy(Selstr+1, newpath);
	       
	       return;
	  }

	  /*** Check and see if it's mailbox data ***/
	  
	  if (is_mail_from_line(Zebuf)==0) {
	       *ObjType = "1";
	       *Selstr = 'm';
	       strcpy(Selstr+1, newpath);

	       return;
	  }
	  

	  /*** Check for uuencoding data ***/

	  cp = Zebuf;
	  
	  if (strncmp(cp,"begin",6) == 0)  {
	       *ObjType = "6";
	       *Selstr = '6';
	       strcpy(Selstr+1, newpath);
	       
	       return;
	  }
	  
	  /*** The default is a generic text file ***/

	  *ObjType = "0";
	  *Selstr = '0';
	  strcpy(Selstr + 1, newpath);
	  return;
     }

}




/*
** This function lists out what is in a particular directory.
** it also outputs the contents of link files.
**
** It also checks for the existance of a .cache file if caching is
** turned on...
**
** Ack is this ugly.
*/

void
listdir(sockfd, pathname)
  int sockfd;
  char *pathname;
{
     DIR                   *ZeDir;
     FILE                  *DotFile;
     int                   i;
     char                  *cp, ch;
     static char           ZeObjType[3];
     char                  sidename[256];
     char                  filename[256];
     static char           newpath[512];
#ifdef DL
     char                  dlpath[2];    /*** for DL**/
     char                  *dlout;
#endif
     FILE                  *SideFile;
     static GopherStruct   *Gopherp = NULL;
     char	           *Typep, *Pathp, *cachefile;
     struct dirent         *dp;


     /*** Make our gopherobj ****/
     if (Gopherp == NULL)
	  Gopherp = GSnew();

     if (rchdir(pathname)<0) {
	  Abortoutput(sockfd, "- Cannot access that directory");
          return;
     }
     
     if (UsingHTML)
	  cachefile = ".cache.html";
     else
	  cachefile = ".cache";

     if (Caching && Cachetimedout(cachefile, CACHE_TIME, ".")==FALSE) {
	  /** Cache is still active, spit out the cache file 
	      and get outta here..... **/
	  printfile(sockfd, cachefile, 0, -1);
	  return;
     }

     /** If we didn't cache then we have to use a sorting directory... **/
     SortDir = GDnew(64);

     /* open "." since we just moved there - makes it work when not
	chroot()ing and using relative paths */
     if ((ZeDir = ropendir(".")) == NULL) {
	  Abortoutput(sockfd, "Cannot get that directory");
	  return;
     }

     for (dp = readdir(ZeDir); dp != NULL; dp = readdir(ZeDir)) {

          strcpy(newpath, pathname);
          if (newpath[strlen(newpath)-1] != '/')
               strcat(newpath, "/");
          strcat(newpath, dp->d_name);

	  strcpy(sidename, "./.cap/");
	  strcpy(filename, dp->d_name);
	  strcat(sidename, dp->d_name);
	  
	  /** Only chew list out files that don't start with a dot **/
	  /** or aren't named dev, usr, bin, etc, or core.         **/

	  if (filename[0] == '.' && 
	      isadir(filename)==0  &&
	      strncmp(filename, ".cache", 6) !=0) {
	       /*** This is a link file, let's process it ***/
	       int linkfd;

	       linkfd = uopen(filename, O_RDONLY);

	       if (linkfd >0) {
		    GDfromLink(SortDir, linkfd, Zehostname, GopherPort);
		    close(linkfd);
	       }
	       
	  }

	  if ((filename[0] != '.')
	      && strcmp(filename, "bin") != 0 && 
	         strcmp(filename, "dev") != 0 &&
	         strcmp(filename, "usr") != 0 &&
	         strcmp(filename, "core")!= 0 &&
	         strcmp(filename, "tmp") != 0 &&
	         strcmp(filename, "etc") != 0)   {
	       
	       /** Check to see if there's a set-aside file with more info ***/
	       /** But first initialize the Gopherstruct **/

	       GSinit(Gopherp);

	       GSsetHost(Gopherp, Zehostname);
	       GSsetPort(Gopherp, GopherPort);
	       Typep = Pathp = NULL;
	       

	       Getfiletypes(newpath, filename, &Typep, &Pathp);
	       if (*Typep =='3')
		    continue;
	       
	       GSsetType(Gopherp, Typep[0]);
	       GSsetPath(Gopherp, Pathp);


 	       if (GSgetTitle(Gopherp) == NULL) {
		    /*** Check to see if we have a compressed file ***/
		    
		    if (strcmp(filename + strlen(filename) -2, ".Z") ==0 &&
			strcmp(filename + strlen(filename) -6, ".tar.Z") != 0)
			 filename[strlen(filename) - 2] = '\0';
		    
		    GSsetTitle(Gopherp, filename);
	       }
	       else
		    GSsetTitle(Gopherp, filename);


	       if ((SideFile = rfopen(sidename, "r"))!=0) {
		    if (DEBUG == TRUE)
			 printf("Side file name: %s\n", sidename);
		    Process_Side(SideFile, Gopherp);
	       }

#ifdef DL
	       /* Process a "dl" description if there is one! */

	       dlpath[0] = '.';
	       dlpath[1] = '\0';
	       dlout = getdesc(NULL,dlpath,filename,0);

	       if (DEBUG)
		    printf("dl: %s %s %s\n", dlpath, filename, dlout);
	       if (dlout != NULL) {
		    GSsetTitle(Gopherp, dlout);
	       }
#endif

	       /*** Add the entry to the directory ***/
  
	       GDaddGS(SortDir, Gopherp);

	  }
     }
     
     GDsort(SortDir);

     if (UsingHTML)  {
	  int aboutfd;

	  aboutfd = uopen(".about.html", O_RDONLY);
	  if (aboutfd > 0) {
	       while (readline(aboutfd, newpath, 512))
		    writestring(sockfd, newpath);
	       close(aboutfd);
	  }
	       
	  GDtoNetHTML(SortDir, sockfd);
     }
     else {
	  GDtoNet(SortDir, sockfd);
	  writestring(sockfd, ".\r\n");
     }

     /*
      * Write out the cache... *After* we send out the data to the net.
      */
     if (Caching) {
	  int cachefd;

	  cachefd = uopen(cachefile, O_WRONLY|O_CREAT|O_TRUNC, 0755);

	  if (cachefd != 0) {
	       if (DEBUG) {
		    printf("Caching directory...\n");
	       }
	       if (UsingHTML) {
		    int aboutfd; 

		    aboutfd = uopen(".about.html", O_RDONLY);
		    if (aboutfd > 0) {
			 while (readline(aboutfd, newpath, 512))
			      writestring(cachefd, newpath);
			 close(aboutfd);
		    }
		    
		    GDtoNetHTML(SortDir, cachefd);
	       }
	       else
		    GDtoNet(SortDir, cachefd);

	       close(cachefd);
	  }
     }

     closedir(ZeDir);
}


/*
 * This processes a file containing any subset of
 * Type, Name, Path, Port or Host, and returns pointers to the
 * overriding data that it finds.
 *
 * The caller may choose to initialise the pointers - so we don't
 * touch them unless we find an over-ride.
 */

Process_Side(sidefile, Gopherp)
  FILE *sidefile;
  GopherObj *Gopherp;
{
     char inputline[MAXLINE];
     static char ItemType[3];
     static char ItemName[255];
     static char Path[255];
     static char Host[64];
     static char Port[10];
     char *cp;


     inputline[0] = '\0';

     for (;;) {
	  for (;;) {
	       cp = fgets(inputline, 1024, sidefile);
	       if (inputline[0] != '#' || cp == NULL)
		    break;
	  }
	  
	  /*** Test for EOF ***/
	  if (cp==NULL)
	       break;
	  
	  ZapCRLF(inputline);  /* should zap tabs as well! */

	  /*** Test for the various field values. **/
	  
	  if (strncmp(inputline, "Type=", 5)==0) {
	       GSsetType(Gopherp, inputline[5]);
	       if (inputline[5] == '7') {
		    /*** Might as well set the path too... ***/
		    *(GSgetPath(Gopherp)) = '7';
	       }
	  }

	  else if (strncmp(inputline, "Name=", 5)==0) {
	       GSsetTitle(Gopherp, inputline+5);
	  }

	  else if (strncmp(inputline, "Host=", 5)==0) {
	       GSsetHost(Gopherp, inputline+5);
	  }

	  else if (strncmp(inputline, "Port=", 5)==0) {
	       GSsetPort(Gopherp, atoi(inputline+5));
	  }

	  else if (strncmp(inputline, "Path=", 5)==0) {
	       GSsetPath(Gopherp, inputline+5);
	  }

	  else if (strncmp(inputline, "Numb=", 5)==0) {
	       GSsetNum(Gopherp, atoi(inputline+5));
	  }

	  else if (strncmp(inputline, "Name=", 5)==0) {
	       GSsetTitle(Gopherp, inputline+5);
	  }

     }

     fclose(sidefile);
}





/*
** This function opens the specified file, starts a zcat if needed,
** and barfs the file across the socket.
**
** It now also checks and sees if access is allowed
**
**
*/

void
printfile(sockfd, pathname, startbyte, endbyte)
  int sockfd;
  char *pathname;
  int startbyte, endbyte;
{
     FILE *ZeFile;
     char inputline[512];
     int i;
     char Zcatcommand[256];
     char *cp;


     /*
      * This chdir is unnecssary, since we have not gone anywhere since
      * the chroot.
      */
     /* rchdir("/"); */

     if (UsingHTML && strcmp(pathname, ".cache.html") != 0) {
	  writestring(sockfd, "<XMP>\r\n");
     }

     /*** Check and see if the peer has permissions to read files ***/
     
     if (Can_Read(sockfd) == FALSE) {
	  if (writestring(sockfd, BUMMERSTR) <0)
	       LOGGopher(sockfd, "Client went away"), exit(-1);
	  writestring(sockfd, "\r\nBummer.....\r\n.\r\n");
	  close(sockfd);
	  return;
     }

     if ( (ZeFile = rfopen(pathname, "r")) == NULL) {
	  /*
	   * The specified file does not exist
	   */
	  char notexistline[256];
	  sprintf(notexistline, "'%s' does not exist!!", pathname);
	  Abortoutput(sockfd, notexistline);

	  return;
     }

     if (startbyte != 0)
	  fseek(ZeFile, startbyte, 0);

     {
	  FILE *pp;
	  if (pp = specialfile(ZeFile, pathname)) {
	       fclose(ZeFile);
	       ZeFile = pp;
	  }
     }


     while (fgets(inputline, MAXLINE, ZeFile) != NULL) {

	  ZapCRLF(inputline);
	  if (writestring(sockfd, inputline) <0)
	       LOGGopher(sockfd, "Client went away"), exit(-1);
	  if (writestring(sockfd, "\r\n"))
	       LOGGopher(sockfd, "Client went away"), exit(-1);

	  if (endbyte >0) {
	       if (ftell(ZeFile) >= endbyte)
		    break;
	  }
     }

     Specialclose(ZeFile);

     if (UsingHTML) {
	  writestring(sockfd, "</XMP>\r\n");
     }

     if (writestring(sockfd, ".\r\n")<0)
	  LOGGopher(sockfd, "Client went away"), exit(-1);
}


#define BUFSIZE 1400  /* A pretty good value for ethernet */

void
echosound(sockfd, filename)
  int sockfd;
  char *filename;
{

     FILE *sndfile;
     unsigned char in[BUFSIZE];
     register int j;
     int gotbytes;

     if (strcmp(filename, "-") == 0) {
	  /*** Do some live digitization!! **/
	  sndfile = popen("record -", "r");
     }
     else
	  sndfile = rfopen(filename, "r");

     while(1) {
	  gotbytes = fread(in, 1, BUFSIZE, sndfile);
	  
	  if (gotbytes == 0)
	       break;       /*** end of file or error... ***/

          j = writen(sockfd, in, gotbytes);

	  if (j == 0)
	       break;       /*** yep another error condition ***/

     }
}

