SMOLNET PORTAL home about changes
/********************************************************************
 * wilkinson
 * 3.40VMS-1
 * 1995/11/24 13:00   
 * gopher_root1:[gopher.g2.vms2_13.gopherd]serverutil.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: serverutil.c
 * utilities for the server.
 *********************************************************************
 * Revision History:
 * serverutil.c,v
 * Revision 3.40VMS-1 1995/11/24 13:00  wilkinson
 * Tweaked security violation character scan and logging in Gpopen().
 *
 * Revision 3.40VMS 1995/09/25 14:40    wilkinson
 * Consolodate VMS/Unix source code for server as well as client
 *
 * Revision 3.40  1995/02/25  20:49:05  lindner
 * More timeouts..
 *
 * Revision 3.39  1995/02/11  06:22:49  lindner
 * Fix for setvbuf parameters
 *
 * Revision 3.38  1995/02/07  19:34:08  lindner
 * A little more readable
 *
 * Revision 3.37  1995/02/07  08:37:37  lindner
 * Rewrite of mailfile/multifile parsing
 *
 * Revision 3.36  1995/02/07  07:03:45  lindner
 * performance fixes
 *
 * Revision 3.35  1995/02/02  17:13:54  lindner
 * Fix memory leaks
 *
 * Revision 3.34  1994/12/31  07:46:45  lindner
 * Make shell script output line buffered for slow scripts
 *
 * Revision 3.33  1994/12/20  17:20:56  lindner
 * Put environment variable setter here..
 *
 * Revision 3.32  1994/12/06  20:56:41  lindner
 * Fix for multi-line bummer messages
 *
 * Revision 3.31  1994/11/29  05:00:09  lindner
 * Fix for exec:
 *
 * Revision 3.30  1994/11/16  18:53:35  lindner
 * Allow multi-line Bummer messages
 *
 * Revision 3.29  1994/10/13  05:17:50  lindner
 * Compiler complaint fixes
 *
 * Revision 3.28  1994/09/29  19:57:09  lindner
 * Make Grep Index queries work
 *
 * Revision 3.27  1994/07/21  15:46:49  lindner
 * New multipart file code
 *
 * Revision 3.26  1994/07/03  21:18:04  lindner
 * Add initgroup() call
 *
 * Revision 3.25  1994/06/29  05:36:54  lindner
 * Add username logging
 *
 * Revision 3.24  1994/04/22  03:35:50  lindner
 * for sunos 4.0.3
 *
 * Revision 3.23  1994/03/31  22:45:50  lindner
 * Generate gopher- error responses for gopher- clients
 *
 * Revision 3.22  1994/03/31  21:10:30  lindner
 * Don't need Setuid_username unless using UMNDES
 *
 * Revision 3.21  1994/03/08  15:56:09  lindner
 * gcc -Wall fixes
 *
 * Revision 3.20  1994/03/04  23:25:38  lindner
 * faster isadir()
 *
 * Revision 3.19  1994/01/21  04:01:10  lindner
 * Add support for flock(), fix LOGGopher() function declaration
 *
 * Revision 3.18  1993/11/05  07:25:44  lindner
 * futzing with stdarg lines
 *
 * Revision 3.17  1993/10/11  04:40:54  lindner
 * Changes to allow logging via daemon.info syslogd facility
 *
 * Revision 3.16  1993/09/30  23:16:50  lindner
 * Hack out SETPROCTITLE on systems that can't hack it
 *
 * Revision 3.15  1993/09/30  17:01:18  lindner
 * Remove unnessesary logging
 *
 * Revision 3.14  1993/09/20  16:54:00  lindner
 * Remove dead code, moved to Sockets.c
 *
 * Revision 3.13  1993/08/10  20:28:10  lindner
 * return true for non-existant cache file
 *
 * Revision 3.12  1993/08/06  14:30:47  lindner
 * Fixes for better security logging
 *
 * Revision 3.11  1993/08/05  20:46:36  lindner
 * Fix for Gpopen for single quotes and !
 *
 * Revision 3.10  1993/08/04  22:14:51  lindner
 * Mods to use Gpopen
 *
 * Revision 3.9  1993/08/04  22:12:48  lindner
 * Mods to use Gpopen
 *
 * Revision 3.8  1993/07/27  05:27:56  lindner
 * Mondo Debug overhaul from Mitra
 *
 * Revision 3.7  1993/07/20  23:57:29  lindner
 * Added LOGGopher here, added routine to set proctitle
 *
 * Revision 3.6  1993/07/07  19:34:53  lindner
 * fixed typo in GplusError
 *
 * Revision 3.5  1993/06/22  07:07:14  lindner
 * prettyfication
 *
 * Revision 3.4  1993/04/10  06:07:40  lindner
 * More debug msgs
 *
 * Revision 3.3  1993/04/09  15:12:09  lindner
 * Better error checking on getpeername()
 *
 * Revision 3.2  1993/03/24  20:28:55  lindner
 * Moved some code from gopherd.c and changed error message delivery
 *
 * Revision 3.1.1.1  1993/02/11  18:02:53  lindner
 * Gopher+1.2beta release
 *
 * Revision 1.2  1993/02/09  22:16:24  lindner
 * Mods for gopher+ error results.
 *
 * Revision 1.1  1992/12/10  23:13:27  lindner
 * gopher 1.1 release
 *
 *
 *********************************************************************/



#include "gopherd.h"
#include "serverutil.h"
#include "Debug.h"
#include "Dirent.h"		/* For S_ISADIR */

#ifndef VMS_SERVER
#ifndef NOSYSLOG
#include <syslog.h>
#else
#define syslog(a,b)	fprintf(stderr, "%s\n", (b))
#define openlog(a,b,c)	fprintf(stderr, "%s\n", (a))
#endif
#else
#ifdef MULTINET
#include "syslog.h"
#else
#ifndef NOSYSLOG
#define NOSYSLOG
#endif
#endif
#endif

#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#ifndef VMS_SERVER
/* Try using flock() if fcntl() won't let us lock. */
#if !defined(USE_FLOCK) && !defined(F_SETLKW)
#define USE_FLOCK
#endif

#ifdef USE_FLOCK
#include <sys/file.h>
#endif
#endif

/*
 * This finds the current peer and the time and  jams it into the
 * logfile (if any) and adds the message at the end
 */
#ifdef __STDC__
void LOGGopher(int sockfd, const char *fmt, ...)
#else /* !__STDC__ */
void
LOGGopher(sockfd, fmt, va_alist)
  int sockfd;
  char *fmt;
