/********************************************************************
* 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 ([email protected])
* 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 ([email protected]) -- 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 ([email protected]) -- 21-Feb-1994
**      (based on code from H.Flowers <[email protected]>)
**
**      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 ([email protected]) -- 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