/********************************************************************
 * $Author: lindner $
 * $Revision: 1.2.1.6 $
 * $Date: 1993/01/09 02:37:29 $
 * $Source: /home/mudhoney/GopherSrc/release1.11/gopherd/RCS/gopherd.c,v $
 * $State: Rel $
 *
 * Paul Lindner, University of Minnesota CIS.
 *
 * Copyright 1991, 1992 by the Regents of the University of Minnesota
 * see the file "Copyright" in the distribution for conditions of use.
 *********************************************************************
 * MODULE: gopherd.c
 * Routines to implement the gopher server.
 *********************************************************************
 * Revision History:
 * $Log: gopherd.c,v $
 * Revision 1.2.1.6  1993/01/09  02:37:29  lindner
 * Added ftp gateway logging
 * Changed gethostbyaddr() to work on UNICOS
 *
 * Revision 1.2.1.5  1993/01/05  21:32:25  lindner
 * Fixed printfile() so that it deals with files with a period on a line
 * all by itself correctly.  Also removed one writestring() call.
 *
 * Revision 1.2.1.4  1993/01/05  21:10:38  lindner
 * Removed setuid() call that broke when using -u and chroot()
 *
 * Revision 1.2.1.3  1992/12/22  21:55:30  lindner
 * fixed typo, int was meant to be double..  Grrrr
 *
 * Revision 1.2.1.2  1992/12/22  21:48:15  lindner
 * Added extern maxload, fixes bug with previous version...
 *
 * Revision 1.2.1.1  1992/12/21  20:51:52  lindner
 * Moved loadrestrict stuff to kernutils.c, also the server
 * can now restrict for load average from standalone mode.
 *
 * Revision 1.2  1992/12/13  06:13:59  lindner
 * Added code to do readline timeouts <mtm>
 *
 * Revision 1.1  1992/12/10  23:13:27  lindner
 * gopher 1.1 release
 *
 *
 *********************************************************************/


/* 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();
extern double  maxload;
void Process_Side();



/* This function is called on a read timeout from the network */

#include <setjmp.h>
jmp_buf env;

SIGRETTYPE read_timeout()
{
     longjmp(env,1);
}


/*
 * 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;
     char *cp;

     cp = GDCgetHostname(Config);
     if (*cp != '\0')
	  return(cp);

     DNSname[0] = '\0';
     /* 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 = gethostbyname( 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);

}


/*
 * This function returns a socket file descriptor bound to the given port
 */

int
bind_to_port(port) 
  int port;
{
    struct sockaddr_in serv_addr;
    struct linger linger;
    int reuseaddr = 1;
    int sockfd;

     
     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(port);
     
     if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <0)
	  err_dump("server: can't bind local address");
     linger.l_onoff = linger.l_linger = 0;
     if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *)&linger,
		    sizeof (linger)) < 0)
	  err_dump("server: can't turn off linger sockopt");

     if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseaddr,
		    sizeof(reuseaddr)) < 0)
	  err_dump("server: can't set REUSEADDR!");

    return(sockfd);
}