va_dcl
#endif /* __STDC__ */
{
     va_list args;
     char message[512];
     time_t          Now;
     char            *cp;
                     /* cp + ' ' + host_name + ' : ' + MAXLINE + '\n' + '\0' */
     char            buf[286+MAXLINE];
     char            userandhost[256];
#ifndef VMS_SERVER
#ifndef USE_FLOCK
     struct flock    lock;
#endif
#else
     char            NowBuf[26];
     char	    *c2;
     char	    LogTag[60];
     char	    LogBuf[100];
     int	    i;
#endif

#ifdef __STDC__
     va_start(args, fmt);
#else
     va_start(args);
#endif
     
     (void) vsprintf(message, fmt, args);

#ifdef VMS_SERVER
     va_end(args);

     userandhost[0] = '\0';
     if (CurrentUser != NULL) {
	  strcpy(userandhost, CurrentUser);
	  strcat(userandhost, "@");
	  strcat(userandhost, CurrentPeerName);
     } else
	  strcpy(userandhost, CurrentPeerName);

     if (c2=GDCgetLogTag(Config)) {
	c2 = strcpy(LogTag, c2);
	if (cp=strchr(c2,'#')) {
	    if (strncasecmp(cp-2,"Cn",2)==0) {
		cp -= 2;
		*cp = '\0';
		strcpy(LogBuf,LogTag);
		cp += 3;
		i = strlen(LogBuf);
		LogBuf[i+=sprintf(LogBuf+i,"#%d",Connections)] = '\0';
		strcat(LogBuf, cp);
		c2 = LogBuf;
	    }
	}
     }
#else
     if (LOGFileDesc == -1) {
	  return;
     }
#endif

     if ((int)LOGFileDesc == -2) {
	  /** Syslog case.. **/
#ifndef VMS_SERVER
	  syslog(LOG_INFO, "%s : %s", CurrentPeerName, message);
#else
	  syslog(LOG_INFO, "p%d%s %s : %s", Config?GDCgetPort(Config):0, c2,
						    userandhost, message);
#endif
	  return;
     }
     /** File case ***/
  
#ifndef VMS_SERVER
#ifdef USE_FLOCK
     flock(LOGFileDesc, LOCK_EX);
#else
     lock.l_type = F_WRLCK;
     lock.l_whence = SEEK_SET;
     lock.l_start = 0L;
     lock.l_len = 0L;
     fcntl(LOGFileDesc, F_SETLKW, &lock);
#endif
#endif

     time(&Now);         /* Include this in the lock to make sure */
     cp = ctime(&Now);   /*  log entries are chronological */
     ZapCRLF(cp);
     
#ifndef VMS_SERVER
     /* someone else may have written to the file since we opened it */
     lseek(LOGFileDesc, 0L, SEEK_END);

     userandhost[0] = '\0';
     if (CurrentUser != NULL) {
	  strcpy(userandhost, CurrentUser);
	  strcat(userandhost, "@");
	  strcat(userandhost, CurrentPeerName);
     } else
	  strcpy(userandhost, CurrentPeerName);

     sprintf(buf, "%s %d %s : %s\n", cp, getpid(), userandhost, message);
     write(LOGFileDesc, buf, strlen(buf));
     
     /* unlock the file */
#ifdef USE_FLOCK
     flock(LOGFileDesc, LOCK_UN);
#else
     lock.l_type = F_UNLCK;
     fcntl(LOGFileDesc, F_SETLKW, &lock);
#endif

     Debug("%s", buf);
#else
     cp=strcpy(NowBuf,cp);
     sprintf(buf, "%s p%d%s %s : %s\n", cp, Config?GDCgetPort(Config):0, c2,
						    userandhost, message);
     if (!GDCgetLogfile(Config) && strlen(fmt) && !RunFromInetd) {
	Debug("LOGGopher w/out LogFile: %s\n", buf);
     }
     else {
	if ((LOGFileDesc = fopen_VMSopt(GDCgetLogfile(Config),"a",
					log_alq,log_deq)) !=NULL) {
	    if (strlen(fmt)) {
		fwrite(buf, strlen(buf), 1, LOGFileDesc);
		Debug("LOGGopher: %s\n", buf);
		fflush(LOGFileDesc);
	    }
	    fclose(LOGFileDesc);
	 }
	 else {
	    if (strlen(fmt) && !RunFromInetd) {
		char *oops;

		oops = (char *)malloc(strlen(buf)+256);
		sprintf(oops, "Can't open the logfile %s - (%d/%d=%s/%s)\n%s", 
				    GDCgetLogfile(Config), 
					vaxc$errno, vaxc$errno_stv,
					    STRerror(errno), STRerror_stv(),
						    buf);
		fprintf(stderr, "LOGGopher: %s\n",oops);
		Debug("LOGGopher w/ bad Logfile: %s\n", oops);
		free(oops);
		if (vaxc$errno == RMS$_ACC)
		    gopherd_exit(vaxc$errno);
	    }
	 }
     }
     if (sockfd == -99) {
	DEBUG = TRUE;
	LOGGopher(-1,"server shutdown!!!");
	gopherd_exit(-1);
     }
#endif
     
}


/*
 * Gopher + error mechanism
 *
 * errclass is the type of error
 * text is the first line of error text,
 * moretext is a char array of yet more text to send
 */
/*
	    For Gopher0 requests
		    if they're expecting a directory, just pass back the 
			AbortMsg -- it's a single-item directory
			reference pointing to the message file.  
		    if they're expecting a document, we should read the
			document to them.
		For Gopher+ users
		    we should read the AbortMsg file to them.
*/

