/********************************************************************
 * lindner
 * 3.142
 * 1995/02/25 20:49:03
 * /home/arcwelder/GopherSrc/CVS/gopher+/gopherd/gopherd.c,v
 * Exp
 *
 * 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:
 * gopherd.c,v
 * Revision 3.142  1995/02/25  20:49:03  lindner
 * More timeouts..
 *
 * Revision 3.141  1995/02/25  06:42:55  lindner
 * Add write timeouts
 *
 * Revision 3.140  1995/02/17  18:32:35  lindner
 * Fix for mindex
 *
 * Revision 3.139  1995/02/16  22:34:01  lindner
 * Fix for no defined Maxsessions, A/UX signal handling
 *
 * Revision 3.138  1995/02/16  22:32:38  lindner
 * HTML icon support
 *
 * Revision 3.137  1995/02/13  19:07:57  lindner
 * Add MaxConnection limit for total server
 *
 * Revision 3.136  1995/02/11  06:22:25  lindner
 * Mods to use new statistics and concurrent session tracking
 *
 * Revision 3.135  1995/02/07  08:37:36  lindner
 * Rewrite of mailfile/multifile parsing
 *
 * Revision 3.134  1995/02/07  07:12:33  lindner
 * oops!
 *
 * Revision 3.133  1995/02/07  07:04:05  lindner
 * performance fixes
 *
 * Revision 3.132  1995/02/06  22:27:55  lindner
 * Use dynamic space for Data_Dir, remove RunLS
 *
 * Revision 3.131  1995/02/06  21:26:15  lindner
 * Misc performance fixes
 *
 * Revision 3.130  1995/02/02  17:13:53  lindner
 * Fix memory leaks
 *
 * Revision 3.129  1995/02/01  21:41:26  lindner
 * Fix for loco symbolic links
 *
 * Revision 3.128  1995/01/26  18:50:21  lindner
 * More efficient, more understandable, less buggy directory parser..
 *
 * Revision 3.127  1995/01/04  17:37:27  lindner
 * Make shell scripts line buffered...
 *
 * Revision 3.126  1994/12/20  17:20:15  lindner
 * Rewritten directory parser
 *
 * Revision 3.125  1994/12/15  17:47:49  lindner
 * Add script running capability
 *
 * Revision 3.124  1994/12/12  21:46:25  lindner
 * bonehead
 *
 * Revision 3.123  1994/12/12  21:03:50  lindner
 * Start towards CGI compliance
 *
 * Revision 3.122  1994/12/12  17:41:52  lindner
 * Hack around AIX
 *
 * Revision 3.121  1994/12/12  16:58:01  lindner
 * Add new AUTHresult code, AUTHRES_SYSERR
 *
 * Revision 3.120  1994/12/11  18:42:19  lindner
 * Fix for authenticated ask blocks
 *
 * Revision 3.119  1994/12/10  08:24:22  lindner
 * Bracket out the moddate setting code
 *
 * Revision 3.118  1994/12/10  06:13:22  lindner
 * Add error message for writing ask data
 *
 * Revision 3.117  1994/12/02  00:38:37  lindner
 * Fix for odd GUSER malloc
 *
 * Revision 3.116  1994/11/30  23:01:09  lindner
 * Allow username to be passed to the environment
 *
 * Revision 3.115  1994/11/30  22:26:33  lindner
 * Error fix
 *
 * Revision 3.114  1994/11/30  22:25:47  lindner
 * Error fix
 *
 * Revision 3.113  1994/11/30  21:53:25  lindner
 * Fix for one last Gticket problem
 *
 * Revision 3.112  1994/11/30  21:26:57  lindner
 * Fix for one last Gticket problem
 *
 * Revision 3.111  1994/11/08  19:48:50  lindner
 * Fix bug in mailfile processing
 *
 * Revision 3.110  1994/10/24  22:17:08  lindner
 * Add PDF type
 *
 * Revision 3.109  1994/10/21  01:45:53  lindner
 * refix linger problem
 *
 * Revision 3.108  1994/10/19  17:21:56  lindner
 * Fix for dreaded Malformed command dialog
 *
 * Revision 3.105  1994/10/13  05:17:48  lindner
 * Compiler complaint fixes
 *
 * Revision 3.104  1994/10/10  18:38:55  lindner
 * change in linger behavior
 *
 * Revision 3.103  1994/09/29  19:59:31  lindner
 * Force people to update their gopherd.conf files
 *
 * Revision 3.102  1994/08/18  22:28:28  lindner
 * Abstract for top level and malloc casts
 *
 * Revision 3.101  1994/08/01  21:56:01  lindner
 * Add proto
 *
 * Revision 3.100  1994/07/31  05:08:52  lindner
 * Add pid header file...
 *
 * Revision 3.99  1994/07/22  16:36:45  lindner
 * Fix bug in item_info for top level
 *
 * Revision 3.98  1994/07/22  15:02:32  lindner
 * Fix bugs..
 *
 * Revision 3.97  1994/07/21  22:07:21  lindner
 * Remove ifdef for NeXT
 *
 * Revision 3.96  1994/07/21  17:23:55  lindner
 * FTP gopher+ gw, and file separator code
 *
 * Revision 3.95  1994/07/19  20:25:42  lindner
 * Sizes for gopher directories from .cache files
 *
 * Revision 3.94  1994/06/29  05:42:43  lindner
 * Add authentication capabilities
 *
 * Revision 3.93  1994/06/03  06:25:37  lindner
 * Another fix for Hgopher method of alt views
 *
 * Revision 3.92  1994/05/18  04:00:40  lindner
 * Add port# to waiting for connection message
 *
 * Revision 3.91  1994/05/14  04:19:33  lindner
 * Fix for text files with really long lines
 *
 * Revision 3.90  1994/05/02  07:41:12  lindner
 * Mods to use setlocale()
 *
 * Revision 3.89  1994/04/25  20:49:07  lindner
 * Fix for debug code
 *
 * Revision 3.88  1994/04/21  21:24:21  lindner
 * FIOclose fix
 *
 * Revision 3.87  1994/04/21  21:15:12  lindner
 * Add looking up address item
 *
 * Revision 3.86  1994/04/19  14:30:17  lindner
 * Fix for gopher+ shell scripts
 *
 * Revision 3.85  1994/04/13  04:17:57  lindner
 * Fix for abnormal exits
 *
 * Revision 3.84  1994/04/08  21:09:21  lindner
 * fix for shutdown calls and compiler goofiness
 *
 * Revision 3.83  1994/04/07  17:28:39  lindner
 * putenv stuff
 *
 * Revision 3.82  1994/04/01  05:03:35  lindner
 * Move putenv() later
 *
 * Revision 3.81  1994/03/31  22:47:36  lindner
 * Fix for date and time for multiple view items and Type 1 ask forms
 *
 * Revision 3.80  1994/03/31  21:25:37  lindner
 * Shutdown inetd sockets, simplify some code
 *
 * Revision 3.79  1994/03/30  21:36:35  lindner
 * Fix for binary ask data from Don Gilbert
 *
 * Revision 3.78  1994/03/17  21:18:08  lindner
 * Massive reworking of access limits
 *
 * Revision 3.77  1994/03/17  04:30:11  lindner
 * VMS fixes gopherd.h
 *
 * Revision 3.76  1994/03/15  17:59:05  lindner
 * Some code moved to Sockets.c, some reorg. Fixes for SCO compiler
 *
 * Revision 3.75  1994/03/08  17:11:16  lindner
 * One more fix for listdir
 *
 * Revision 3.74  1994/03/08  16:45:30  lindner
 * Fix things broken by fixing recursive dirs
 *
 * Revision 3.73  1994/03/08  15:55:41  lindner
 * gcc -Wall fixes
 *
 * Revision 3.72  1994/03/08  15:02:04  lindner
 * Fix for unset variable i in main() Causes crash on NextStep486
 *
 * Revision 3.71  1994/03/08  06:15:57  lindner
 * Fix for recursive directory traversal
 *
 * Revision 3.70  1994/03/04  23:26:08  lindner
 * Fix for changes in strstring.h
 *
 * Revision 3.69  1994/02/20  21:41:31  lindner
 * Allow use of -u if the uid specified matches the current uid
 *
 * Revision 3.68  1994/02/20  16:53:17  lindner
 * Add buffered text writes to server, shutdown inbound part of socket after reading
 *
 * Revision 3.67  1994/01/25  05:25:25  lindner
 * Many HTML and URL related fixes
 *
 * Revision 3.66  1994/01/06  05:42:41  lindner
 * Fix for bad clients that don't bind right
 *
 * Revision 3.65  1993/12/30  04:15:20  lindner
 * removed extra fclose in Side_File, fixes certain Linux distributions
 *
 * Revision 3.64  1993/12/27  16:34:50  lindner
 * Now Add dots to the end of the DNS name
 *
 * Revision 3.63  1993/12/09  20:47:47  lindner
 * Remove error destroying data, add boot up message with pid to log file
 *
 * Revision 3.62  1993/11/03  03:35:25  lindner
 * Add headlines to the top level HTML page
 *
 * Revision 3.61  1993/11/02  06:08:11  lindner
 * Strip extensions off of files with multiple views
 *
 * Revision 3.60  1993/11/02  05:58:12  lindner
 * WAIS index speedups, mondo HTML mods
 *
 * Revision 3.59  1993/10/28  22:08:17  lindner
 * memory leak fixes, fix for -u problem
 *
 * Revision 3.58  1993/10/20  03:22:59  lindner
 * none
 *
 * Revision 3.57  1993/10/20  03:19:31  lindner
 * Better error messages..
 *
 * Revision 3.56  1993/10/11  04:40:52  lindner
 * Changes to allow logging via daemon.info syslogd facility
 *
 * Revision 3.55  1993/10/04  06:47:16  lindner
 * Eliminate bogus warning messages
 * Remove gindexd crap
 * Mods to allow for new command structure for ASKfile
 * Auxconf support..
 *
 * Revision 3.54  1993/09/30  16:57:02  lindner
 * Fix for WAIS and $ requests
 *
 * Revision 3.53  1993/09/22  04:27:35  lindner
 * Add ignore options to sidefile processing
 *
 * Revision 3.52  1993/09/22  00:29:40  lindner
 * Speedups for gopher0 and big directories
 *
 * Revision 3.51  1993/09/21  07:00:03  lindner
 * Fix for sites that don't do _POSIX_SAVED_IDS
 *
 * Revision 3.50  1993/09/21  04:16:45  lindner
 * Move cache settings into gopherd.conf
 *
 * Revision 3.49  1993/09/21  02:35:07  lindner
 * Server now adds extensions in a case insensitive manner
 *
 * Revision 3.48  1993/09/20  16:56:12  lindner
 * Mods for moved code
 *
 * Revision 3.47  1993/09/18  03:27:19  lindner
 * slight mod for ftp gateway
 *
 * Revision 3.46  1993/09/11  05:06:22  lindner
 * Mod for ignoring files, and more efficient binary transfers
 *
 * Revision 3.45  1993/09/11  04:40:41  lindner
 * Don't fork for localhost mindex databases
 *
 * Revision 3.44  1993/08/24  20:58:58  lindner
 * fixed typo in add_title code
 *
 * Revision 3.43  1993/08/23  20:10:39  lindner
 * Additional colon, gopher+ error fix
 *
 * Revision 3.42  1993/08/23  19:38:23  lindner
 * Yet another fix for mindexd troubles..
 *
 * Revision 3.41  1993/08/23  18:46:11  lindner
 * Crude addition of a veronica top-level block
 *
 * Revision 3.40  1993/08/23  02:34:30  lindner
 * Optional date and time
 *
 * Revision 3.39  1993/08/20  18:03:00  lindner
 * Mods to allow gopherd.conf files control ftp gateway access
 *
 * Revision 3.38  1993/08/19  20:52:25  lindner
 * Mitra comments
 *
 * Revision 3.37  1993/08/19  20:25:50  lindner
 * Mitra's Debug patch
 *
 * Revision 3.36  1993/08/12  06:27:35  lindner
 * Get rid of errant message when using inetd
 *
 * Revision 3.35  1993/08/11  22:47:44  lindner
 * Fix for gopher0 clients on gopher+ server
 *
 * Revision 3.34  1993/08/11  21:34:05  lindner
 * Remove extensions from titles for files with multiple views.
 * Move CMDfromNet() to *after* the chroot() and setuid()
 *
 * Revision 3.33  1993/08/11  14:39:24  lindner
 * Fix for send_binary bug
 *
 * Revision 3.32  1993/08/11  02:27:40  lindner
 * Fix for wais gateway and Unix client
 *
 * Revision 3.31  1993/08/10  20:26:57  lindner
 * Fixed bogus reading of .cache+ files
 *
 * Revision 3.30  1993/08/06  14:42:49  lindner
 * fix for mindex
 *
 * Revision 3.29  1993/08/06  14:30:40  lindner
 * Fixes for better security logging
 *
 * Revision 3.28  1993/08/05  20:47:16  lindner
 * Log execution of programs
 *
 * Revision 3.27  1993/08/02  17:59:26  lindner
 * Fix for Debug syntax error when using DL
 *
 * Revision 3.26  1993/07/29  20:49:25  lindner
 * removed dead vars, test for non-existant binary files, Dump_Core thing..
 *
 * Revision 3.25  1993/07/27  20:16:03  lindner
 * Fixed bug logging directory transactions
 *
 * Revision 3.24  1993/07/27  06:13:40  lindner
 * Bug fixes for redeffed .cache stuff
 *
 * Revision 3.23  1993/07/27  05:27:46  lindner
 * Mondo Debug overhaul from Mitra
 *
 * Revision 3.22  1993/07/27  01:52:59  lindner
 * More comments, don't let server die if fork error..
 *
 * Revision 3.21  1993/07/26  20:33:02  lindner
 * Fix from guyton, cachefd can be zero
 *
 * Revision 3.20  1993/07/26  17:24:02  lindner
 * Faster .cache output
 *
 * Revision 3.19  1993/07/26  15:32:26  lindner
 * mods for application/gopher-menu and faster .cache sending
 *
 * Revision 3.18  1993/07/23  03:24:22  lindner
 * fix for ppoen and NeXTs with overwritten envp
 * mucho mods for looking up filenames,
 * enhanced item info for waissrc: and others.
 * update for Text/plain & other MIME types.
 *
 * Revision 3.17  1993/07/20  23:53:51  lindner
 * LOGGOpher changes, Argv mucking, and Version Number enhancements
 *
 * Revision 3.16  1993/07/13  03:57:29  lindner
 * Fix for gopherls, improved mailfile handling, iteminfo on 3b2
 *
 * Revision 3.15  1993/07/10  04:22:36  lindner
 * fix for gopherls
 *
 * Revision 3.14  1993/07/08  17:54:40  lindner
 * fix for .src files that already end with .src
 *
 * Revision 3.13  1993/07/07  19:33:00  lindner
 * Sockets.c update, fix for compressed binarys, exec: fixes
 *
 * Revision 3.12  1993/06/11  16:59:57  lindner
 * gzip support, less lookups, etc.
 *
 * Revision 3.11  1993/04/15  22:20:08  lindner
 * CAPFILES mods
 *
 * Revision 3.10  1993/04/15  04:48:07  lindner
 * Debug code from Mitra
 *
 * Revision 3.9  1993/04/10  06:06:11  lindner
 * Admit1 Fixes for combined public/authed server
 *
 * Revision 3.8  1993/04/07  05:54:39  lindner
 * Fixed dreaded .mindex addition problem
 *
 * Revision 3.7  1993/03/26  19:47:50  lindner
 * Hacks to support wais gateway and gplus indexing
 *
 * Revision 3.6  1993/03/25  21:36:30  lindner
 * Mods for directory/recursive etal
 *
 * Revision 3.5  1993/03/24  22:08:59  lindner
 * Removed unused variable
 *
 * Revision 3.4  1993/03/24  20:23:17  lindner
 * Lots of bug fixes, compressed file support, linger fixes, etc.
 *
 * Revision 3.3  1993/03/01  02:22:40  lindner
 * Mucho additions for admit1 stuff..
 *
 * Revision 3.2  1993/02/19  21:21:11  lindner
 * Fixed problems with signals, problems with gethostbyaddr() and
 * inconsisent behavior that depended on the order of files in a directory.
 *
 * Revision 3.1.1.1  1993/02/11  18:02:55  lindner
 * Gopher+1.2beta release
 *
 *********************************************************************/


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