void
main(argc, argv)
  int 	argc;
  char 	*argv[];
{
     int                childpid;
     int                sockfd, newsockfd;
     int                clilen;
     struct sockaddr_in cli_addr;
     boolean            OptionsRead = FALSE;

 
     /*** 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 **/

     Config = GDCnew();  /** Set up the general configuration **/
 
     while ((c = getopt(argc, argv, "mCDIcL:l:o: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 **/
	       maxload = atof(optarg);
	       break;

	  case 'l':  /** logfile name **/
	       if (*optarg == '/')
		    GDCsetLogfile(Config, optarg);
	       else {
		    char tmpstr[256];
		    
		    getwd(tmpstr);
		    strcat(tmpstr, "/");
		    strcat(tmpstr, optarg);
		    
		    GDCsetLogfile(Config, tmpstr);
	       }
	       break;
	       
	  case 'o': /** option file **/
	       if (*optarg == '/')
		    GDCfromFile(Config, optarg);
	       else {
		    char tmpstr[256];
		    getwd(tmpstr);
		    strcat(tmpstr, "/");
		    strcat(tmpstr, optarg);
		    GDCfromFile(Config, tmpstr);
	       }
	       OptionsRead = TRUE;
	       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, "   -o  override the default options file '%s'\n", CONF_FILE);
	  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("Warning! You really shouldn't run the server as root!...\n");


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

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

     /** Read the options in, if not overridden **/
     if (OptionsRead == FALSE)
	  GDCfromFile(Config, CONF_FILE);
     

     if (RunLS) {
	  Zehostname =GetDNSname(DOMAIN_NAME);
	  Caching = FALSE;

	  fflush(stdout);
	  uchdir(Data_Dir);

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

     if (!RunFromInetd) {
	  printf("Gopher Server, Copyright 1991,92 the Regents of the University of Minnesota\n");
	  printf("See the file 'Copyright' for conditions of use\n");
	  printf("Data directory is %s\n", Data_Dir);
	  printf("Port is %d\n", GopherPort);
     }

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

     /*
      * 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);
     }

     fflush(stderr);
     fflush(stdout);

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


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


     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 **/
     sockfd = bind_to_port(GopherPort);

     /* 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);

	  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
 *                      "host_name"
 *                      is filled in by this routine with the
 *                      corresponding ASCII names of our peer.
 *       
 *                      if there doesn't exist a hostname in DNS etal,
 *                      the IP# will be inserted for the host_name
 *
 *                      "ipnum" is filled in with the ascii IP#
 *      Returns:        Nothing.
 *      Side effects:   None.
 */

void
inet_netnames(sockfd, host_name, ipnum)
  int  sockfd;
  char *host_name;
  char *ipnum;
{
     struct sockaddr_in      sa;
     int                     length;
     u_long                  net_addr;
     struct hostent          *hp;

     length = sizeof(sa);
     getpeername(sockfd, &sa, &length);
     strcpy(ipnum, inet_ntoa(sa.sin_addr));
     strcpy(host_name, inet_ntoa(sa.sin_addr));

     hp = gethostbyaddr((char *) &sa.sin_addr,
			sizeof (sa.sin_addr.s_addr), AF_INET);
     
     if (hp != NULL)
	  (void) strcpy(host_name, hp->h_name);

}



/*
 * 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;
{
     static char     host_name[256];
     static char     ipnum[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) {
	       inet_netnames(sockfd,host_name, ipnum);
	  }

	  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);
	  
     }
}

void
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;
{
     int                length;
     char               host_name[256];
     char               ip[256];
     char               *cp;

     inet_netnames(sockfd, host_name, ip);

     return(GDCCanRead(Config, host_name, ip));
}

boolean
Can_Browse(sockfd)
  int sockfd;
{
     int                length;
     char               host_name[256];
     char               ip[256];
     char               *cp;

     inet_netnames(sockfd, host_name, ip);

     return(GDCCanBrowse(Config, host_name, ip));
}

boolean
Can_Search(sockfd)
  int sockfd;
{
     int                length;
     char               host_name[256];
     char               ip[256];
     char               *cp;

     inet_netnames(sockfd, host_name, ip);

     return(GDCCanSearch(Config, host_name, ip));
}


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

     /*** Reopen the log file ***/

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

     if(LoadTooHigh()) {
	  LOGGopher(sockfd, "System Load Too High.");
	  Abortoutput(sockfd, "System is too busy right now. Please try again later.");
	  exit(-1);
     }

     /*** Make sure we do a tzset before doing a chroot() ***/
     tzset();

     /** 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);
     }
	  

     (void) signal(SIGALRM,read_timeout);
     (void) alarm(READTIMEOUT);

     if(setjmp(env)) {
	  LOGGopher(sockfd,"readline: Timed out!");
	  Abortoutput(sockfd,"readline: Timed out!");
	  exit(-1);
     }

     length = readline(sockfd, inputline, MAXLINE); /** Get the line **/

     /** Disable the alarm signal **/
     (void) alarm(0);
     (void) signal(SIGALRM,SIG_IGN);


     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) {

	  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 ***/
	  if (Can_Search(sockfd) == FALSE) {
	       char tmpstr[256];

	       Abortoutput(sockfd, GDCgetBummerMsg(Config));
		    
	       sprintf(tmpstr, "Denied access for %s", selstr+1);
	       LOGGopher(sockfd, tmpstr);
	       break;
	  }

	  Do_IndexTrans(sockfd, selstr+1);

	  break;

     case 'I':
     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.
	  ***/
	  if (Can_Browse(sockfd) == FALSE) {
	       char tmpstr[256];
	       Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	       sprintf(tmpstr, "Denied access for %s", selstr+1);
	       LOGGopher(sockfd, tmpstr);
	       break;
	  }

	  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 (Can_Browse(sockfd) == FALSE) {
	       char tmpstr[256];
	       Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	       sprintf(tmpstr, "Denied access for %s", selstr);
	       LOGGopher(sockfd, tmpstr);
	       break;
	  }

	  if (strncmp(selstr, "ftp:",4)==0){
	       sprintf(logline, "retrieved %s", selstr);
	       LOGGopher(sockfd, logline);

	       SendFtpQuery(sockfd, selstr+4);
	       TranslateResults(sockfd);
	       break;
	  }
	  break;

     case 'e':
	  if (Can_Browse(sockfd) == FALSE) {
	       char tmpstr[256];
	       Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	       sprintf(tmpstr, "Denied access for %s", selstr);
	       LOGGopher(sockfd, tmpstr);
	       break;
	  }

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

	       if (*(selstr+5) == ':' && *(selstr+6) == ':')
		    args = NULL;
	       else
		    args = selstr+5;

	       *command = '\0';
	       command++;
	       
	       EXECargs = args;

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

     case 'w':
     {
	  if (strncmp(selstr, "waissrc:", 8) == 0) {
	       if (Can_Search(sockfd) == FALSE) {
		    char tmpstr[256];
		    Abortoutput(sockfd,  GDCgetBummerMsg(Config));
		    sprintf(tmpstr, "Denied access for %s", selstr);
		    LOGGopher(sockfd, tmpstr);
		    break;
	       }

	       SearchRemoteWAIS(sockfd, selstr+8);
	       break;
	  }
	  else if (strncmp(selstr, "waisdocid:", 10) == 0) {
	       if (Can_Browse(sockfd) == FALSE) {
		    char tmpstr[256];
		    Abortoutput(sockfd,  GDCgetBummerMsg(Config));
		    sprintf(tmpstr, "Denied access for %s", selstr);
		    LOGGopher(sockfd, tmpstr);
		    break;
	       }
	       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;

     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);
     else
	  return(TRUE);

}

/*
 * 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.
 */
void
Getfiletypes(newpath, filename, ObjType, ServerPath)
  char *newpath;
  char *filename;
  char *ObjType;
  char **ServerPath;
{
     boolean dirresult;
     int Zefilefd;
     static char Zebuf[256];
     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.... */

	  /*** The default is a generic text file ***/

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

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

	  if (strncmp(cp, ".snd", 4)==0) {
	       *ObjType = 's';
	       *Selstr = 's';
	       strcpy(Selstr+1, newpath);
	  }

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

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

	  if (strncmp(cp,"begin",6) == 0)  {
	       *ObjType = '6';
	       *Selstr = '6';
	       strcpy(Selstr+1, newpath);
	  }
	  
	  /*** Check for GIF magic code ***/
	  
	  if (strncmp(cp, "GIF", 3) == 0) {
	       *ObjType = 'I';
 	       *Selstr = 'I';
 	       strcpy(Selstr + 1, newpath);
 	  }

	  /*** Okay, now let's check for the stuff from gopherd.conf files ***/
     {
	  char Gtype, *prefix;

     	  if (GDCExtension(Config, filename, &Gtype, &prefix)) {
	       *ObjType = Gtype;
	       strcpy(Selstr, prefix);
	       strcpy(Selstr+strlen(prefix), newpath);
	  }
     }
			   

     }

}




/*
** 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;
     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 (Can_Browse(sockfd) == FALSE) {
	  char tmpstr[256];
	  Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	  sprintf(tmpstr, "Denied access for %s", pathname);
	  LOGGopher(sockfd, tmpstr);
	  return;
     }


     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);
	  
	  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);
	       }
	       
	  }

	  /** 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] != '.') && !GDCignore(Config, filename)) {
	       
	       /** 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 = '\0';
	       Pathp = NULL;
	       

	       Getfiletypes(newpath, filename, &Typep, &Pathp);
	       
	       if (Typep =='3')
		    continue;
	       
	       GSsetType(Gopherp, Typep);
	       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.
 */

void
Process_Side(sidefile, Gopherp)
  FILE *sidefile;
  GopherObj *Gopherp;
{
     char inputline[MAXLINE];
     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';
	       }
	       if (inputline[5] == '9') {
		    /*** Might as well set the path too... ***/
		    *(GSgetPath(Gopherp)) = '9';
	       }
	  }

	  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];


     /*** Check and see if the peer has permissions to read files ***/
     
     if (Can_Read(sockfd) == FALSE) {
	  char tmpstr[256];
	  if (writestring(sockfd, GDCgetBummerMsg(Config)) <0)
	       LOGGopher(sockfd, "Client went away"), exit(-1);
	  writestring(sockfd, "\r\nBummer.....\r\n.\r\n");
	  sprintf(tmpstr, "Denied access for %s", pathname);
	  LOGGopher(sockfd, tmpstr);
	  return;
     }

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


     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(sockfd, ZeFile, pathname)) {
	       fclose(ZeFile);
	       ZeFile = pp;
	  }
     }


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

	  ZapCRLF(inputline);

	  /** Period on a line by itself, double it.. **/
	  if (*inputline == '.' && inputline[1] == '\0') {
	       inputline[1] = '.';
	       inputline[2] = '\0';
	  }

	  strcat(inputline, "\r\n");
	  if (writestring(sockfd, inputline) <0)
	       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 1523  /* 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 (Can_Read(sockfd) == FALSE) {
	  char tmpstr[256];
	  if (writestring(sockfd, GDCgetBummerMsg(Config)) <0)
	       LOGGopher(sockfd, "Client went away"), exit(-1);
	  writestring(sockfd, "\r\nBummer.....\r\n.\r\n");
	  sprintf(tmpstr, "Denied access for %s", filename);
	  LOGGopher(sockfd, tmpstr);
	  return;
     }

     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 ***/

     }
}