void
GplusError(sockfd, errclass, text, moretext)
  int sockfd;
  int errclass;
  char *text;
  char **moretext;
{
     char outputline[256];
     int i;
     char *tempstr = NULL;
     char *temptext[256];
#ifdef VMS_SERVER
     char expected;

     if (tempstr = CMDgetSelstr(cmd))
	switch (*tempstr) {
	case A_DIRECTORY:
	case A_INDEX:
	case A_FTP:
	case A_MAILSPOOL:
		    expected = A_DIRECTORY;
		    break;
	default:    expected = A_FILE;
	}
     if (AbortString)
	text = AbortString;
#endif
     
     if (text == NULL) 
	  text = "Unspecified error";

     if ((moretext == NULL) && (strchr(text, '\n') != NULL)) {
	  char *nl;
	  int i = 0;

	  tempstr = strdup(text);
	  nl = tempstr;
	  while (nl = strchr(nl, '\n')) {
	       *nl = '\0';
	       nl++;
	       
	       temptext[i++] = nl;
	  }
	  temptext[i] = NULL;

	  moretext = temptext;
	  text     = tempstr;
     }


     if (!IsGplus)
#ifndef VMS_SERVER
	  sprintf(outputline, "0%s\t\terror.host\t1\r\n", text);
#else
    {
          if (expected == A_DIRECTORY)
	  	  sprintf(outputline, "0%s\t%s\t%s\t%d\r\n", text,
				AbortMsg?AbortMsg:"",
				AbortMsg?GDCgetHostname(Config):"error.host",
				AbortMsg?GDCgetPort(Config):1);
          else
	          sprintf(outputline, "%s\r\n", text);
    }
#endif
     else
	  sprintf(outputline, "--1\r\n%d %s <%s>\r\n%s\r\n", 
	     errclass, GDCgetAdmin(Config),
	     GDCgetAdminEmail(Config), text);

     (void) alarm(WRITETIMEOUT);
     if (writestring(sockfd, outputline)<0) {
	  LOGGopher(sockfd, "Client went away!");
#ifndef VMS_SERVER
	  exit(-1);
#else
	  (void) alarm(0);
	  return;
#endif
     }
     (void) alarm(0);

     if (moretext != NULL)
	  for (i=0; moretext[i] != NULL; i++) {
	       if (!IsGplus) {
#ifndef VMS_SERVER
		    sprintf(outputline, "0%s\t\terror.host\t1\r\n", 
			    moretext[i]);
#else
		    if (expected==A_DIRECTORY)
			sprintf(outputline, "0%s\t\terror.host\t1\r\n", 
				moretext[i]);
		    else
			sprintf(outputline, "%s\r\n", text);
#endif
		    writestring(sockfd, outputline);
	       } else  {
		    (void) alarm(WRITETIMEOUT);
		    writestring(sockfd, moretext[i]);
		    writestring(sockfd, "\n");
		    (void) alarm(0);
	       }
	  }
#ifdef VMS_SERVER
     if (AbortMsg != NULL && (IsGplus || (expected == A_FILE))) {
	FILE *AbortFile;
	time_t now;
	char *line;

	if (AbortFile = fopen_VMSopt(AbortMsg+1,"r")) {
	  while (fgets(outputline, sizeof(outputline), AbortFile) != NULL) {
	       ZapCRLF(outputline);
	       if (*outputline == '.' && outputline[1] == '\0') {
		       outputline[1] = '.';
		       outputline[2] = '\0';
	       }
	       now = time(&now);
	       if (AbortGS)
		    line = VMS$FormatTokens(outputline, GSgetPath(AbortGS)+1,
							NULL, &now,
				    GSgetPort(AbortGS)?GSgetPort(AbortGS):-1,
							GSgetHost(AbortGS),
							GSgetType(AbortGS),
							GSgetAdmin(AbortGS),
							cmd);
	       else
		    line = VMS$FormatTokens(outputline, NULL, NULL, &now,
						    -1, NULL, -1, NULL, cmd);

	       if (!IsGplus) {
		    writestring(sockfd, line);
		    writestring(sockfd, "\r\n");
	       } else  {
		    (void) alarm(WRITETIMEOUT);
		    writestring(sockfd, line);
		    writestring(sockfd, "\n");
		    (void) alarm(0);
	       }
	  }
	  fclose(AbortFile);
	}
     }
#endif
     (void) alarm(WRITETIMEOUT);
     writestring(sockfd, ".\r\n");
     (void) alarm(0);

     close(sockfd);

     return;
}


/* 
 * This routine cleans up an open file descriptor and sends out a bogus
 * filename with the error message
 */

void
Abortoutput(sockfd, errmsg)
  int sockfd;
  char *errmsg;
{
     GplusError(sockfd, 1, errmsg, NULL);
#ifdef VMS_SERVER
     AbortMsg = NULL;
     if (AbortGS)
	if (discardAbortGS)
	    GSdestroy(AbortGS);
     AbortGS = NULL;
     AbortString = NULL;
     discardAbortGS = FALSE;
#endif
}

#ifndef VMS_SERVER

/*
 * only works if non-chroot really...
 */

boolean
Setuid_username(username)
  char *username;
{
     struct passwd *pw;

     if (getuid() != 0)
	  return(FALSE);

     pw = getpwnam(username);
     
     if (!pw) {
	  Debug("Couldn't find user '%s'\n", username);

	  return(FALSE);
     }

     if (pw->pw_uid == 0)   /* Don't allow this to be used for root access*/
	  return(FALSE);

     if (initgroups(pw->pw_name, pw->pw_gid) != 0 )
          return(FALSE);

     if (setgid(pw->pw_gid) < 0)
	  return(FALSE);

     if (setuid(pw->pw_uid) < 0)
	  return(FALSE);

     Debug("Successfully changed user privs to '%s'\n", username);

     return(TRUE);
}

/*
 * is_mail_from_line - Is this a legal unix mail "From " line?
 *
 * Given a line of input will check to see if it matches the standard
 * unix mail "from " header format. Returns 0 if it does and <0 if not.
 *
 * 2 - Very strict, also checks that each field contains a legal value.
 *
 * Assumptions: Not having the definitive unix mailbox reference I have
 * assumed that unix mailbox headers follow this format:
 *
 * From <person> <date> <garbage>
 *
 * Where <person> is the address of the sender, being an ordinary
 * string with no white space imbedded in it, and <date> is the date of
 * posting, in ctime(3C) format.
 *
 * This would, on the face of it, seem valid. I (Bernd) have yet to find a
 * unix mailbox header which doesn't follow this format.
 *
 * From: Bernd Wechner (bernd@bhpcpd.kembla.oz.au)
 * Obfuscated by: KFS (as usual)
 */

#define MAX_FIELDS 10

static char legal_day[]         = "SunMonTueWedThuFriSat";
static char legal_month[]       = "JanFebMarAprMayJunJulAugSepOctNovDec";
static int  legal_numbers[]     = { 1, 31, 0, 23, 0, 59, 0, 60, 1969, 2199 };

int is_mail_from_line(line)
  char *line;     /* Line of text to be checked */
{
     char *fields[MAX_FIELDS];
     char *sender_tail;
     register char *lp, **fp;
     register int n, i;
     
     if (strncmp(line, "From ", 5)) return -100;
     
     lp = line + 5;
     /* sender day mon dd hh:mm:ss year */
     for (n = 0, fp = fields; n < MAX_FIELDS; n++) {
	  while (*lp && *lp != '\n' && isascii(*lp) && isspace(*lp)) lp++;
	  if (*lp == '\0' || *lp == '\n') break;
	  *fp++ = lp;
	  while (*lp && isascii(*lp) && !isspace(*lp))
	       if (*lp++ == ':' && (n == 4 || n == 5)) break;
	  if (n == 0) sender_tail = lp;
     }
     
     if (n < 8) return -200-n;
     
     fp = fields;
     
     if (n > 8 && !isdigit(fp[7][0])) fp[7] = fp[8]; /* ... TZ year */
     if (n > 9 && !isdigit(fp[7][0])) fp[7] = fp[9]; /* ... TZ DST year */
     
     fp++;
     for (i = 0; i < 21; i += 3)
	  if (strncmp(*fp, &legal_day[i], 3) == 0) break;
     if (i == 21) return -1;
     
     fp++;
     for (i = 0; i < 36; i += 3)
	  if (strncmp(*fp, &legal_month[i], 3) == 0) break;
     if (i == 36) return -2;
     
     for (i = 0; i < 10; i += 2) {
	  lp = *++fp;
	  if (!isdigit(*lp)) return -20-i;
	  n = atoi(lp);
	  if (n < legal_numbers[i] || legal_numbers[i+1] < n) return -10-i;
     }
     return 0;
}