#include <stdio.h>
#include "gopherd.h"
#include "command.h"
#include "patchlevel.h"
#include "Malloc.h"
#include "Debug.h"
#include "fileio.h"

#ifdef _AUX_SOURCE
#  include <compat.h>
#endif

#undef stat /** Stupid openers thing..**/

GopherDirObj *GDfromSelstr();
GopherDirObj *GDfromUFS();
void         item_info();

/******* Global variables *********/

static	char*	Gdefusername = NULL;
static  uid_t   Guid;
static  gid_t   Ggid;
/**********************************/

extern char *getdesc();
extern double maxload;
int Process_Side();
char *GDESencrypt();

#include "STAarray.h"
#include "STRstring.h"
#include "Sockets.h"

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

#include <setjmp.h>
jmp_buf env;

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

void
gopherd_usage(progname) 
  char *progname;
{
     fprintf(stderr, "Usage: %s [-mCDIc] [-u username] [-s securityfile] [-l logfile] [ -L loadavg ] <datadirectory> <port>\n", progname);
     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, "   -o  override the default options file '%s'\n", CONF_FILE);
     fprintf(stderr, "   -l  specifies the name of a logfile\n");
     fprintf(stderr, "   -L  specifies maximum load to run at\n");
}

void
gopherd_exit(val)
  int val;
{
     exit(val);
}

/*
 * This is for when we dump core..  Make an attempt to clean up..
 */

SIGRETTYPE 
sigabnormalexit()
{
     gopherd_exit(-1);
}

void
main(argc, argv, envp)
  int 	argc;
  char 	*argv[];
  char  *envp[];
{
     int                childpid;
     int                sockfd, newsockfd;
     int                clilen;
     struct sockaddr_in cli_addr;
     boolean            OptionsRead = FALSE;
     int                i=0;
     char               tmpstr[256];
 
     /*** for getopt processing ***/
     int c;
     extern char *optarg;
     extern int optind;
     int errflag =0;


     Gtxtlocale(LC_ALL, "");

     Argv = argv;

#if !(defined(NeXT) || defined(_AIX))
     /* NeXTs don't like their envp to be overwritten... */

     for (i=0; envp[i] != NULL; i++)
	  ;
#endif

     if (i > 0)
	  LastArgv = envp[i - 1] + strlen(envp[i - 1]);
     else
	  LastArgv = argv[argc - 1] + strlen(argv[argc - 1]);

     pname = argv[0];

     err_init();	/* openlog() early - before we chroot() of course */

     RunServer = TRUE;  /** Run the server by default **/

     Config = GDCnew();  /** Set up the general configuration **/
 
     while ((c = getopt(argc, argv, "CDIcL:l:o:u:")) != -1)
	  switch (c) {
	  case 'D':
	       DEBUG = TRUE;
	       break;

	  case 'I':
	       RunFromInetd = TRUE;
	       break;

	  case 'C':
	       GDCsetCaching(Config, FALSE);
	       break;

	  case 'c':
	       dochroot = FALSE;
	       break;

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

	  case 'l':  /** logfile name **/
	       if (*optarg == '/' || strcasecmp(optarg, "syslog") == 0)
		    GDCsetLogfile(Config, optarg);
	       else {
		    getwd(tmpstr);
		    strcat(tmpstr, "/");
		    strcat(tmpstr, optarg);
		    
		    GDCsetLogfile(Config, tmpstr);
	       }
	       break;
	       
	  case 'o': /** option file **/
	       if (*optarg == '/')
		    GDCfromFile(Config, optarg);
	       else {
		    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 {
			 if (!(pw->pw_uid == getuid() || getuid() == 0))
			      printf("Need to be root to use -u\n"), gopherd_exit(-1);
			 
			 Gdefusername = strdup(optarg);
			 
			 Guid = pw->pw_uid;
			 Ggid = pw->pw_gid;

		    }
	       }
	       break;

	  case '?':
	  case 'h':
	       errflag++;
	       break;
	  }

     if (errflag) {
	  gopherd_usage(argv[0]);
	  gopherd_exit(-1);
     }

     if (optind < argc) {
	  Data_Dir = strdup(argv[optind]);
	  optind++;
	  Debug("main: Setting data to Data Directory is %s\n",Data_Dir);
     }
     if (Data_Dir == NULL)
	  Data_Dir = strdup(DATA_DIRECTORY);

     Debug("main: Data Directory is %s\n",Data_Dir);

     if (optind < argc) {
	  GopherPort = atoi(argv[optind]);
	  optind++;
	  Debug("main: Setting port to %d\n",GopherPort);
     }
     Debug("main: Port is %d\n",GopherPort);

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

     /** Check to make sure the options specified are workable... ***/
     GDCintegrityCheck(Config);

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

     if (!RunFromInetd) {
	  char *cp;
	  printf("Internet Gopher Server %s.%s patch %d\n", GOPHER_MAJOR_VERSION, GOPHER_MINOR_VERSION, PATCHLEVEL);
	  printf("Copyright 1991,92,93 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);

	  /*** Check for weird inconsistencies, security warnings etal ***/
	  if (getuid() == 0 && Gdefusername == NULL)
	       printf("Warning! You should run the server with the -u option!\n");
	  else if (Gdefusername != NULL)
	       printf("Running as user '%s'\n", Gdefusername);
	  
	  if (dochroot == FALSE)
	       printf("Not using chroot() - be careful\n");

	  cp = GDCgetLogfile(Config);

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


     if (uchdir(Data_Dir)) {
	  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);

#ifdef _AUX_SOURCE
     (void) set42sig();
#endif

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

     /** We ignore SIGUSR2, so the PID routines can "ping" us **/
     (void) signal(SIGUSR2, SIG_IGN);
     (void) signal(SIGINT, sigabnormalexit);
     (void) signal(SIGSEGV, sigabnormalexit);

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

     /** Ask the system what host we're running on **/
     Zehostname = SOCKgetDNSname(DOMAIN_NAME, GDCgetHostname(Config));
     Debug("I think your hostname is %s\n", Zehostname);

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

	  /*** Do the stuff for inetd ***/
	  while(do_command(fileno(stdout))!=0);	/* process the request */

	  shutdown(fileno(stdout), 2);
	  shutdown(fileno(stdin), 2);
	  shutdown(fileno(stderr), 2);

	  fclose(stdout);
	  fclose(stdin);
	  fclose(stderr);

	  gopherd_exit(0);
     } else {
	  LOGGopher(-1, "Starting gopher server (pid %d)", getpid());
     }

     /** Set our cmd string **/

     ServerStarted = time(NULL);
     ServerSetArgv("waiting for connection @ %d", GopherPort);

     /** Open a TCP socket (an internet stream socket **/
     sockfd = SOCKbind_to_port(GopherPort);

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

	       if (newsockfd >= 0)
		    break;
	       else if (errno != EINTR)  /** Restart accept if we hit a
					     SIGCHLD signal..**/
		    LOGGopher(newsockfd, "Client went away");
	  }
		    

	  SOCKlinger(sockfd, FALSE);
	  SOCKlinger(newsockfd, FALSE);

	  ActiveSessions ++;
	  if ((GDCgetMaxconns(Config) > 0 ) && ActiveSessions > GDCgetMaxconns(Config)) {
	       writestring(newsockfd, "0Too Many Users at this time, try again later\txx\txx\t70\r\n");
	       ActiveSessions--;
	       close(newsockfd);

	  } else {
	  
	       if ( (childpid = fork()) < 0) {
		    /** Problems forking..  **/
		    writestring(newsockfd, "3Problems forking!\tmoo\terror.host\t0\r\n.\r\n");
	       }
	       
	       else if (childpid == 0) {	/* Child process */
		    close(sockfd);		/* close original socket */
		    
		    (void)do_command(newsockfd);/* process the request */
		    gopherd_exit(0);
	       }
	       /** Parent **/
	       /** clean up any zombie children **/
	       sig_child();
	       
	       close(newsockfd); 		/* parent process */
	       Connections++;
	  }
     }
}