Splittype
is_multipartfile(line)
  char *line;
{
     int success, i;
     int numitems = GDCgetNumFileSep(Config);

     success = is_mail_from_line(line);
     if (success == 0)
	  return(SPLIT_MAIL);

     for (i=0; i< numitems; i++) {
	  re_comp(GDCgetFileSep(Config, i));
	  if (re_exec(line))
	       return(i);
     }
     
     return(SPLIT_UNKNOWN);
}


#define NO_SUBJECT "<no subject>"

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    foundtitle = 0;
     char       *p;
     Splittype  septype = SPLIT_UNKNOWN;

     Debug("process_mailfile %s\n",Mailfname);

     Mailfile = rfopen(Mailfname, "r");

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

     septype = is_multipartfile(Zeline);
     Bytecount += strlen(Zeline);

     /** Read through the file, finding sections **/
     while (fgets(Zeline, MAXLINE, Mailfile) != NULL) {
	  if (!foundtitle) {
	       if (septype==SPLIT_MAIL) {
		    if (strncmp(Zeline,"Subject: ",9)==0) {
			 foundtitle = TRUE;
			 /* trim out the white space.. */
			 p = Zeline + 8;
			 while ((*p == ' ')||(*p == '\t'))
			      p++;
			 if (*p == '\n')
			      strcpy(Title, NO_SUBJECT);
			 else
			      strcpy(Title,p);
			 
			 ZapCRLF(Title);
			 Debug("Found title %s\n", Title);
		    } else if (strcmp(Zeline, "\n")==0) {
			 foundtitle = TRUE;
			 strcpy(Title, NO_SUBJECT);
			 Debugmsg("No subject found - using default\n");
		    }
	       } else {
		    /** Not SPLIT_MAIL **/
		    strcpy(Title, Zeline);
		    ZapCRLF(Title);
		    foundtitle = TRUE;
	       }
	  }
	  else if (is_multipartfile(Zeline) == septype) {
	       Endbyte = Bytecount;
	       foundtitle = FALSE;

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

}

#endif



/* 
 * Return the basename of a filename string, i.e. everything
 * after the last "/" character. 
 */

char *mtm_basename(string)
  char *string;
{
     static   char *buff;
#ifndef VMS_SERVER
     buff = string + strlen(string); /* start at last char */
     while (*buff != '/' && buff > string)
	  buff--;
     return( (char *) (*buff == '/'? ++buff : buff));
#else
/* 
 * VMS users return the basename of a filename string, as everything
 * after the last "]" character, or ":" if no "]", or as the input
 * string if neither.
 */

    if(buff = strchr(string,']'))
	return(++buff);
    else
	if (buff = strchr(string,':'))
	    return(++buff);
	else
	    return(string);
#endif
}



/*
 * Cache timeout value.
 *   If cache is less than secs seconds old, it's ok.
 *   If cache is newest, it's ok, otherwise it must be rebuilt.
 * 
 */

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

     time(&now);
     
     Debug("Cache now: %d, ", now);
     Debug("cache file: %d\n", 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;
{
     static STATSTR buf;
     int result;

     result = rstat(path, &buf);

     if (result != 0)
	  return(-1);
     
     if (S_ISDIR(buf.st_mode)) {
#ifndef VMS_SERVER
	  char *cp = fixfile(path);
	  if (! access(cp, F_OK))
	       return(1);
	  else
	       return(-1);
#else
	  return(1);
#endif
     }
     else if (S_ISREG(buf.st_mode))
	  return(0);
     else
	  return(-1);
}

/*
 * This function sets the process title given by ps on a lot of BSDish
 * systems
 */

#ifdef __STDC__
void
ServerSetArgv(const char *fmt, ...)
#else /* !__STDC__ */
void
ServerSetArgv(fmt, va_alist)
  char *fmt;
va_dcl
#endif /* __STDC__ */
{
#ifdef VMS_SERVER
#   define SETPROCTITLE
#endif
#if defined(SETPROCTITLE) && !defined(__sgi) && !defined(_AUX_SOURCE) && !defined(sgi) && !defined(USG)

     va_list args;

     register char *p;
     register int i;
     char buf[MAXLINE];
#ifdef VMS_SERVER
     char var[60];
#endif



     Argv[1] = NULL;

# ifdef __STDC__
     va_start(args, fmt);
# else /* !__STDC__ */
     va_start(args);
# endif /* __STDC__ */
     (void) vsprintf(buf, fmt, args);
     
     va_end(args);

#ifdef VMS_SERVER
     Debug("ServerSetArgv: %s\n", buf);
#endif
     
     /* make ps print "(gopherd)" */
     p = Argv[0];
#ifndef VMS_SERVER
     *p++ = '-';
#endif
     
     i = strlen(buf);
     if (i > LastArgv - p - 2)
     {
	  i = LastArgv - p - 2;
	  buf[i] = '\0';
     }
     (void) strcpy(p, buf);
     p += i;
#ifndef VMS_SERVER
     while (p < LastArgv)
	  *p++ = ' ';
#else
     *p = '\0';
     sprintf(var,"GOPHERD_STATE_%x", getpid());
     VMS$SetEnv("LNM$SYSTEM_TABLE",var, buf);     
#endif
#endif /* SETPROCTITLE */
     ;
}

FILE*
Gpopen(sockfd, cmd, rw)
  int  sockfd;
  char *cmd;
  char *rw;
{
     int inquote = 0;
     int insquote = 0;
     int i;
     FILE *theproc;

     /** Strip out the naughty bits..  **/
     for (i=0; cmd[i] != '\0'; i++) {
	  switch (cmd[i]) {
	  case '"':
	       if (!insquote)
		    inquote = 1-inquote;
	       break;
	       
	  case '\'':
	       if (!inquote)
		    insquote = 1-insquote;
	       break;

	  case '&':
	  case '|':
#ifndef VMS_SERVER
	  case ';':
	  case '=':
#endif
	  case '?':
	  case '>':
	  case '(':
	  case ')':
	  case '{':
	  case '}':
#ifndef VMS_SERVER
	  case '[':
	  case ']':
#endif
	  case '^':
	       /*** Stuff that's okay if quoted.. ***/
#ifdef VMS_SERVER
		/*** Additional stuf that's ok if quoted under OpenVMS	***/
	  case '!':
	  case '\\':
	  case '\n':
#endif

	       if (!inquote && !insquote) {
#ifndef VMS_SERVER
		    LOGGopher(sockfd, "Possible Security Violation '%s'", cmd);
#else
		    LOGGopher(sockfd, 
			    "Possible Security Violation @'%.4s<%c>%.4s'", 
						cmd+i-((i>=4)?4:i), cmd[i],
							cmd+i+
							(i==strlen(cmd)?0:1));
	       LOGGopher(sockfd, "unquoted in \"%s\"", cmd);
#endif
		    return(NULL);
	       }
	       
	       break;

#ifndef VMS_SERVER
	  case '!':
	  case '\\':
	  case '`':
	  case '\n':
	  case '$':
#else
    /*** Hmm... OpenVMS doesn't have so many dangerous escape codes, eh? ***/
#endif
	       /*** Stuff that shouldn't be in there at all! **/

#ifndef VMS_SERVER
	       LOGGopher(sockfd, "Possible Security Violation '%s'", cmd);
#else
	       LOGGopher(sockfd,
			"Possible Security Violation @'%.4s<%c>%.4s'", 
						cmd+i-((i>=4)?4:i), cmd[i],
							cmd+i+
							(i==strlen(cmd)?0:1));
	       LOGGopher(sockfd, "in \"%s\"", cmd);
#endif
	       return(NULL);

	       break;
	  }
     }

     theproc = popen(cmd, rw);
#ifndef VMS_SERVER
     if (theproc != NULL)
	  setvbuf(theproc, NULL, _IOLBF, 0);
#endif

     return(theproc);
}

/*
 * Set an environment variable 
 */

#ifndef VMS_SERVER
void
SetEnvironmentVariable(variable, value)
#else
void
VMS$SetEnv(table, variable, value)
  char *table;
#endif
  char *variable, *value;
{
     char *tmpputenv;
     
     if (variable == NULL)
	  return;

     if (value == NULL)
	  value = "";

#ifndef VMS_SERVER
     tmpputenv = (char*) malloc(strlen(variable) + strlen(value) + 2);
     sprintf(tmpputenv, "%s=%s", variable, value);
     putenv(tmpputenv);
#else
     if (table == NULL)
	table = "";
     tmpputenv = (char*) malloc(strlen(table)+ 1 + strlen(variable) + 
						    strlen(value) + 2);
     sprintf(tmpputenv, "%s%s%s=%s", table, strlen(table)>0?":":"",
						    variable, value);
     putenv(tmpputenv);
     free(tmpputenv);
#endif
}

#ifdef VMS_SERVER
/*
 *	Continuation on .LINKS, lookasides, and the like would really be nice
 *	so we'll add code to detect a trailing "-", and if the next record in
 *	the file starts with a space we'll concatenate the records up to a max.
 */
int
VMS$Continuation(char *buf, FILE *file, int max, char cont)
{
    int	    i, posit;
    char    *cp;

    while((buf[i=(strlen(buf)-1)]==cont) && (i>0)) {
	posit = ftell(file);
	cp=fgets(buf+i,max-i,file);
	if (cp==NULL || buf[i]!=' ') {
	    buf[i++] = cont;
	    buf[i] = '\0';
	    fseek(file,posit,0);
	    return;
	}
	ZapCRLF(buf);
    }
}

/*
 *  This routine validates a selector path as being a valid VMS file 
 *  specification.
 **/
char *
VMS$Validate_Filespec(char *path)
{
     struct FAB	 fab;
     struct NAM	 nam;
     static
        char expanded[256];    
     register    status;
     char *cp;

     for(cp = path; *cp; cp++) if (*cp == ' ') break;

     fab = cc$rms_fab;
     nam = cc$rms_nam;
     fab.fab$b_fac = FAB$M_GET;
     fab.fab$l_fop = FAB$V_NAM;
     fab.fab$l_nam = &nam;
     fab.fab$l_dna = GDCgetDatadir(Config);
     fab.fab$b_dns = strlen(fab.fab$l_dna);
     fab.fab$l_fna = path;
     fab.fab$b_fns = (cp - path);
     nam.nam$l_esa = expanded;
     nam.nam$b_ess = 255;
     nam.nam$b_nop = NAM$V_SYNCHK;
     expanded[0] = 0;
     if ((status = SYS$PARSE(&fab)) != RMS$_NORMAL)
	return(NULL);
     expanded[nam.nam$b_esl] = '\0';
     return(expanded);
}

/*
 *  Modification of Bruce Tanner's vms_system() function.
 *     F.Macrides (MACRIDES@SCI.WFEB.EDU) -- 08-Jul-1993
 *
 *  This routine permits a server started under Inetd/MULTINET_SERVER
 *   to spawn subprocesses with the DCL CLI.
 *
 *  The subprocess is created with LOGINOUT.EXE as its image, so that
 *   it has a DCL CLI, but it has F$MODE() .eqs. "OTHER" and does not
 *   execute SYS$MANAGER:SYLOGIN.COM (furthermore, MULTINET recommends
 *   that you explicitly direct an exit at the top of SYLOGIN.COM for
 *   "OTHER" processes).  To pass the subprocess logicals and foreign
 *   command definitions (most importantly, that for EGREP), you can
 *   define a command file to be executed before the execution of the
 *   gopher server's command, using any of these options:
 *       (1) Define the system logical "GOPHER_LOGIN" to point to the
 *            command file.
 *       (2) Set the "SpawnInit" field in the server's configuration
 *            file so that it points to the command file.
 *       (3) Define the program logical "LOGINCOM" in CONF.H so that
 *            it points to the command file instead of to the system
 *            logical.
 *
 *  The subprocess has its privileges set to only TMPMBX and NETMBX,
 *   but it will be owned by SYSTEM, which grants it privileges you
 *   can't totally restrict (e.g., due to ACL settings and rights
 *   identifiers for SYSTEM).  Therefore, if the server is not running
 *   from Inetd/MULTINET_SERVER, the function reroutes the call to the
 *   C library's system().
 *
 *  The function talks to the subprocess via sys$qiow()'s to a mailbox,
 *   and can hang if the subprocess crashes.  I therefore check that the
 *   subprocess is still alive, via a "throwaway" sys$getjpi() call, after
 *   the server's DCL command has been mailed, and before mailing a suicide
 *   command.  I haven't had any problems since adding this simple trick,
 *   but someday the function should be rewritten to check event flags.
 */

#include <ssdef.h>
#include <iodef.h>
#include <dvidef.h>
#include <prvdef.h>
#include <jpidef.h>
#define check(status) if ((status & 1) != 1) return (status)

int
VMS$system(char *command)
{
     char buf[256], mbx_name[20], *cp, username[12];
     long name_len;
     unsigned int pid;
     short chan;
     static int unit;
     int status, iosb[2], privs[2] = {PRV$M_NETMBX|PRV$M_TMPMBX, 0};
     struct itemlist3 {
         short buflen;
         short itmcode;
         int *bufadr;
         short *retadr;
     } itmlst[2] = {
         {4, DVI$_UNIT, (int *) &unit, 0},
         {0, 0, 0, 0}
       };
     struct {
	 short buffer_length;
	 short item_code;
	 char  *buffer_address;
	 long  return_length_address;
	 long  terminator[3];
     } itmlstj;

     $DESCRIPTOR(d_out, "NL:");
     $DESCRIPTOR(d_err, "NL:");
     $DESCRIPTOR(d_image, "sys$system:loginout.exe");
     struct dsc$descriptor_s
          d_input = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};

    /*
     *  If we're not under MULTINET_SERVER/Inetd, use system()
     */
     if (GDCgetInetdActive(Config)==FALSE)
	return(system(command));

    /*
     *  Create a mailbox for passing DCL commands to the subprocess.
     */
     status = sys$crembx(0, &chan, 0, 0, 0, 0, 0, 0);
     check(status);

    /*
     *  Identify the mailbox for the d_input descriptor.
     */
     status = sys$getdviw(0, chan, 0, itmlst, 0, 0, 0, 0);
     check(status);
     sprintf(mbx_name, "_MBA%d:", unit);
     d_input.dsc$w_length = (short) strlen(mbx_name);
     d_input.dsc$a_pointer = mbx_name;

    /*
     *  Create the subprocess with only TMPMBX and NETMBX privileges.
     */
     status = sys$creprc(&pid, &d_image, &d_input, &d_out, &d_err,
		&privs, 0, 0, 4, 0, 0, 0);
     check(status);

    /* 
     *  The subprocess doesn't execute SYLOGIN.COM, and it's F$MODE()
     *  is "OTHER", so pass it the EGREP foreign command, and other
     *  symbols and logicals you want the subprocess to have, via a
     *  command file.  But make sure the subprocess can execute the
     *  command file.
     */
     if (access(GDCgetSpawnInit(Config), 1) == 0) {
          sprintf(buf, "$ @%s", GDCgetSpawnInit(Config));
          status = sys$qiow(0, chan, IO$_WRITEVBLK, &iosb, 0, 0, buf,
                       strlen(buf), 0, 0, 0, 0);
          check(status);
     }

    /*
     *  Mail the server's DCL command to the subprocess.
     */
     status = sys$qiow(0, chan, IO$_WRITEVBLK, &iosb, 0, 0, command,
                       strlen(command), 0, 0, 0, 0);
     check(status);

    /*
     *  Wait a second, and use a non-mailbox service to see if the
     *  command caused the subprocess to crash (so we don't send it
     *  a suicide note and end up hanging ourselves on the mailbox).
     */
     sleep(1);
     itmlstj.buffer_length = 12;
     itmlstj.item_code = JPI$_USERNAME;
     itmlstj.buffer_address = username;
     itmlstj.return_length_address = (long) &name_len;
     itmlstj.terminator[0] = 0;
     itmlstj.terminator[1] = 0;
     itmlstj.terminator[2] = 0;
     name_len = 0;
     status = sys$getjpiw(0, &pid, 0, &itmlstj.buffer_length, &iosb[0], 0, 0);
     check(status);

    /*
     *  If the subprocess is still alive, mail it instructions to
     *  commit suicide (when it's done executing the DCL command).
     */
     sprintf(buf, "$ stop/id=%x", pid);
     status = sys$qiow(0, chan, IO$_WRITEVBLK, &iosb, 0, 0, buf,
                       strlen(buf), 0, 0, 0, 0);
     check(status);

    /*
     * Deassign the mailbox channel.
     */
     status = sys$dassgn(chan);
     check(status);

     return SS$_NORMAL;
}

/*
 *  This routine validates a selector path as being a valid VMS file 
 *  specification.
 **/
boolean
ArbitraryDevice(char *type, char *path)
{
    return(TRUE);
}

/*  Emulate BSD UNIX's re_comp() and re_exec() functions (note: Emulated 
    right down to their inability to be multi-threaded!)		    */

static char *re_pattern = NULL;

/*
 *  re_comp() accepts a potential patter string are compiles it into an
 *		internal format suitable for pattern matching.  It returns
 *		NULL if successful, a character string containing an error
 *		message if unsuccessful.  Specifying NULL or a zero-length
 *		string for the pattern causes re_comp() to return without
 *		changing the precompiled pattern.
 */
char *
re_comp(char *pattern)
{

    if (!pattern)	    return(NULL);
    if (strlen(pattern))    return(NULL);

    if (re_pattern)
	free(re_pattern);    
    if (!(re_pattern = (char *)malloc(strlen(pattern)+1)))
	return("Insufficient memory for pattern");
    strcpy(re_pattern, pattern);

    return(NULL);
}

/*
 *  re_exec() accepts a string and compares it to the most recently compiled
 *		pattern used by re_comp().  It returns 1 if the string
 *		matches the pattern, 0 if it does not match, and -1 if the
 *		pattern is not set up properly.
 */
int
re_exec(char *string)
{
    int	status;
    struct dsc$descriptor_s
          dsc$string = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
	  dsc$pattern = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};

    if (!re_pattern)
	return(-1);

    dsc$pattern.dsc$w_length = (short) strlen(re_pattern);
    dsc$pattern.dsc$a_pointer = re_pattern;
    dsc$string.dsc$w_length = (short) strlen(string);
    dsc$string.dsc$a_pointer = string;
    status = str$match_wild(dsc$string, dsc$pattern);
    return (((status & 1) != 1) ? 0 : 1);
}

/*	Disable all privileges except TMPMBX and NETMBX
**	------------------------------------------------
**
**      F.Macrides (MACRIDES@SCI.WFEB.EDU) -- 21-Feb-1994
**	(based on code from H.Flowers <flowers@narnia.memst.edu>)
**
**	Note that if an INETD server process is running with a system UIC
**	(like the SYSTEM account has), turning off SYSPRV will have little
**	effect due to SYSTEM rights identifiers, but we should turn off all
**	these other privs, and may as well turn off SYSPRV too.
*/
void
VMS$DisableAllPrivs(void)
{
    union prvdef prvadr;

    bzero((char *) &prvadr, sizeof(prvadr));
    prvadr.prv$v_cmkrnl = 1;      
    prvadr.prv$v_cmexec = 1;      
    prvadr.prv$v_sysnam = 1;      
    prvadr.prv$v_grpnam = 1;      
    prvadr.prv$v_allspool = 1;    
    prvadr.prv$v_detach = 1;      
    prvadr.prv$v_diagnose = 1;    
    prvadr.prv$v_log_io = 1;      
    prvadr.prv$v_group = 1;       
    prvadr.prv$v_noacnt = 1;      
    prvadr.prv$v_prmceb = 1;      
    prvadr.prv$v_prmmbx = 1;      
    prvadr.prv$v_pswapm = 1;      
    prvadr.prv$v_setpri = 1;      
    prvadr.prv$v_setprv = 1;      
    prvadr.prv$v_world = 1;       
    prvadr.prv$v_mount = 1;       
    prvadr.prv$v_oper = 1;        
    prvadr.prv$v_exquota = 1;     
    prvadr.prv$v_volpro = 1;      
    prvadr.prv$v_phy_io = 1;      
    prvadr.prv$v_bugchk = 1;      
    prvadr.prv$v_prmgbl = 1;      
    prvadr.prv$v_sysgbl = 1;      
    prvadr.prv$v_pfnmap = 1;      
    prvadr.prv$v_shmem = 1;       
    prvadr.prv$v_sysprv = 1;      
    prvadr.prv$v_bypass = 1;      
    prvadr.prv$v_syslck = 1;      
    prvadr.prv$v_share = 1;       
    prvadr.prv$v_upgrade = 1;     
    prvadr.prv$v_downgrade = 1;   
    prvadr.prv$v_grpprv = 1;      
    prvadr.prv$v_readall = 1;     
    prvadr.prv$v_security = 1;    
    if (SS$_NORMAL != (vaxc$errno = SYS$SETPRV (DEBUG, &prvadr, 0, 0))) {
	LOGGopher(-1,"Can't discard PRIVS, %s", STRerror(vaxc$errno));
    }
}