/*
 * Given a specific view, add the filename extension that
 * we stripped off for the multiple views stuff
 */

char *
AddExtension(cmd, view)
  CMDobj *cmd;
  char *view;
{
     char *filename = CMDgetFile(cmd);
     char *newselstr;
     int  flen=0, newlen=0;
     char *newfile;

     if (filename == NULL)
	  return(CMDgetSelstr(cmd));

     newfile = EXAfindFile(Config->Extensions, filename, view);
     if (newfile == NULL)
		 return(CMDgetSelstr(cmd));
     
     flen = strlen(filename);
     newlen = strlen(newfile);

     if (newlen > flen) {
	  /*** Add the found extensions... ***/
	  newselstr = (char *)malloc(MAXPATHLEN);
	  
	  strcpy(newselstr, CMDgetSelstr(cmd));
	  strcat(newselstr, newfile+flen);
	  Debug("New long file is %s", newselstr);
	  return(newselstr);
     }
     return(CMDgetSelstr(cmd));
}


int EXECflag;
  

void
SetScriptEnvironment(cmd, view)
  CMDobj *cmd;
  char *view;
{
     char tmpstr[256];

     SetEnvironmentVariable("GPLUS_CMD", CMDgetCommand(cmd));
     SetEnvironmentVariable("CONTENT_TYPE", view);
     SetEnvironmentVariable("GUSER", CMDgetUser(cmd));
     SetEnvironmentVariable("GTICKET", CMDgetTicket(cmd));

     sprintf(tmpstr, "UofMNgopherd/%s.%spl%d", GOPHER_MAJOR_VERSION,
	     GOPHER_MINOR_VERSION, PATCHLEVEL);

     SetEnvironmentVariable("SERVER_SOFTWARE", tmpstr);

     SetEnvironmentVariable("SERVER_NAME", Zehostname);

     sprintf(tmpstr, "%d", GopherPort);
     SetEnvironmentVariable("SERVER_PORT", tmpstr);
     SetEnvironmentVariable("SERVER_PROTOCOL", "gopher/1.0");
     SetEnvironmentVariable("GATEWAY_INTERFACE", "CGI/1.0");
     
     SetEnvironmentVariable("REQUEST_METHOD", "GET");
     SetEnvironmentVariable("PATH_INFO", CMDgetSelstr(cmd));
     SetEnvironmentVariable("PATH_TRANSLATED", CMDgetSelstr(cmd));

     SetEnvironmentVariable("SCRIPT_NAME", CMDgetFile(cmd));
     SetEnvironmentVariable("QUERY_STRING", CMDgetSearch(cmd));
     
     SetEnvironmentVariable("REMOTE_HOST", CurrentPeerName);
     SetEnvironmentVariable("REMOTE_ADDR", CurrentPeerIP);
     
     SetEnvironmentVariable("REMOTE_USER", CMDgetUser(cmd));
     
}


int
do_command(sockfd)
  int sockfd;
{
     char    logline[MAXLINE];
     char    *view     = NULL;
     char    *Selstr   = NULL;
     CMDobj  *cmd;
     char    *filter   = NULL;
     char    *cp;
     int     result;

     cmd = CMDnew();

     /*** Reopen the log file ***/
     cp = GDCgetLogfile(Config);

     if (cp != NULL && *cp != '\0') {
	  if (strcasecmp(GDCgetLogfile(Config), "syslog")==0) {
	       LOGFileDesc = -2;  /** log file is syslog **/
	  } else if (LOGFileDesc < 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));
	       gopherd_exit(-1);
	  }
     }

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


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

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

     ServerSetArgv("Looking up address");

     /*** Find out who's knockin' ***/
     SOCKnetnames(sockfd, CurrentPeerName, CurrentPeerIP);
     
     ServerSetArgv("input from %s", CurrentPeerName);

     CMDfromNet(cmd, sockfd);

     if (CMDgetUser(cmd) != NULL)
	  CurrentUser = strdup(CMDgetUser(cmd));

     GDCevalDir(Config, CMDgetFile(cmd));
     IsGplus = CMDisGplus(cmd);  /* need for error output.. */


#ifndef NO_AUTHENTICATION

     if (CMDgetTicket(cmd) != NULL) {
	  /** The user has already authenticated, extract from ticket **/
	  char   *tix = CMDgetTicket(cmd);
	  char   *user = CMDgetUser(cmd);
	  char   *cleartext;

	  GDCsetCaching(Config, FALSE);

	  /** decode ticket here... ***/
	  cleartext = (char*) GDESdecrypt(CMDgetUser(cmd), CurrentPeerIP,
				  GDCgetPW(Config), tix);
	  
	  if (strcmp(cleartext, CMDgetUser(cmd)) == 0) {
	       /*CMDremoveAsk(cmd);*/
	       ; /** Do the trans... **/
	       Setuid_username(user);

	       Gticket = (char*) malloc(sizeof(char*) *
					(strlen(CMDgetUser(cmd)) +
					 strlen(tix) + 4));
	       sprintf(Gticket, "*%s %s ", CMDgetUser(cmd), tix);

	  } else {
	       GplusError(sockfd, 1, "Your ticket is invalid", NULL);
	  }

     } else if (strncmp(CMDgetSelstr(cmd), "validate ", 9) == 0) {
	  /** Take the information from the ASK block and validate the user,
	      then, generate tickets **/
	  char *selstr, *auth;
	  
	  GDCsetCaching(Config, FALSE);

	  /** Only look at the ask block if it's there... **/
	  if (*CMDgetCommand(cmd) != '!') {
	       
	       selstr = CMDgetSelstr(cmd) + 9;

	       /** Doctor up the item so it looks real.. **/
	       CMDsetSelstr(cmd, selstr);
	       
	       auth = GDCauthType(Config, CMDgetFile(cmd));
	       
	       switch (GDCvalidate(Config, auth, CMDgetAskline(cmd, 0),
				   CMDgetAskline(cmd, 1), CurrentPeerName,
				   CurrentPeerIP)) {
		    char *ask1, *ask0, *crypted;

	       case AUTHRES_OK:

		    Gticket = (char*) malloc(sizeof(char*) *
					     (strlen(CMDgetAskline(cmd,0)) +
					      strlen(CMDgetAskline(cmd,1))+4));
		    ask0 = CMDgetAskline(cmd, 0);
		    ask1 = CMDgetAskline(cmd, 1);
		    crypted = GDESencrypt(ask0, CurrentPeerIP, 
					  GDCgetPW(Config), ask0);

		    sprintf(Gticket, "*%s %s ", ask0, crypted);


		    CMDremoveAsk(cmd);
		    ; /** Do the trans... **/
		    break;
	       case AUTHRES_BADPW:
		    if (strlen(CMDgetAskline(cmd, 1)) == 0) 
			 GplusError(sockfd, 1, "Please enter a valid password", NULL);
		    else
			 GplusError(sockfd, 1, "Your password is incorrect, try again", NULL);
		    break;
	       case AUTHRES_SYSERR:
		    GplusError(sockfd, 1, "Authentication system failue, please contact administrator", NULL);
		    break;
		    
	       default:
		    GplusError(sockfd, 1, "Your username is incorrect, try again", NULL);
		    break;
	       }
	  }
	  
     } else {
	  /** Check to see if this item needs authentication, and send
	      a redirect error message **/
	  char *auth = GDCauthType(Config, CMDgetFile(cmd));
	  if (auth != NULL) {
	       /** Need to redirect **/
	       Abortoutput(sockfd, "Sorry, no access to this item");
	  }
	  
     }