/*  ROUTINE							WWW_to_VMS()
 *  Replace slash-separated pathspecs with VMS pathspecs.
 *     F.Macrides (MACRIDES@SCI.WFEB.EDU) -- 08-Sep-1994
 *
 *  This routine accepts pathspecs which begin with a slash, and replaces
 *   all slashes to create a VMS pathspec.  If a GType for a directory is
 *   indicated (A_DIRECTORY or '1') and there is no terminal slash, it will
 *   append one before performing the conversion.
 *
 *  The sole purpose of this routine is to allow slashes to be substituted
 *   for ':', ":[", '[' or ']' in the *pathspec* portions of selectors for
 *   gopher URL's, so that they do not need to be escaped to hex notation.
 *   It does *not* do a SHELL$ or POSIX conversion from Unix pathspecs, nor
 *   emulate the pathspec rules for http URL's.  All VMSGopherServer rules
 *   with respect to DataDirectory defaulting, and uses of wildcards and/or
 *   ellipses still apply.  In the pathspec fields of URL's, you simply
 *   replace the above three characters and/or ":[" string with slashes,
 *   and add a lead slash if not already present via the substitutions.
 *
 *  You also can substituTe the dots between subdirectories with slashes,
 *   but the dots in ellipses are associated with the preceding directory
 *   string and should *not* be replaced.  Also, you still must escape
 *   the pair or colons (':' == %3a) which serve as ARG delimiters in exec
 *   (A_EVENT, 'e') and search (A_INDEX, '7') selectors, and any spaces
 *   (' ' == %20) in the selector.  E.g.,
 *      7[foo...]*.txt              can be replaced by:
 *      7/foo.../*.txt
 *         and
 *	7:nosort:[foo...]*.txt      by:
 *    	7%3anosort%3a/foo.../*.txt
 *	   and 
 *      1gopher_root4:[neat.stuff.for.you.to read]   by:
 *      1/gopher_root4/neat/stuff/for/you/to/read/   or:
 *      1/gopher_root4/neat.stuff.for.you.to.read/
 *
 *  Here's a full example of a URL for:
 *                Type=7
 *                Path=7[_shell]search.shell gopher_rooti:[foo]foodoc
 *
 *   On the command line it would be:
 *      gopher://host/77/_shell/search.shell%20/gopher_rooti/foo/foodoc
 *
 *   In a foo.html would be:
 *  <A HREF="gopher://host/77/_shell/search.shell%20/gopher_rooti/foo/foodoc";
 *  >Search the foo database</A>.
 */