#endif

     /** At this point there won't be any more data coming in, so shutdown
         the incoming data for the socket
      **/
     shutdown(sockfd, 0);

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

     if (getuid() == 0) {
	  setgid(Ggid);
	  setuid(Guid);
     }


     if (CMDisAskitem(cmd)) {
	  FILE *retrfile;

	  /** Write the stuff out to a file, here, as gopherd user
	      so we can remove file later on.. **/
          ASKfile = tempnam(NULL, "gdata");
          Debug("Ask data is in %s\n", ASKfile);
          retrfile = ufopen(ASKfile, "w",0777);
	  if (retrfile != NULL) {
	       int i;

	       for (i=0; i < CMDnumAsklines(cmd); i++) {
		    fputs(CMDgetAskline(cmd, i), retrfile);
                    putc('\n', retrfile);
               }
	       fclose(retrfile);
          } else {
	       Abortoutput(sockfd, "Cannot write ask data");
	  }
     }

     /** Extract the view if it exists **/
     if (CMDgetCommand(cmd) != NULL) {
	  char *command = CMDgetCommand(cmd);
	  int  cmdlen = strlen(command);

	  if ((cmdlen > 1) && (*command == '+'))
	       view = command+1;
	  else if (*command == '!') {
	       item_info(cmd, sockfd);
	       if (*(command+1) != '\0')
		    filter = command + 1;
	       return(0);
	  }
	  else if (*command == '$') {
	       if (*(command+1) != '\0')
		    filter = command + 1;
	       view = "application/gopher+-menu";
	       CMDsetView(cmd, view);
	       *command = '+';
	  }
	  else if (*command == '+') {
	       ;
	  }
	  else
	       Abortoutput(sockfd, "Malformed command"); /*** Error ***/
     }
     
     if (strncmp(CMDgetSelstr(cmd), "waisdocid:",10)==0)
	  view = "Text/plain";

     /*** Root level null selector string.. ***/
     if (!view && strlen(CMDgetSelstr(cmd)) == 0) 
	  view = "application/gopher-menu";


     if (!view && *CMDgetSelstr(cmd) == 'h') {
 	  /** It might be a directory..., or an HTML file on disk **/
 	  struct stat    statbuf;
 
 	  if (!rstat(CMDgetFile(cmd), &statbuf)) {
 	       if (S_ISDIR(statbuf.st_mode)) {
 		    /*** It's a directory capability ***/
  		    *CMDgetSelstr(cmd) = '1';
 	       }
 	  }
  	  view = "text/html";
     }

     /*** Try to speed things up for gopher0 requests, avoid reading
          big directories.. ***/

     if (!view && CMDisGplus(cmd) == FALSE) {
	  struct stat  statbuf;
	  char         *cp = CMDgetSelstr(cmd);
	  ;
	  if (*cp == '0' ||
	      *cp == 'R') {
	       if (!rstat(CMDgetFile(cmd), &statbuf))
		    view = "text/plain";
	  }
	  else if (*cp == '1') {
	       if (!rstat(CMDgetFile(cmd), &statbuf))
		    view = "application/gopher-menu";
	  }
     }


     /*** Try to find a view if not supplied ***/
     if (view == NULL) {
	  GopherDirObj *gd;
	  int num,i;

	  /** Get gopher directory containing item in question **/
	  gd = GDfromSelstr(cmd, sockfd);

	  if (gd != NULL) {
	       num = GDSearch(gd, CMDgetSelstr(cmd));
	       if (num >=0) {
		    GopherObj *gs;
	       
		    gs= GDgetEntry(gd, num);

		    if (GSgplusInited(gs) == FALSE) {
			 view = "";
		    }

		    /**  If only one view, take it **/
		    else if (GSgetNumViews(gs) == 1)
			 view = VIgetViewnLang(GSgetView(gs, 0),(char*)malloc(128));
		    else {
			 /*** Hmmm, let's choose one.. ***/
			 for (i=0; i<GSgetNumViews(gs); i++) {
			      char *tmpview;

			      tmpview = VIgetType(GSgetView(gs,i));
			      if (GSgetType(gs) == '0') {
				   if (strcasecmp(tmpview, "Text/plain")==0) {
					view = VIgetViewnLang(GSgetView(gs, i),(char*)malloc(128));
					break;
				   }
			      }
			      if (GSgetType(gs) == 'I') {
				   if (strcmp(tmpview, "image/gif")==0) {
					view = VIgetViewnLang(GSgetView(gs, i),(char*)malloc(128));
					break;
				   }
			      }

			 }
			 if (view == NULL)
			      /** Give up, take the first view... **/
			      view = VIgetViewnLang(GSgetView(gs,0), (char*)malloc(128));
		    }
		    /** We should have a view by now **/
	       }
	       GDdestroy(gd);
	  } else {
	       ;/* Can't get a Gopher directory listing... */;
	  }
     }

     CMDsetView(cmd, view);

     /*
      * Set the environment variables for the scripts
      * shell script writers everywhere..
      */

     SetScriptEnvironment(cmd, view);


     /** Decide whether to add extensions of not .. **/
     if (view != NULL )
	  Selstr = AddExtension(cmd, view);
     else
	  Selstr = CMDgetSelstr(cmd);



     ServerSetArgv("%s to %s", Selstr, CurrentPeerName);

     EXECflag = 0;

     /*  Ask items are shell scripts, not directories.
	 Selector strings with '1/' should never be shell scripts */
	
     if (CMDisAskitem(cmd) && *Selstr == '1')
	  *Selstr = '0';

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


     Debug("Attempting to get Selstr %s\n", Selstr);

     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, "/", CMDisGplus(cmd), view, filter);
	  LOGGopher(sockfd, "Root Connection");
	  break;

     case 'h':
     case '0':
     case '9':
     case 's':
     case 'I':
     case 'g':
	  /*** It's some kind of file ***/

	  /*** Is it binary??  ***/
	  
	  if (view == NULL)  {
	       Debugmsg("View is null\n");
	       if (*Selstr != '0')
		    send_binary(sockfd, Selstr+1, CMDisGplus(cmd));
	       else
		    printfile(sockfd, Selstr+1, 0, -1, CMDisGplus(cmd));
	  } else {
	       Debug("Testing view %s\n",view);
	       if (GSisText(NULL, view))
		    printfile(sockfd, Selstr+1, 0, -1, CMDisGplus(cmd));
	       else
		    send_binary(sockfd, Selstr+1, CMDisGplus(cmd));
	  }
	  /*** Log it ***/
	  LOGGopher(sockfd, "retrieved file %s", Selstr+1 );
	  break;


     case '1':
	  /*** It's a directory capability ***/
	  listdir(sockfd, Selstr+1, CMDisGplus(cmd), view, filter);

	  /** Log it **/
	  LOGGopher(sockfd, "retrieved directory %s", Selstr+1);
	  break;

     case '7':
	  /*** It's an index capability ***/
	  result = GDCCanSearch(Config, CurrentPeerName, CurrentPeerIP,
				ActiveSessions);

	  if (result == SITE_NOACCESS) {
	       Abortoutput(sockfd, GDCgetBummerMsg(Config));
	       LOGGopher(sockfd, "Denied access for %s", Selstr+1);
	       break;
	  } else if (result == SITE_TOOBUSY) {
	       Abortoutput(sockfd, "Sorry, too busy now...");
	       break;
	  }

	  Do_IndexTrans(sockfd, Selstr+1, cmd, TRUE);

	  break;


     case 'm':
	  if (strncmp(Selstr, "mindex:", 7)==0) {
	       /*** First test for multiple indexes ***/
	       result = GDCCanSearch(Config, CurrentPeerName, CurrentPeerIP,
				ActiveSessions);

	       if (result == SITE_NOACCESS) {
		    Abortoutput(sockfd, GDCgetBummerMsg(Config));
		    LOGGopher(sockfd, "Denied access for %s", Selstr+1);
		    break;
	       } else if (result == SITE_TOOBUSY) {
		    Abortoutput(sockfd, "Sorry, too busy now...");
		    break;
	       }

	       do_mindexd(sockfd, Selstr+7, CMDgetSearch(cmd), CMDisGplus(cmd),
			  view);
	       break;
	  }


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

	  result = GDCCanBrowse(Config, CurrentPeerName, CurrentPeerIP,
				ActiveSessions);

	  if (result == SITE_NOACCESS) {
	       Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	       LOGGopher(sockfd,"Denied access for %s", Selstr+1);
	       break;
	  } else if (result == SITE_TOOBUSY) {
	       Abortoutput(sockfd, "Sorry, too busy right now..");
	       break;
	  }

	  process_mailfile(sockfd, Selstr + 1);
	  writestring(sockfd, ".\r\n");

	  /** Log it **/
	  LOGGopher(sockfd, "retrieved maildir %s", Selstr+1 );

	  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");
	       gopherd_exit(-1);
	  }

	  *cp = '\0';
	  endbyte = atoi(oldcp);
	  oldcp = cp + 1;

	  Debug("Start: %d, ", startbyte);
	  Debug("End: %d, ", endbyte);
	  Debug("File: %s\n", oldcp);

	  printfile(sockfd, oldcp, startbyte, endbyte, CMDisGplus(cmd));

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

     case 'f':
	  result = GDCCanFTP(Config,CurrentPeerName,CurrentPeerIP,ActiveSessions);
	  if (result == SITE_NOACCESS) {
	       Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	       LOGGopher(sockfd, "Denied access for %s", Selstr);
	       break;
	  } else if (result == SITE_TOOBUSY) {
	       Abortoutput(sockfd, "Sorry, too busy now...");
	       break;
	  }

	  if (strncmp(Selstr, "ftp:",4)==0){

	       LOGGopher(sockfd, "retrieved %s", Selstr);

	       GopherFTPgw(sockfd, Selstr+4, cmd);
	       break;
	  }
	  break;


     case 'e':
	  result = GDCCanBrowse(Config, CurrentPeerName, CurrentPeerIP,
				ActiveSessions);

	  if (result == SITE_NOACCESS) {
	       Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	       LOGGopher(sockfd,"Denied access for %s", Selstr+1);
	       break;
	  } else if (result == SITE_TOOBUSY) {
	       Abortoutput(sockfd, "Sorry, too busy right now..");
	       break;
	  }

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

	       if (*(Selstr+4) == ':' && *(Selstr+5) == ':')
		    args = NULL;
	       else
		    args = Selstr+5;

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

	       printfile(sockfd, command, 0, -1, CMDisGplus(cmd));
	       LOGGopher(sockfd, "Executed %s %s", command,  (args == NULL)
			 ? " " : args);
	  }
	  break;

     case 'w':
     {
	  if (strncmp(Selstr, "waissrc:", 8) == 0) {
	       char waisfname[512];  /*** Ick this is gross ***/

	       result = GDCCanSearch(Config, CurrentPeerName, CurrentPeerIP,
				ActiveSessions);

	       if (result == SITE_NOACCESS) {
		    Abortoutput(sockfd, GDCgetBummerMsg(Config));
		    LOGGopher(sockfd, "Denied access for %s", Selstr+1);
		    break;
	       } else if (result == SITE_TOOBUSY) {
		    Abortoutput(sockfd, "Sorry, too busy now...");
		    break;
	       }

	       strcpy(waisfname, Selstr+8);
	       if ((int)strlen(waisfname) <= 4 ||
		   strncmp(&waisfname[strlen(waisfname)-4],".src",4) )
		    strcat(waisfname, ".src");
	       SearchRemoteWAIS(sockfd, waisfname, cmd, view);
	       break;
	  }
	  else if (strncmp(Selstr, "waisdocid:", 10) == 0) {

	       result = GDCCanSearch(Config, CurrentPeerName, CurrentPeerIP,
				ActiveSessions);

	       if (result == SITE_NOACCESS) {
		    Abortoutput(sockfd, GDCgetBummerMsg(Config));
		    LOGGopher(sockfd, "Denied access for %s", Selstr+1);
		    break;
	       } else if (result == SITE_TOOBUSY) {
		    Abortoutput(sockfd, "Sorry, too busy now...");
		    break;
	       }

	       Fetchdocid(sockfd, cmd);
	       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);
	       Abortoutput(sockfd, logline);
	       break;

	  case 0:
	       /* it's a file */
	       printfile(sockfd, Selstr, 0, -1, CMDisGplus(cmd));
	       
	       /* Log it... */
	       LOGGopher(sockfd, "retrieved file %s", Selstr);

	       break;

	  case 1:
	       /* it's a directory */
	       listdir(sockfd, Selstr, CMDisGplus(cmd), view, filter);

	       /* Log it */
	       LOGGopher(sockfd, "retrieved directory %s", Selstr);

	       break;
	  }
     }

     /** Free data ***/
     CMDdestroy(cmd);

     return(0);
}