char *
VMS$WWW_to_VMS(char *WWWname, char GType)
{
    static char vmsname[256];
    char *filename=NULL;	/* Working copy of pathspec */
    char *second;		/* 2nd slash */
    char *last;			/* last slash */
    
    if(!WWWname)		/* Make sure we got a pathspec */
	return(NULL);

    filename  = (char*)malloc(strlen(WWWname)+4);
    strcpy(filename, WWWname);	/* If a directory, ensure a terminal slash */
    if(GType == A_DIRECTORY && filename[strlen(filename)-1] != '/')
        strcat(filename, "/");
    
    second = strchr(filename+1, '/');		/* 2nd slash */
    last = strrchr(filename, '/');		/* last slash */
        
    if (!second) {				/* Only one slash */
	sprintf(vmsname, "%s", filename+1);
    } else if(second==last) {			/* Exactly two slashes */
	*second = 0;
	sprintf(vmsname, "[%s]%s", filename+1, second+1);
	*second = '/';
    } else { 					/* More than two slashes */
	char * p;
	*second = 0;		/* Split disk or dir from rest */
	*last = 0;		/* Split dir from filename */
	if(strncasecmp(filename+1, GDCgetDatadir(Config),
			   strcspn(GDCgetDatadir(Config),":")))
	    sprintf(vmsname, "[%s.%s]%s", filename+1, second+1, last+1);
	else
	    sprintf(vmsname, "%s:[%s]%s", filename+1, second+1, last+1);
	*second = *last = '/';	/* restore filename */
	for (p=strchr(vmsname, '['); *p!=']'; p++)
	    if (*p=='/') *p='.';	/* Convert dir sep.  to dots */
    }
    free(filename);
    return vmsname;
}




/*
    Given a format string containing %xx tokens, substitute from the other
    parameters the %xx tokens according to their definition.  Return pointer
    to the static buffer wher we've done this substitution.
*/

int getload();