/*
 * This function tries to find out what type of file a pathname is.
 */

void
Getfiletypes(newpath, filename, gs)
  char *newpath;
  char *filename;
  GopherObj *gs;
{
     int Zefilefd;
     char *cp;
     char Zebuf[100];
     char Selstr[512];

     
     switch (isadir(filename)) {
     case -1:
	  GSsetType(gs,'3');
	  return;

     case 1:
	  GSsetType(gs,A_DIRECTORY);
	  *Selstr = '1';
	  strcpy(Selstr +1, newpath);
	  GSsetPath(gs, Selstr);
	  GSsetTTL(gs, GDCgetCachetime(Config));
	  return;

     default:
	  /*** The default is a generic text file ***/
	  GSsetType(gs, A_FILE);

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

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

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

	  /*** Check and see if it's mailbox data ***/
	  
	  else if (is_multipartfile(Zebuf) != SPLIT_UNKNOWN) {
	       GSsetType(gs, A_DIRECTORY);
	       *Selstr = 'm';
	       strcpy(Selstr+1, newpath);
	       GSsetGplus(gs, FALSE);  /** Not yet.. **/
	  }
	  
	  /*** Check for uuencoding data ***/

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

	  GSsetPath(gs, Selstr);

     }
}

/*
 * Add a default view if none exists..
 */

void
AddDefaultView(gs, size, dirname)
  GopherObj *gs;
  int size;
  char *dirname;
{
     char *lang = GDCgetLang(Config);
     STATSTR statbuf;

     switch (GSgetType(gs)) {
     case A_FILE:
	  GSaddView(gs, "Text/plain", lang, size);
	  break;
     case A_DIRECTORY:
	  if (GDCgetCaching(Config) && dirname != NULL) {
	       strcat(dirname, "/.cache");
	       if (ustat(dirname, &statbuf) == 0)
		    size = statbuf.st_size;
	       
	       GSaddView(gs, "application/gopher-menu", lang, size);

	       strcat(dirname, "+");
	       if (ustat(dirname, &statbuf) == 0)
		    size = statbuf.st_size;
	       
	       GSaddView(gs, "application/gopher+-menu", lang, size);
	       GSaddView(gs, "text/html", lang, size);
	  } else {
	       GSaddView(gs, "application/gopher-menu", lang, size);
	       GSaddView(gs, "application/gopher+-menu", lang, size);
	       GSaddView(gs, "text/html", lang, size);
	  }
	       
	  break;
     case A_MACHEX:
	  GSaddView(gs, "application/mac-binhex40", lang, size);
	  break;
     case A_PCBIN:
	  GSaddView(gs, "application/octet-stream", lang, size);
	  break;
     case A_CSO:
	  GSaddView(gs, "application/qi", lang, 0);
	  break;
     case A_INDEX:
	  GSaddView(gs, "application/gopher-menu", lang, size);
	  GSaddView(gs, "application/gopher+-menu", lang, size);
	  break;
     case A_TELNET:
	  break;
     case A_SOUND:
	  GSaddView(gs, "audio/basic", lang, size);
	  break;
     case A_UNIXBIN:
	  GSaddView(gs, "application/octet-stream", lang, size);
	  break;
     case A_GIF:
	  GSaddView(gs, "image/gif", lang, size);
	  break;	
     case A_HTML:
	  GSaddView(gs, "text/html", lang, size);
	  break;
     case A_TN3270:
	  GSaddView(gs, "application/tn3270", lang, 0);
	  break;
     case A_MIME:
	  GSaddView(gs, "multipart/mixed", lang, size);
	  break;
     case A_IMAGE:
	  GSaddView(gs, "image", lang, size);
	  break;
     case A_PDF:
	  GSaddView(gs, "application/pdf", lang, size);
	  break;
     }
}



void
GSaddDateNsize(gs, statbuf)
  GopherObj *gs;
  struct stat statbuf;
{
#ifdef ADD_DATE_AND_TIME
     int           fd, i;
     char         longname[256];
     char         *cdate, *ti, *fp, *stitle;
     
     switch (GSgetType(gs)) {
     case '1': /*** It's a directory ***/
     case '7': /*** It's an index ***/
     case 'f': /*** ftp link ***/
     case 'e': /*** exec link ***/
     case 'h': /*** www link ***/
     case 'w': /*** wais link ***/
     case 'm':
	  break;
     default:
     {
	  stitle = GSgetTitle(gs);
	  if (strstr( stitle, "kb]") == 0) {
	       /* Correct for multiple view items */

	       cdate= ctime( &statbuf.st_mtime); /* last mod time */
	       cdate[ 7]= 0; cdate[10]= 0; cdate[24]= 0;
	       sprintf( longname, "%s  [%s%s%s, %ukb]", stitle,
		      cdate+8,cdate+4,cdate+22, (statbuf.st_size+1023) / 1024);
	       GSsetTitle(gs,longname);
	  }
     }
	  break;
     }
#endif /* ADD_DATE_AND_TIME */
     ;
}     



/*
 * Add a DL description if it's there ...
 */

void
GStitlefromDL(gs, filename)
  GopherObj *gs;
  char *filename;
{
#ifdef DL
     char               dlpath[2];    /*** for DL**/
     char               *dlout;

     /* Process a "dl" description if there is one! */
     
     dlpath[0] = '.';
     dlpath[1] = '\0';
     dlout = getdesc(NULL,dlpath,filename,0);
     
     Debug("dl: %s", dlpath);
     Debug(" %s", filename);
     Debug(" %s\n", dlout);

     if (dlout != NULL) {
	  GSsetTitle(gs, dlout);
     }
#endif
     ;
}


void
GSfromCapfile(gs, filename)
  GopherObj *gs;
  char *filename;
{
#ifdef CAPFILES
     
     char capfile[MAXPATHLEN];
     FILE *SideFile;
     
     strcpy(capfile,".cap/");
     strcat(capfile, filename);
     
     if ((SideFile = rfopen(capfile, "r"))!=0) {
	  Debug("cap file name: %s\n", capfile);
	  (void) Process_Side(SideFile, gs);
	  fclose (SideFile);
     }
#endif
     ;
}

/*
 * Run scripts that generate blocks...
 */

GSrunScripts(gs, filename)
  GopherObj *gs;
  char      *filename;
{
     FileIO *fio;
     char tmpstr[256];
     char *bname, *sname;
     int i, blocks;
     
     blocks = GDCnumBlkScripts(Config);

     for (i=0; i < blocks; i++) {
	  bname = GDCgetBlkName(Config, i);
	  sname = GDCgetBlkScript(Config, i);

	  if (GSfindBlock(gs, bname) == NULL) {
	       *tmpstr = '\0';
	       if (!dochroot)
		    strcpy(tmpstr, Data_Dir);
		    
	       strcat(tmpstr, sname);
	       strcat(tmpstr, " ");
	       strcat(tmpstr, filename);
	       
	       fio = FIOopenCmdline(tmpstr, "r");
	       if (fio == NULL)
		    return;

	       while (FIOreadlinezap(fio, tmpstr,256)) {
		    GSsetBlock(gs, bname, tmpstr, TRUE);
	       }
	       
	       FIOclose(fio);
	  }
     }
}



void
GSsetDefaults(gs) 
  GopherObj *gs;
{
     GSinit(gs);
     GSsetHost(gs, Zehostname);
     GSsetPort(gs, GopherPort);
     GSsetGplus(gs, TRUE);
     GSsetType(gs, '\0');
}

/*
 * Load up a gopher directory from the file system given a directory
 */