char *
VMS$FormatTokens(char *fmt, char *Path, off_t *Size, time_t *TStamp, int Port, 
			    char *Host, int Type, char *Admin, CMDobj *Cmd)
{
#define	TKN_SZ	0	/* SiZe			    */
#define TKN_DT  1	/* DaTe	only		    */
#define TKN_FN  2	/* FileName		    */
#define TKN_PA  3	/* directory PAth only	    */
#define TKN_DV  4	/* DeVice only		    */
#define TKN_FQ	5	/* Full-Qualified filespec  */
#define	TKN_TS	6	/* TimeStamp		    */
#define TKN_TI	7	/* TIme only		    */
#define TKN_PT	8	/* PorT			    */
#define TKN_HO	9	/* HOst name		    */
#define TKN_LD	10	/* system LoadD		    */
#define	TKN_AM	11	/* effective AdMinistrator  */
#define	TKN_RQ	12	/* CMDgetData(Cmd) ReQuest   */
#define	TKN_AT	13	/* Abortoutput() Title	    */
    static char	*codes = "%sz%dt%fn%pa%dv%fq%ts%ti%pt%ho%ld%am%rq%at";
/*                        |  |  |  |  |  |  |  |  |  |  |  |  |  |
                          |  |  |  |  |  |  |  |  |  |  |  |  |  Abortoutput 
                          |  |  |  |  |  |  |  |  |  |  |  |  |      title
                          |  |  |  |  |  |  |  |  |  |  |  |  CMDgetData(Cmd)
                          |  |  |  |  |  |  |  |  |  |  |  Administrator
                          |  |  |  |  |  |  |  |  |  |  systemload
                          |  |  |  |  |  |  |  |  |  host
                          |  |  |  |  |  |  |  |  port
                          |  |  |  |  |  |  |  time only
                          |  |  |  |  |  |  timestamp
                          |  |  |  |  |  fully-qualified filespec
                          |  |  |  |  device only
                          |  |  |  directory path only
                          |  |  filename only
                          |  date only
                          size 
*/
    int		l;
    extern
       double	sysload;
    static
       String  	*buf = NULL;
    char	*ThisYear;
    char	work[60];
    time_t	now;
    struct tm	*local;
    char	code[4];
    char	*ft;
    char	*ftx;
    char	*cp;
    int 	fd;
    boolean	stats_ok = TRUE;
    char	date[26];
#define	MMM	date+4
#define DD	date+8
#define HHMMSS	date+11
#define	YYYY	date+20

    switch(Type) {
    case A_DIRECTORY:		/*** It's a directory ***/
    case A_INDEX:		/*** It's an index ***/
    case A_FTP:			/*** ftp link ***/
    case A_EXEC:		/*** exec link ***/
    case A_HTML:		/*** www link ***/
    case A_WAIS:		/*** wais or whois link ***/
		stats_ok = FALSE;
    default:    stats_ok = TRUE;
    }

    if (!strchr(fmt,'%'))
	return(fmt);

    if (!stats_ok) {
	Size = NULL;
	TStamp = NULL;
    }

    if (TStamp) {
	strcpy(date,ctime(TStamp));
	date[3] = date[7]= date[10]= date[19] = date[24]= '\0';
	if (date[8]==' ')
	    date[8] = '0';
	date[0] = '\0';
    }
    if (!buf)
	buf = STRnew();
    else
	STRset(buf,"");

    for (l=0,code[3]='\0'; *fmt!='\0';) {
	fd=strcspn(fmt,"%");
	if (fd) {
	    STRncat(buf,fmt,fd);
	    fmt += fd;
	    l += fd;
	}
	if (*fmt=='%') {
	    strncpy(code,fmt,3);
	    if (NULL == (ftx=strstr(codes,code))) {
		STRncat(buf,fmt++,1);
		l++;
		continue;
	    }
	    fmt += 3;
	    switch((ftx-codes)/3) {
/*%sz*/case TKN_SZ: if (!Size)	break;
		    work[sprintf(work, (*Size >= 1000)?"%uKB":"%u Bytes",
					(*Size >= 1000)?((*Size+1023)/1024)
					     :*Size)] = '\0';
		    STRcat(buf, work);
		    break;
/*%fn*/case TKN_FN: if (!Path)	break;
		    if (cp = strchr(Path,']'))
			STRcat(buf, cp+1);
		    break;
/*%pa*/case TKN_PA: if (!Path)	break;
		    if (cp = strchr(Path,'['))
			if (fd=strcspn(cp,"]"))
			    STRncat(buf, cp, fd+1);
		    break;
/*%dv*/case TKN_DV: if (!Path)	break;
		    if (fd=strcspn(Path,":"))
			STRncat(buf, Path, fd);
		    break;
/*%fq*/case TKN_FQ: if (!Path)	break;
		    STRcat(buf, Path);
		    break;
/*%ts*/case TKN_TS: if (!TStamp) break;
		    now = time(&now);
		    local = localtime(&now);
		    ThisYear = asctime(local) + 20;
		    if (strncmp(YYYY,ThisYear,4)==0) {
			work[sprintf(work,"%s-%s %s", DD, MMM, HHMMSS)]='\0';
			STRcat(buf, work);
			break;
		    }
/*%dt*/case TKN_DT: if (!TStamp)  break;
		    work[sprintf(work,"%s-%s-%s", DD, MMM, YYYY)] = '\0';
		    STRcat(buf, work);
		    break;
/*%ti*/case TKN_TI: if (!TStamp)  break;
		    STRcat(buf, HHMMSS);
		    break;
/*%pt*/case TKN_PT: if (Port==-1)
			work[sprintf(work,"%d", GDCgetPort(Config))] = '\0';
		    else
			work[sprintf(work,"%d",Port)] = '\0';
		    STRcat(buf, work);
		    break;
/*%ho*/case TKN_HO: if (!Host)
		    	STRcat(buf,GDCgetHostname(Config));
		    else
			STRcat(buf, Host);
		    break;
/*%ld*/case TKN_LD: getload();
		    work[sprintf(work, "%f",sysload)] = '\0';
		    STRcat(buf, work);
		    break;
/*%am*/case TKN_AM: if (!Admin)
			STRcat(buf, GDCgetAdminEmail(Config));
		    else 
			STRcat(buf, Admin);
		    break;
/*%rq*/case TKN_RQ: if (!Cmd)	break;
		    STRcat(buf, CurrentPeerName);
		    STRcat(buf, ": <");
		    STRcat(buf, CMDgetData(Cmd));
		    STRcat(buf, ">");
		    break;
/*%at*/case TKN_AT: if (!AbortString)	break;
		     STRcat(buf, AbortString);
		     break;
	    }
	}
    }
    return(STRget(buf));
}



void
str_tolower(char *string)
{
    char *c2;

    for(c2 = string; *c2; c2++)
	    *c2 = _tolower(*c2);

}
#endif
.
Response: text/plain
Original URLgopher://bitreich.org/0/gopher2007/2007-gopher-mirror/gop...
Content-Typetext/plain; charset=utf-8