GopherDirObj *
GDfromUFS(pathname, sockfd, isGplus)
  char *pathname;
  int sockfd;
  boolean isGplus;
{
     DIR                *ZeDir;
     char               filename[256];
     static char        newpath[512];
     static GopherObj   *Gopherstow = NULL;
     static Extobj      *extstow = NULL;
     Extobj             *ext;
     GopherObj          *gs;
     struct dirent      *dp;
     GopherDirObj       *gd;
     struct stat        statbuf;
     boolean            AddItem = TRUE;
     static char        Pathp[512];
     StrArray           *Linkfiles;
     int                i;


     Debug("GDfromUFS:%s\r\n",pathname);
     Debug("GDfromUFS:Config=%d\r\n",Config);

     /*** Initialize static memory... ****/
     if (Gopherstow == NULL) {
	  Gopherstow = GSnew();
     }

     if (isGplus && GSgplusInited(Gopherstow) == FALSE)
          GSplusnew(Gopherstow);

     if (extstow == NULL)
	  ext = extstow = EXnew();
     else
	  ext = extstow;


     gs = Gopherstow;
     gd = GDnew(32);

     if (rchdir(pathname)<0) {
	  return(NULL);
     }

     if (GDCgetCaching(Config) && 
	 Cachetimedout(".cache+", GDCgetCachetime(Config), ".")==FALSE) {
	  int cachefd; 

	  if ((cachefd = ropen(".cache+", O_RDONLY)) >=0) {
	       GDplusfromNet(gd, cachefd, NULL);
	       close(cachefd);
	       return(gd);
	  }
     }

     /* open "." since we just moved there - makes it work when not
	chroot()ing and using relative paths */
     if ((ZeDir = uopendir(".")) == NULL) {
	  char tmpstr[256];

	  sprintf(tmpstr, "Cannot access directory '%s'", pathname);
	  Abortoutput(sockfd, tmpstr);
	  return(NULL);
     }

     Linkfiles = STAnew(10); if (Linkfiles==NULL) return(NULL);


     for (dp = readdir(ZeDir); dp != NULL; dp = readdir(ZeDir)) {
	  
          strcpy(newpath, pathname);
	  strcpy(filename, dp->d_name);

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

	  gs = Gopherstow;
	  GSinit(gs);

	  /*********************************************************/
	  /*** Ignored file ***/
	  if (strcmp(filename, ".")==0 || strcmp(filename, "..")==0 ||
	      strncmp(filename, ".cache", 6) ==0 || 
	      strcmp(filename, ".about.html") == 0 ||
	      GDCignore(Config,filename))
	       continue;

	  /*********************************************************/
	  /*** This is a link file, process it after other files ***/
	  else if (filename[0] == '.' && isadir(filename)==0) {
	       String *temp;
	       
	       temp = STRnew();
	       STRset(temp, filename);
	       
	       STApush(Linkfiles, temp);
	       STRdestroy(temp);
	       continue;
	  } 

	  /*********************************************************/
	  /*** directory starting with a period ***/
	  else if (filename[0] == '.') {
	       continue;
	  } 

	  /*********************************************************/
	  /*** Gopher+ block file  ***/

	  else if (GDCBlockExtension(Config, filename, ext)) {
	       char Selstr[512];
	       char *tmpstr = Selstr;
	       int num;

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

	       /** Strip off the extension from the path **/
	       tmpstr[strlen(tmpstr) - strlen(EXgetExt(ext))]='\0';

	       num = GDSearch(gd, tmpstr);
	       if (num != -1) {
		    gs = GDgetEntry(gd, num);
		    AddItem = FALSE;
	       } else {
		    GSsetDefaults(gs);
		    GSsetPath(gs, tmpstr);
	       }
	       
	       if (strcasecmp(EXgetBlockname(ext), "ASK") == 0)
		    GSsetAsk(gs, TRUE);

	       GSaddBlock(gs, EXgetBlockname(ext), fixfile(newpath));
	       
	  }

	  /*********************************************************/
	  /*** Some kind of data file                            ***/
	  else {
	       ustat(filename, &statbuf);

	       /* Strip any Decoder extensions from newpath before processing
		  them, filename needs to remain as is for type checking*/
	  
	       if (EXAcasedSearch(Config->Extensions, ext, filename, 
				  EXT_DECODER)) {

		    newpath[strlen(newpath) - strlen(EXgetExt(ext))] = '\0';
		    filename[strlen(filename) - strlen(EXgetExt(ext))] = '\0';
	       }

	       /*** Add views to item... ***/

	       if (GDCViewExtension(Config, filename, &ext)) {
		    char *Prefix;
		    int  num;
		    
		    Prefix = EXgetPrefix(ext);
		    
		    strcpy(Pathp, Prefix);
		    strcpy(Pathp+strlen(Prefix), newpath);
		    
		    /*** Strip extension off of pathname***/
		    Pathp[strlen(Prefix)+strlen(newpath)-strlen(EXgetExt(ext))]= '\0';

		    /*** Strip extension off of title***/
		    filename[strlen(filename)-strlen(EXgetExt(ext))]= '\0';

		    /*** search for existing entry to add view to **/
		    num = GDSearch(gd, Pathp);
		    if (num != -1) {
			 gs = GDgetEntry(gd, num);
			 AddItem = FALSE;
		    } else {
			 GSsetDefaults(gs);
		    }
			 
		    GSsetTitle(gs, filename);
		    GSsetPath(gs, Pathp);
		    GSsetType(gs, EXgetObjtype(ext));
	       
		    
		    /** Oh say can we hack, by the dawns early day :-) **/
		    if (strcasecmp(EXgetExt(ext), ".mindex")==0) {
			 GSsetGplus(gs, FALSE);
		    }
		    
		    if (isGplus) {
			 char *lang;
			 
			 lang = EXgetVLang(ext);
			 if (lang == NULL || strcmp(lang, "")==0)
			      lang = GDCgetLang(Config);
			 GSaddView(gs, EXgetView(ext), lang, statbuf.st_size);
		    }
	       }
	       /** Mystery file without known extension ***/
	       else {
		    char Selstr[512];
		    int num;
		    
		    strcpy(Selstr+1, newpath);
		    *Selstr = '0';

		    num = GDSearch(gd, Selstr);
		    if (num != -1) {
			 gs = GDgetEntry(gd, num);
			 AddItem = FALSE;
		    } else {
			 GSsetDefaults(gs);
		    }

		    Getfiletypes(newpath ,filename, gs);
		    if (GSgetType(gs) == '3')
			 continue;

		    GSsetTitle(gs, filename);
		    
		    if (strncmp(GSgetPath(gs), "validate ", 9) == 0) {
			 /** Ugly hack for now...  **/
			 char **askb = GDCauthAsk(Config, GSgetPath(gs)+10);
			 int i;
			 
			 GSsetAsk(gs, TRUE);
			 
			 for (i=0; askb[i] != NULL; i++) {
			      GSsetBlock(gs, "ASK", askb[i], TRUE);
			 }
		    }

		    if (GSisGplus(gs) && isGplus)
			 AddDefaultView(gs, statbuf.st_size, NULL);
	       }

	       /** Check DL database for a good name**/
	       GStitlefromDL(gs, filename);

	       if (GSgetTitle(gs) == NULL)
		    GSsetTitle(gs, filename);
	  
	       GSaddDateNsize(gs, statbuf);

	       GSfromCapfile(gs, dp->d_name);
	       if (GSgetType(gs) == '3' || GSgetType(gs) == '-')
		    continue;
	  }

	  /*** Here we go, we have either a filled in block or data item ***/

	  if (isGplus && AddItem) {
	       char tmpstr[256];
	       char timeval[16];
	       struct tm *tmthing;
	       char *cp;
	       
	       if (GSgplusInited(gs) == FALSE)
		    GSplusnew(gs);
	       
	       /*** Add admin, abstract entries, etal ***/
	       if (!GSgetAdmin(gs)) {
		    sprintf(tmpstr, "%s <%s>", 
			    GDCgetAdmin(Config), GDCgetAdminEmail(Config));
		    GSsetAdmin(gs, tmpstr);
	       }

	       if (GSgetModDate(gs) == NULL) {
		    /** Set mod date entry **/
		    tmthing = localtime(&(statbuf.st_mtime));
		    strftime(timeval,sizeof(timeval), "%Y%m%d%H%M%S", tmthing);
		    sprintf(tmpstr,"%s<%s>", asctime(tmthing),timeval);
		    cp = strchr(tmpstr, '\n');
		    if (cp != NULL)
			 *cp = ' ';
		    GSsetModDate(gs, tmpstr);
	       }	       

	  }

	  
	  /*** Add the entry to the directory ***/
	  if (AddItem) {
	       GDaddGS(gd, gs);
	  } else
	       AddItem = TRUE;
	  
     }

     /*** We don't have links yet, so we can process the BlockScripts ***/
     if (isGplus) {
	  char *cp;

	  for (i=0; i < GDgetNumitems(gd); i++) {
	       gs = GDgetEntry(gd, i);
	       cp = strrchr(GSgetPath(gs), '/');
	       if (cp != NULL) 
		    GSrunScripts(gs, cp+1);
	  }
     }

     /** Process .Link files **/

     for (i=0 ; i<STAgetTop(Linkfiles); i++) {
	  FileIO *fio;

	  fio = FIOopenUFS(STAgetText(Linkfiles,i), O_RDONLY, 0);
     
	  if (fio != NULL) {
	       GDfromLink(gd, fio, Zehostname, GopherPort, pathname, CurrentPeerName);
	       FIOclose(fio);
	  }
     }

     closedir(ZeDir);
     
     GDsort(gd);
     STAdestroy(Linkfiles);

     return(gd);
     
}



/* Misleading title - its actually loading a GD from the parent of cmd*/

GopherDirObj *
GDfromSelstr(cmd,sockfd)
  CMDobj *cmd;
  int sockfd;
{
     char *it = NULL;
     char *cp;
     GopherDirObj *gd;
     char directory[512];

     it = CMDgetFile(cmd);

     if (it == NULL)
	  return(NULL);
     else
	  strcpy(directory, it);

     cp = strrchr(directory, '/');
     if (cp != NULL)
	  *(cp+1) = '\0';
     
     if (rchdir(directory)<0) {
	  char tmpstr[512];
	  if (isadir(directory)==0)
		  return(NULL);
	  sprintf(tmpstr, "- Cannot access directory '%s'", directory);
	  Abortoutput(sockfd, tmpstr);
	  return(NULL);
     }
     
     gd = GDfromUFS(directory, sockfd, TRUE); /** Returns NULL if error **/
     
     return(gd);
}



/*
 * Send item information to client
 */
void
item_info(cmd, sockfd)
  CMDobj *cmd;
  int sockfd;
{
     GopherDirObj *gd = NULL;
     GopherObj *gs = NULL;
     int num;
     struct stat statbuf;
     char tmpstr[256];
     char *cp;

     cp = CMDgetSelstr(cmd);
     num = strlen(cp);


     /* If the selstr starts with ftp: hand it off to the gateway code */
     if (strncmp(cp, "ftp:", 4) == 0) {
	  GopherFTPgw(sockfd, cp+4, cmd);
	  return;
     }

     /* If the selstr ends in '/', strip it off. */
     if ( num != 0 && *(cp + num - 1) == '/' )
 	  *(cp + num - 1) = '\0';

     /** For now, strip off first character and find the directory above **/

     Debug("Item info for %s\n", cp);

     if ((*cp == '/') || (*cp == '\0') || (*(cp+1) == '\0')) {
	  gs = GSnew();
	  GSsetDefaults(gs);
	  GSsetPath(gs, "");
	  GSsetTitle(gs, GDCgetSite(Config));
	  GSsetType(gs, '1');
	  GSplusnew(gs);

	  GSsendHeader(sockfd, -1);
	  writestring(sockfd, "+INFO ");
	  GStoNet(gs,sockfd, GSFORM_G0, Gticket);

	  sprintf(tmpstr, "+ADMIN:\r\n Admin: %s <%s>\r\n", GDCgetAdmin(Config),
		  GDCgetAdminEmail(Config));
	  writestring(sockfd, tmpstr);


	  if (GSgetModDate(gs) == NULL) {
	       if (rstat("/", &statbuf) == 0) {
		    char timeval[16];
		    struct tm *tmthing;

		    tmthing = localtime(&(statbuf.st_mtime));
		    strftime(timeval,sizeof(timeval), "%Y%m%d%H%M%S", tmthing);
		    sprintf(tmpstr," Mod-Date: %s<%s>\r\n",
			    asctime(tmthing),timeval);
		    cp = strchr(tmpstr, '\n');
		    if (cp != NULL)
			 *cp = ' ';
		    writestring(sockfd, tmpstr);
	       }
	  } else {
	       sprintf(tmpstr, " Mod-Date: %s\r\n", GSgetModDate(gs));
	       writestring(sockfd, tmpstr);
	  }

	  if (GSgetTTL(gs) > -1) {
	       sprintf(tmpstr, " TTL: %d\r\n", GSgetTTL(gs));
	  } else {
	       sprintf(tmpstr, " TTL: %d\r\n", GDCgetCachetime(Config));
	  }
	  writestring(sockfd, tmpstr);

	  sprintf(tmpstr, " Site: %s\r\n", GDCgetSite(Config));
	  writestring(sockfd, tmpstr);
	  sprintf(tmpstr, " Org: %s\r\n", GDCgetOrg(Config));
	  writestring(sockfd, tmpstr);
	  sprintf(tmpstr, " Loc: %s\r\n", GDCgetLoc(Config));
	  writestring(sockfd, tmpstr);
	  sprintf(tmpstr, " Geog: %s\r\n", GDCgetGeog(Config));
	  writestring(sockfd, tmpstr);
	  sprintf(tmpstr, " Version: U of Minnesota Unix %s.%s pl%d\r\n",
		  GOPHER_MAJOR_VERSION, GOPHER_MINOR_VERSION, PATCHLEVEL);
	  writestring(sockfd, tmpstr);

	  if (Connections != 0) {
	       time_t howlong  = time(NULL) - ServerStarted + 1;
	       int connperhour = (Connections * 3600) / howlong;
	       char *started = ctime(&ServerStarted);
	       
	       *(started+24) = '\0';
	       sprintf(tmpstr, "+STATISTICS:\r\n Total Connections: %d\r\n Server Started: %s\r\n Connections per Hour: %d\r\n Concurrent Sessions: %d\r\n", 
		       Connections, started, connperhour, ActiveSessions);
	       writestring(sockfd, tmpstr);
	       
	  }


	  if (GDCgetAbstract(Config) != NULL) {
	       char abline[256];
	       char *cp, *nl;

	       writestring(sockfd, "+ABSTRACT:\r\n");
	       nl = cp = GDCgetAbstract(Config);
	       while (nl) {
		    nl = strchr(cp, '\n');
		    writestring (sockfd, " ");
		    if (nl == NULL)
			 writestring(sockfd, cp);
		    else {
			 strncpy(abline, cp, (nl-cp));
			 abline[nl-cp] = '\0';
			 writestring(sockfd, abline);
			 cp = nl+1;
		    }
		    writestring(sockfd, "\r\n");
	       }

	  }

	  writestring(sockfd, "+VERONICA:\r\n treewalk:");
	  if (GDCgetShouldIndex(Config) == TRUE)
	       writestring(sockfd, " yes");
	  else
	       writestring(sockfd, " no");

	  writestring(sockfd, "\r\n+VIEWS:\r\n");
	  if (GDCgetCaching(Config)) {
	       if (rstat("/.cache", &statbuf) == 0) {
		    sprintf(tmpstr, " application/gopher-menu %s: <%dk>\r\n",
			    GDCgetLang(Config), (statbuf.st_size + 512)/1024);
		    writestring(sockfd, tmpstr);
	       } else {
		    writestring(sockfd, " application/gopher-menu: <0k>\r\n");
	       }

	       if (rstat("/.cache+", &statbuf) == 0) {
		    sprintf(tmpstr, " application/gopher+-menu %s: <%dk>\r\n",
			    GDCgetLang(Config), (statbuf.st_size + 512)/1024);
		    writestring(sockfd, tmpstr);
	       } else {
		    writestring(sockfd, " application/gopher+-menu: <0k>\r\n");
	       }

	  } else {
	       writestring(sockfd, " application/gopher-menu: <0k>\r\n");
	       writestring(sockfd, " application/gopher+-menu: <0k>\r\n");
	  }

	  writestring(sockfd, " text/html: <0k>\r\n");
	  writestring(sockfd, " Directory/recursive: <0k>\r\n");
	  writestring(sockfd, " Directory+/recursive: <0k>\r\n");

	  GSdestroy(gs);
     } 
#ifndef NO_AUTHENTICATION
     else if (strncmp(CMDgetSelstr(cmd), "validate ",9) == 0) {

	  gd = GDfromSelstr(cmd,sockfd);
	  
	  num = GDSearch(gd, CMDgetSelstr(cmd)+9);

	  uchdir("/");

	  if (num < 0 || gd == NULL) {
	       GplusError(sockfd, 1, "Cannot find item information for that item", NULL);
	       return;
	  }
	  else {
	       /* Find the authenticator associated with the directory/item **/
	       char *selstr = CMDgetSelstr(cmd);
	       char **askb = GDCauthAsk(Config, CMDgetFile(cmd));
	       int i;

	       gs = GDgetEntry(gd, num);
	       GSsetPath(gs, selstr);
	       Debug("Askblock is %s\n", askb[0]);
	       GSsetAsk(gs, TRUE);

	       for (i=0; askb[i] != NULL; i++) {
		    GSsetBlock(gs, "ASK", askb[i], TRUE);
	       }

	       DebugGSplusPrint(gs,"got here...");


	       GSsendHeader(sockfd, -1);
	       GSplustoNet(gs, sockfd, NULL, "");
	  }
	  
     } 
#endif /* NO_AUTHENTICATION */

     else {

	  gd = GDfromSelstr(cmd,sockfd);

	  num = GDSearch(gd, CMDgetSelstr(cmd));
	  uchdir("/");

	  if (num < 0 || gd == NULL) {
	       GplusError(sockfd, 1, "Cannot find item information for that item", NULL);
	       return;
	  }
	  else {
	       gs = GDgetEntry(gd, num);
	       GSsendHeader(sockfd, -1);
	       GSplustoNet(gs, sockfd, NULL, Gticket);
	  }

     }
     if (gd)
	  GDdestroy(gd);

     writestring(sockfd, ".\r\n");
}

/*
 * Output HTML for cutesy icons
 */

GSicontoNet(gs, sockfd)     
  GopherObj *gs;
  int       sockfd;
{
     char iconfile[64];
     int fd;

     if (gs==NULL)
	  return;

     sprintf(iconfile, "/lib/htmlicon.%c", GSgetType(gs));

     
     if ((fd = ropen(iconfile, O_RDONLY)) >= 0) {
	  char imgline[128];
	  sprintf(imgline, "<IMG ALIGN=absbottom BORDER=0 SRC=gopher://%s:%d/I9%s>",
		  Zehostname, GopherPort, iconfile);
	  writestring(sockfd, imgline);
	  close(fd);
     }
}
/*
** 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, isgplus, view, filter)
  int sockfd;
  char *pathname;
  boolean isgplus;
  char *view;
  char *filter;
{
     GopherDirObj *gd = NULL;
     boolean      attrlist = FALSE;
     boolean      HTMLit   = FALSE;
     boolean      Recurse  = FALSE;
     char         *filtereddata[16], **filtered=filtereddata;
     int          i=0;
     StrArray     *RecurseDirs = NULL;
     String       *stapath;
     int          result;


     result = GDCCanBrowse(Config, CurrentPeerName, CurrentPeerIP,
			   ActiveSessions);
     
     if (result == SITE_NOACCESS) {
	  Abortoutput(sockfd,  GDCgetBummerMsg(Config));
	  return;
     } else if (result == SITE_TOOBUSY) {
	  Abortoutput(sockfd, "Sorry, too busy right now..");
	  return;
     }


     if (uchdir("/"))
	  perror("SOL dude");

     if (filter != NULL) {
	  while (*filter != '\0') {
	       if (*filter=='+') {
		    *filter = '\0';
		    filtered[i] = filter+1;
		    filter++; i++;
	       }
	       filter++;
	  }
	  filtered[i] = NULL;
     } else
	  filtered = NULL;


     if (view != NULL) {
	  if (strncmp(view, "application/gopher+-menu",24) == 0)
	       attrlist = TRUE;
	  else if (strncmp(view, "text/html", 9) == 0) {
	       attrlist = TRUE;
	       HTMLit   = TRUE;
	  }
	  else if (strncmp(view, "Directory+/recursive", 20)==0)
	       Recurse = TRUE;
	  else if (strncmp(view, "Directory/recursive", 19)==0)
	       Recurse = TRUE;
     }
     if (Recurse)
	  RecurseDirs = STAnew(32);

     if (rchdir(pathname)<0) {
	  char tmpstr[512];

#ifdef UMNDES
	  if (errno == EACCES)
	       PleaseAuthenticate(sockfd);
#endif

	  sprintf(tmpstr, "- Cannot access directory '%s'", pathname);
	  Abortoutput(sockfd, tmpstr);
	  return;
     }

     if (isgplus)
	  GSsendHeader(sockfd, -1);


     do {
	  Debug("Sending %s\n", pathname);

	  if (Recurse) {
	       rchdir("/");
	       rchdir(pathname);
	  }


	  
	  if (!attrlist && GDCgetCaching(Config) && !HTMLit &&
	     (Cachetimedout(".cache", GDCgetCachetime(Config), ".") ==FALSE)) {
	       /*** Old style cache  ***/
	       send_binary(sockfd, ".cache", FALSE);

	  } else if (GDCgetCaching(Config) && !HTMLit &&
		     Cachetimedout(".cache+", GDCgetCachetime(Config)
				   , ".")==FALSE) {
	       /*** Gopher+ cache. ***/
	       
	       if (strncmp(view, "application/gopher+-menu",24)==0)
		    send_binary(sockfd, ".cache+", FALSE);
	       else if (strncmp(view, "application/gopher-menu",23)==0)
		    send_binary(sockfd, ".cache", FALSE);
	       
	  } else {
	       /** If we didn't cache then we have to 
		 load up the directory **/
	       gd = GDfromUFS(pathname, sockfd, attrlist);
	       
	       if (gd == NULL) {
		    /** Should generate an error message here **/
		    break;
	       }

	       rchdir("/");
	       
	       if (HTMLit) {
		    writestring(sockfd, "<HTML>\r\n<HEAD>\r\n");
		    writestring(sockfd, "<LINK rev=made href=\"mailto:");
		    writestring(sockfd, GDCgetAdminEmail(Config));
		    writestring(sockfd, "\">\r\n");
		    
		    if (GDgetTitle(gd) != NULL) {
			 writestring(sockfd, "<TITLE>");
			 writestring(sockfd, GDgetTitle(gd));
			 writestring(sockfd, "</TITLE>");
		    }
		    writestring(sockfd, "</HEAD>\r\n<BODY>\r\n");
		    
	       {
		    STATSTR   statbuf;
		    char      html_path[1024];
		    char      *cp;
		    
		    strcpy    (html_path, pathname);
		    cp = html_path + strlen(html_path) - 1;
		    if (*cp != '/') {
			 *(++cp) = '/';
			 *(++cp) = '\0';
		    }
		    strcat    (html_path, ".about.html");
		    
		    if (rstat(html_path, &statbuf) == 0)
			 send_binary(sockfd, html_path, FALSE);
		    else if (strcmp(pathname, "/") == 0) {
			 writestring(sockfd, "<H1>");
			 writestring(sockfd, GDCgetSite(Config));
			 writestring(sockfd, "</H1>\r\n<H2>");
			 writestring(sockfd, GDCgetOrg(Config));
			 writestring(sockfd, "</H2>\r\n");
		    }
	       }
		    GDtoNet(gd, sockfd, GSFORM_HTML, Gticket, &GSicontoNet);
		    writestring(sockfd, "</BODY>\r\n</HTML>\r\n");
	       }
	       else if (attrlist)
		    GDplustoNet(gd, sockfd,filtered, Gticket);
	       else
		    GDtoNet(gd, sockfd, GSFORM_G0, Gticket, NULL);

	  }
	  /*
	   * Write out the cache... After we send out the data to the net.
	   */
	  if (GDCgetCaching(Config) && gd != NULL) {
	       int cachefd;
	       char cachefile[MAXPATHLEN];
	       
	       strcpy(cachefile, pathname);
	       strcat(cachefile, "/.cache");
	       
	       cachefd = ropen(cachefile, O_WRONLY|O_CREAT|O_TRUNC, 0644);
	       
	       if (cachefd >= 0) {
		    Debug("Caching directory... into %s\n",cachefile);
		    GDtoNet(gd, cachefd, GSFORM_G0, NULL, NULL);
		    close(cachefd);
	       }
	       
	       if (attrlist) {
		    strcat(cachefile, "+");
			 
		    cachefd = ropen(cachefile, O_WRONLY|O_CREAT|O_TRUNC,0644);
		    
		    if (cachefd >= 0) {
			 GDplustoNet(gd, cachefd,filtered, "");
			 close(cachefd);
		    }
	       }
	  }
	  
	  if (Recurse) {
	       GopherObj *gs;
	       String    *pushstring = STRnew();

	       /** Push entries on the stack **/
	       for (i=0; i< GDgetNumitems(gd); i++) {
		    STATSTR stbuf;
		    char    *cp;

		    gs = GDgetEntry(gd, i);

		    if ((GSgetType(gs) == A_DIRECTORY) &&
			(strcmp(GSgetHost(gs), Zehostname) == 0)) {
			 STRset(pushstring,  GSgetPath(gs));

			 rchdir("/");

			 cp = STRget(pushstring);
			 if (rstat(cp+1, &stbuf) == 0)
			      STApush(RecurseDirs, pushstring);
		    }
	       }

	       do {
		    stapath = STApop(RecurseDirs);
		    if (stapath == NULL) {
			 Recurse = FALSE;  /** Done **/
			 break;
		    }
		    pathname = STRget(stapath);
		    
		    if (*pathname == 'm')
			 process_mailfile(sockfd, pathname+1);
	       } while (*pathname == 'm');

	       pathname++;

	       if (gd != NULL) {
		    GDdestroy(gd);
		    gd = NULL;
	       }
	       STRdestroy(pushstring);
	  }
     } while (Recurse);

     writestring(sockfd, ".\r\n");
     GDdestroy(gd);
}


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

int
Process_Side(sidefile, Gopherp)
  FILE *sidefile;
  GopherObj *Gopherp;
{
     char inputline[MAXLINE];
     char *cp;
     int  retval = TRUE;

     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... ***/
		    cp = GSgetPath(Gopherp);
		    *cp = '7';
	       } else if (inputline[5] == '9') {
		    /*** Might as well set the path too... ***/
		    cp = GSgetPath(Gopherp);
		    *cp = '9';
	       } else if (inputline[5] == '-')
		    retval = FALSE;
	  }

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

	  else if (strncmp(inputline, "Abstract=", 9)==0) {
	       GSsetAbstract(Gopherp, inputline+9);
	  }

	  else if (strncmp(inputline, "Admin=", 9)==0) {
	       GSsetAdmin(Gopherp, inputline+6);
	  }

     }

     return(retval);
}


/* dgg++   test if file is ASK script */
boolean
IsAskfile( pathname)
  char *pathname;
{
     struct stat statbuf;
     static char tmpfile[512];

     strcpy(tmpfile, pathname);
     strcat(tmpfile, ".ask"); /* hack -- should do fancy lookup in Config */

     if (!rstat( tmpfile, &statbuf))
	  return TRUE;
     else 
	  return FALSE;
}

int 
check_file(sockfd, filename, isGplus)
  int sockfd;
  char *filename;
  boolean isGplus;
{
     ;
}

/*
** 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
**
**  This also used the global variable ASKfile
**
*/

void
printfile(sockfd, pathname, startbyte, endbyte, Gplus)
  int     sockfd;
  char    *pathname;
  int     startbyte, endbyte;
  boolean Gplus;
{
     FILE        *ZeFile;
     char         inputline[MAXPATHLEN+4];
     FILE        *pp;
     static char *writebuf = NULL;
     static char *bufptr   = NULL;
     int          len;
     int          result;
     boolean      dontbuffer = FALSE;

     /*** Check and see if the peer has permissions to read files ***/
     
     result = GDCCanRead(Config, CurrentPeerName, CurrentPeerIP, ActiveSessions);

     if (result == SITE_NOACCESS) {
	  Abortoutput(sockfd, GDCgetBummerMsg(Config));
	  LOGGopher(sockfd,"Denied access for %s", pathname);
	  return;
     } else if (result == SITE_TOOBUSY) {
	  Abortoutput(sockfd, "Sorry, too busy right now. Try again in a few minutes");
	  return;
     }


     Debugmsg("starting printfile");

     /* dgg++  patch to block ASK calls from bad clients */
     if (IsAskfile(pathname)) {
	  if (!Gplus || !ASKfile) {
	       char errmsg[256];
	       if (!ASKfile)
		    sprintf(errmsg," Missing input data to ASK form\t\t\t\r\n");
	       else
		    sprintf(errmsg," This ASK form needs a gopher+ client\t\t\t\r\n");
	       Abortoutput(sockfd, errmsg);
	       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 (writebuf == NULL) {
	  writebuf = (char *) malloc(4096 * sizeof(char));
	  bufptr   = writebuf;
     }

     if (startbyte != 0)
	  fseek(ZeFile, startbyte, 0);
     
     if ((pp = specialfile(sockfd, ZeFile, pathname))!=NULL) {
	  fclose(ZeFile);
	  ZeFile = pp;
	  dontbuffer = TRUE;
     }

     if (Gplus)
	  GSsendHeader(sockfd, -1);

     while (fgets(inputline, MAXPATHLEN, ZeFile) != NULL) {
	  
	  ZapCRLF(inputline);

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

	  if ((int)strlen(inputline) < 512)
	       strcat(inputline, "\r\n");

	  len = strlen(inputline);

	  if (dontbuffer) {
	       alarm(WRITETIMEOUT);
	       writestring(sockfd, inputline);
	       alarm(0);
	  } else {
	       if (((bufptr-writebuf) + len+1) > 4096) {
		    /** Write out the buffer if it's too big to fit.. **/
		    (void) alarm(WRITETIMEOUT);
		    if (writestring(sockfd, writebuf))
			 LOGGopher(sockfd, "Client went away"), gopherd_exit(-1);
		    (void) alarm(0);
		    bufptr=writebuf;
	       }

	       
	       strcpy(bufptr, inputline);
	       bufptr += len;
	       *bufptr = '\0';
	  }
	  if (endbyte >0) {
	       if (ftell(ZeFile) >= endbyte)
		    break;
	  }
     }

     Specialclose(ZeFile);

     if (bufptr != writebuf) {
	  alarm(WRITETIMEOUT);
	  writestring(sockfd, writebuf);
	  alarm(0);
     }

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


#define BUFSIZE 8192  /* No, user process can't affect Kernel-IP
			 decission on what the actual output MTU is.
			 System call overhead may well be too great
			 to care for this.  If the protocol had been
			 on top of the UDP, things would be
			 different..  [mea@gopher.funet.fi] */

void
send_binary(sockfd, filename, isGplus)
  int sockfd;
  char *filename;
  boolean isGplus;
{
     FILE           *sndfile,*pp;
     unsigned char  in[BUFSIZE];
     register int   j;
     int            gotbytes, size;
     struct stat    buf;
     int            result;


     Debug("Sending %s, binary way\n", filename);
     /** Don't check decoder/extension if .cache **/
     if (strncmp(filename, ".cache",6)!=0) {

	  result = GDCCanRead(Config, CurrentPeerName, CurrentPeerIP, 
			      ActiveSessions);
	  if (result == SITE_NOACCESS) {
	       Abortoutput(sockfd, GDCgetBummerMsg(Config));
	       return;
	  } else if (result == SITE_TOOBUSY) {
	       Abortoutput(sockfd, "Too busy right now");
	       return;
	  }
	  
	  if (strcmp(filename, "-") == 0) {
	       /*** Do some live digitization!! **/
	       sndfile = popen("record -", "r");
	  }
	  else
	       sndfile = rfopen(filename, "r");
	  
	  if (!sndfile) {
	       /*
		* The specified file does not exist
		*/
	       char notexistline[256];
	       sprintf(notexistline, "'%s' does not exist!!", filename);
	       Abortoutput(sockfd, notexistline);
	       
	       return;
	  }

	  if ((pp = specialfile(sockfd, sndfile, filename)) != NULL) {
	       fclose(sndfile);
	       sndfile = pp;
	  }
     } else
	  sndfile = rfopen(filename, "r");


     if ((isGplus) && strcmp(filename, "-") == 0) 
	  GSsendHeader(sockfd, -2);
     else if (isGplus) {
	  rstat(filename, &buf);
	  size = buf.st_size;
	  GSsendHeader(sockfd, size);
     }


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

	  (void) alarm(WRITETIMEOUT);  /** In case client is hung.. **/
          j = writen(sockfd, in, gotbytes);
	  (void) alarm(0);

	  Debug("Wrote %d binary bytes\n",j);
	  if (j <= 0)
	       break;       /*** yep another error condition ***/

     }
     Specialclose(sndfile);
}


