/********************************************************************
* wilkinson
* 3.43VMS
* 1995/11/24 13:00
* gopher_root1:[gopher.g2.vms2_13.gopherd]ftp.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: ftp.c
* Routines to translate gopher protocol to ftp protocol.
*********************************************************************
* Revision History:
* ftp.c,v
* Revision 3.43VMS 1995/11/24 13:00    wilkinson
* Fix to deal with Murkworks Novell FTP servers
* Use new FTPsetErrored() macro and back out of errors instead of dying with
*  gopherd_exit().
*
* Revision 3.43  1995/02/20  23:36:50  lindner
* Fix for searches
*
* Revision 3.42  1995/02/06  21:24:42  lindner
* Fix for pedantic compilers
*
* Revision 3.41  1994/11/18  21:41:14  lindner
* Remove unused variable, memory leak
*
* Revision 3.40  1994/11/16  18:53:16  lindner
* Eliminate extra hostname lookup in ftp-gw
*
* Revision 3.39  1994/10/13  05:17:47  lindner
* Compiler complaint fixes
*
* Revision 3.38  1994/08/01  21:59:37  lindner
* Less strict OS/2 ftp gw
*
* Revision 3.37  1994/07/31  04:56:49  lindner
* Mondo new stuff for ftp gateway
*
* Revision 3.36  1994/07/21  15:45:06  lindner
* Gopher+ FTP gateway (modified from patch from Brian Coan)
*
* Revision 3.35  1994/05/24  06:52:30  lindner
* Fix for spinning ftp processes from R.S. Jones
*
* Revision 3.34  1994/05/18  04:28:21  lindner
* Fixes for ftp from Jonzy
*
* Revision 3.33  1994/04/25  20:49:05  lindner
* Fix for debug code
*
* Revision 3.32  1994/04/07  17:27:59  lindner
* Recognize more Unix ftp sites as Unix
*
* Revision 3.31  1994/03/31  21:12:34  lindner
* FTP jumbo patch, fix for skipping permissions bits with large file sizes, Prettier continuation lines
*
* Revision 3.30  1994/03/17  21:58:16  lindner
* Fix for bad ftp servers
*
* Revision 3.29  1994/03/17  21:13:53  lindner
* Fix for pid load limiting
*
* Revision 3.28  1994/03/08  20:03:44  lindner
* Fix for aix function strangeness
*
* Revision 3.27  1994/03/08  15:55:28  lindner
* gcc -Wall fixes
*
* Revision 3.26  1994/03/08  03:56:33  lindner
* Fix for return types
*
* Revision 3.25  1994/02/20  16:51:24  lindner
* Add capability to compile out ftp routines
*
* Revision 3.24  1994/01/25  05:31:06  lindner
* Skip . and .. file in ftp listing..
*
* Revision 3.23  1994/01/20  06:40:38  lindner
* Fix for incorrect number of parameters to FTPerrorMessage
*
* Revision 3.22  1993/12/30  04:48:45  lindner
* mods for Plan 9 ftp servers
*
* Revision 3.21  1993/12/30  04:07:14  lindner
* Additional fix for VM servers
*
* Revision 3.20  1993/12/16  11:34:39  lindner
* Fixes to work with VM ftp servers
*
* Revision 3.19  1993/12/09  20:46:53  lindner
* More robust FTP Implementation
*
* Revision 3.18  1993/11/29  01:04:01  lindner
* Fix for vms version numbers, don't strip any numbers..
*
* Revision 3.17  1993/09/27  20:05:13  lindner
* Fix for broken Unix list
*
* Revision 3.16  1993/09/20  16:52:16  lindner
* Fix for big ftp directories with lots of links
*
* Revision 3.15  1993/09/18  03:25:18  lindner
* Fix for Mac FTPd and ftp gateway
*
* Revision 3.14  1993/09/18  02:21:58  lindner
* Fix for Novell ftp servers
*
* Revision 3.13  1993/09/17  14:47:42  lindner
* Totally new ftp code, eliminates tmp files
*
* Revision 3.12  1993/08/23  21:43:21  lindner
* Fix for bug with symlinks
*
* Revision 3.11  1993/08/23  20:51:48  lindner
* Multiple fixes from Matti Aarnio
*
* Revision 3.10  1993/08/11  14:41:48  lindner
* Fix for error logging and uninitialized gs
*
* Revision 3.9  1993/08/11  14:08:39  lindner
* Fix for hanging ftp gateway connections
*
* Revision 3.8  1993/08/03  20:43:54  lindner
* Fix for sites that have @ -> for symbolic links
*
* Revision 3.7  1993/08/03  06:40:11  lindner
* none
*
* Revision 3.6  1993/08/03  06:14:12  lindner
* Fix for extra slashes
*
* Revision 3.5  1993/07/30  19:21:03  lindner
* Removed debug stuff, fix for extra slashes
*
* Revision 3.4  1993/07/30  18:38:59  lindner
* Move 3.3.1 to main trunk
*
* Revision 3.3.1.7  1993/07/29  21:42:21  lindner
* Fixes for Symbolic links, plus removed excess variables
*
* Revision 3.3.1.6  1993/07/27  05:27:42  lindner
* Mondo Debug overhaul from Mitra
*
* Revision 3.3.1.5  1993/07/26  17:18:55  lindner
* Removed extraneous abort printf
*
* Revision 3.3.1.4  1993/07/26  15:34:21  lindner
* Use tmpnam() and bzero(), plus ugly fixes..
*
* Revision 3.3.1.3  1993/07/23  03:12:29  lindner
* Small fix for getreply, reformatting..
*
* Revision 3.3.1.2  1993/07/07  19:39:48  lindner
* Much prettification, unproto-ed, and use Sockets.c routines
*
* Revision 3.3.1.1  1993/06/22  20:53:21  lindner
* Bob's ftp stuff
*
* Revision 3.3  1993/04/15  04:48:09  lindner
* Debug code from Mitra
*
* Revision 3.2  1993/03/24  20:15:06  lindner
* FTP gateway now gets filetypes from gopherd.conf
*
* Revision 3.1.1.1  1993/02/11  18:02:51  lindner
* Gopher+1.2beta release
*
*********************************************************************/


/* -------------------------------------------------
*     g2fd.c          Gopher to FTP gateway daemon.
*     Version 0.3 Hacked up: April 1992.  Farhad Anklesaria.
*     Based on a Perl story by John Ladwig.
*     Based on a Perl story by Farhad Anklesaria.
*
*      Modified by Greg Smith, Bucknell University, 24 Nov 1992
*      to handle multiline status replies.
*
---------------------------------------------------- */

#ifndef NO_FTP

#include "gopherd.h"
#include "ftp.h"
#include <signal.h>

#include <stdio.h>
#include "ext.h"
#include "Malloc.h"
#include "Sockets.h"
#include "command.h"

#include "Debug.h"

#define SLEN 256


/** Global socket value **/
static int Gsockfd = -1;

/** Global Gplus value **/
static int GFTPplus;

/** track whether we sent a Gplus header already **/
static int SentGPHeader;

boolean GETDIR = FALSE;

/*** Some forward declarations ***/
boolean NotText();
boolean IsBinaryType();
void    Cleanup();
void    FailErr();
char    *WalkDirs();
boolean FTPconnect();

FTP *
FTPnew(host)
 char *host;
{
    FTP *temp;

    if (host == NULL || *host == '\0') {
         Debugmsg("FTPnew, bad param..\n");
         return(NULL);
    }

    temp = (FTP*)malloc(sizeof(FTP));

    temp->control_sock = -1;
    temp->data_sock    = -1;
    temp->mode         = 'A';
    temp->Ftptype      = FTP_UNKNOWN;
#ifdef VMS_SERVER
    temp->error        = FALSE;
#endif

    if (FTPconnect(temp, host))
         return(temp);
    else {
         Debug("FTPnew: FTPconnect failed to %s\n",host);
         free(temp);
         return(NULL);
    }
}


/*
* Close connection, destroy data structures..
*/

void
FTPdestroy(ftp)
 FTP *ftp;
{
    FTPcloseData(ftp);

    if (FTPgetControl(ftp) != -1) {
         FTPbyebye(ftp);
         close(FTPgetControl(ftp));
    }

    free(ftp);
}


/*
 Establish connection, validate DNS name,
 connect and return control pointer
 Error returns ErrSocket as defined in Socket.h
*/

boolean
FTPconnect(ftp, host)
 FTP  *ftp;
 char *host;
{
    int newcontrol;
    char message[256];

    /** Get ready for some cleanup action **/
    signal(SIGPIPE, FTPcleanup);
    signal(SIGINT, FTPcleanup);
    signal(SIGALRM, FTPcleanup);

    newcontrol = SOCKconnect(host,21);

    if (newcontrol < 0) {
                if (GFTPplus && (SentGPHeader == FALSE)) {
                        GSsendHeader(Gsockfd, -1);
                        SentGPHeader = TRUE;
                }
                sprintf (message, "failed to connect to %s %s\n",
                                 host, sys_err_str());
                FTPerrorMessage(ftp, message);
                return(FALSE);
        }

    FTPsetControl(ftp,newcontrol);

    if (FTPgetReply(ftp, 299, message, sizeof(message)) <0)
         return(FALSE);

    if (strcasestr(message, "Murkworks"))
         FTPsetType(ftp, FTP_UNKNOWN);     /* force FTPfindType() call */
    else if (strcasestr(message, "NetWare"))
         FTPsetType(ftp, FTP_NOVELL);
    else if (strcasestr(message, "SunOS"))
         FTPsetType(ftp, FTP_UNIX);
    else if (strcasestr(message, "ultrix"))
         FTPsetType(ftp, FTP_UNIX);
    else if (strcasestr(message, "MultiNet FTP"))
         FTPsetType(ftp, FTP_VMS);
    else if (strcasestr(message, "Windows NT FTP"))
         FTPsetType(ftp, FTP_WINNT);
    else if (strcasestr(message, "Peter's Macintosh FTP daemon"))
         FTPsetType(ftp, FTP_MACOS);
    else if (strcasestr(message, "unix"))
         FTPsetType(ftp, FTP_UNIX);
    else if (strcasestr(message, "Version 4.129"))
         FTPsetType(ftp, FTP_UNIX);
    else if (strcasestr(message, "DG/UX"))
         FTPsetType(ftp, FTP_UNIX_L8);
    else if (strcasestr(message, "Version 4.105"))
         FTPsetType(ftp, FTP_MICRO_VAX);
    else if (strcasestr(message, "OS/2"))
         FTPsetType(ftp, FTP_OS2);


    FTPfindType(ftp);
#ifdef VMS_SERVER
    if (FTPisErrored(ftp))
       return(FALSE);
#endif

    return(TRUE);
}


char *
FTPpwd(ftp)
 FTP *ftp;
{
    static char pwd[256];
    char message[256];
    int ftpnum;

    FTPsend(ftp, "PWD");
    ftpnum = FTPgetReply(ftp, 999, message, sizeof(message));

    if (ftpnum != 257)
         return(NULL);

    sscanf(message,"%*[^\"]%*c%[^\"]%*s",pwd);

    return(pwd);
}


void
FTPfindType(ftp)
 FTP *ftp;
{
    int ftpnum;
    char message[256];

    if (FTPgetType(ftp) != FTP_UNKNOWN)
         return;

    FTPsend(ftp, "SYST");
    ftpnum = FTPgetReply(ftp, 999, message, sizeof(message));
#ifdef VMS_SERVER
    if (FTPisErrored(ftp))
       return;
#endif

    if (ftpnum == 215) {
         Debugmsg("Analyzing ftp syst output\n");
         if (strncmp(message+4, "MTS", 3) == 0 ||
             strncmp(message+4, "VMS", 3) == 0) {
              FTPsetType(ftp, FTP_VMS);
         }
         else if (strncmp(message+4, "UNIX Type: L8", 13)==0)
              FTPsetType(ftp, FTP_UNIX_L8);
         else if (strncmp(message+4, "UNIX", 4)==0 ||
                  strncmp(message+4, "Plan 9", 6)==0)
              FTPsetType(ftp, FTP_UNIX);
         else if (strncmp(message+4, "MACOS", 5)==0)
              FTPsetType(ftp, FTP_MACOS);
         else if (strncmp(message+4, "MAC OS", 5)==0)
              FTPsetType(ftp, FTP_MACOS);
         else if (strncmp(message+4, "AIX-PS/2", 8)==0)
              FTPsetType(ftp, FTP_UNIX_L8);
         else if (strncmp(message+4, "VM", 2)==0)
              FTPsetType(ftp, FTP_VM);
    }
}

/* Send a string across the control channel
  Returns true if an error, false otherwise
*/
boolean
FTPsend(ftp, command)
 FTP  *ftp;
 char *command;
{
    Debug("--> %s\n", command);

    if (writestring(FTPgetControl(ftp), command) <0)
         return(TRUE);

    if (writestring(FTPgetControl(ftp), "\r\n") <0)
         return(TRUE);

    return(FALSE);
}


/* Find the numeric value of the message */

int
FTPfindNum(ftp, message)
 FTP  *ftp;
 char *message;
{
    int ftpnum;

/*
    ftpnum = (message[0] - '0') * 100 +
             (message[1] - '0') * 10 +
             (message[2] - '0');
*/
        ftpnum = (int) strtol(message, (char **)NULL, 10);
    return(ftpnum);
}


boolean
FTPisContinuation(ftp, message)
 FTP  *ftp;
 char *message;
{
       if (message[0] == '\0')
               return TRUE;
       if (isdigit(message[0]) && isdigit(message[1]) &&
               isdigit(message[2]) && message[3] == '-')
               return TRUE;
       return (isspace(message[0]));
}


/*
* Get a reply from the FTP control channel.
*/

int
FTPgetReply(ftp, errorlevel, message, maxlen)
 FTP  *ftp;
 int  errorlevel;
 char *message;
 int  maxlen;
{
    int ftpnum;

    ftpnum = FTPgetReplyline(ftp, message, maxlen);

    if (ftpnum > errorlevel) {
         /*** Had an error ***/
         if (GFTPplus && (SentGPHeader == FALSE)) {
                 GSsendHeader(Gsockfd, -1);
                 SentGPHeader = TRUE;
         }
         LOGGopher(-1, message);
         if (FTPerrorMessage(ftp, message) >= 0)
          while (FTPisContinuation(ftp, message)) {
                if ((ftpnum = FTPgetReplyline(ftp, message, maxlen)) < 0)
                 break;
                if (FTPerrorMessage(ftp, message) < 0)
                 break;
          }
#ifndef VMS_SERVER
         FTPabort(ftp);
         gopherd_exit(-1);
#else
         FTPsetErrored(ftp,TRUE);
         return(-1000);
#endif
    } else if (ftpnum <0) {
         /** Really bad error **/
#ifndef VMS_SERVER
         FTPabort(ftp);
         gopherd_exit(-1);
#else
         FTPsetErrored(ftp,TRUE);
         return(-1000);
#endif
    }

    if (FTPisContinuation(ftp, message)) {
         if (GFTPplus && (SentGPHeader == FALSE)) {
                 GSsendHeader(Gsockfd, -1);
                 SentGPHeader = TRUE;
         }
         do {
                 if (FTPinfoMessage(ftp, message) < 0)
                       return(-1);
                 if ((ftpnum = FTPgetReplyline(ftp, message, maxlen)) < 0)
                       break;
     } while (FTPisContinuation(ftp, message));
    }
    return(ftpnum);
}

/*
* Gets a reply on the control socket.. fills message with what it
* got back and returns the integer value of the return code.
*/

int
FTPgetReplyline(ftp, message, maxlen)
 FTP  *ftp;
 char *message;
 int  maxlen;
{
    int ftpnum, i;

    for (i=0; i<maxlen; i++)
         message[i] = '\n';

    i = readline(FTPgetControl(ftp), message, maxlen);

    Debug("<-- %s\n",message);

    if (i <= 0)
         return(-1);

    ftpnum = FTPfindNum(ftp, message);

    return(ftpnum);
}


/*
* Execute a specific ftp command (an sprintf style thing)
*    replace %s with parameter
*    fail when above errorlevel
*/

int
FTPcommand(ftp, command, parameter, errorlevel)
 FTP  *ftp;
 char *command;
 char *parameter;
 int  errorlevel;
{
    char commandline[512];
    char message[256];

    sprintf(commandline, command, parameter);
    if (FTPsend(ftp, commandline))
         return(-1); /** Error condition **/

    return(FTPgetReply(ftp, errorlevel, message, sizeof(message)));
}


/*
* Send USER and PASS
*/

int
FTPlogin(ftp, username, password)
 FTP  *ftp;
 char *username;
 char *password;
{
    int result;

    /*** Send username ***/
    result = FTPcommand(ftp, "USER %s", username, 599);

    if (result >399 || result < 0) {
         FTPabort(ftp);
         return (-1);
    }
    if (result >300)
         FTPcommand(ftp, "PASS %s", password, 399);
#ifdef VMS_SERVER
    if (FTPisErrored(ftp)) {
       FTPabort(ftp);
       return(-1);
    }
#endif
    return (0);
}



void
FTPbyebye(ftp)
 FTP *ftp;
{
    FTPcommand(ftp, "QUIT", NULL, 399);
}


/** Open a data connection **/
void
FTPopenData(ftp, command, file, errorlevel)
 FTP *ftp;
 char *command;
 char *file;
 int errorlevel;
{
    struct sockaddr_in we;
    char   theline[512];
    int ftp_dataport, ftp_data;

    if ((ftp_dataport = SOCKlisten(&we)) < 0)
         return;

    sprintf(theline, "PORT %d,%d,%d,%d,%d,%d",
            (htonl(we.sin_addr.s_addr) >> 24) & 0xFF,
            (htonl(we.sin_addr.s_addr) >> 16) & 0xFF,
            (htonl(we.sin_addr.s_addr) >>  8) & 0xFF,
            (htonl(we.sin_addr.s_addr)      ) & 0xFF,
            (htons(we.sin_port)        >>  8) & 0xFF,
            (htons(we.sin_port)             ) & 0xFF);

    FTPsend(ftp, theline);
    FTPgetReply(ftp, 201, theline, sizeof(theline));
#ifdef VMS_SERVER
    if (FTPisErrored(ftp))
       return;
#endif

    FTPcommand(ftp, command, file, errorlevel);
#ifdef VMS_SERVER
    if (FTPisErrored(ftp))
       return;
#endif

    ftp_data = SOCKaccept(ftp_dataport, we);

    if (ftp_data < -1)
         return;

    FTPsetData(ftp, ftp_data);

}


void
FTPcloseData(ftp)
 FTP *ftp;
{
    if (FTPgetData(ftp) != -1)
         close(FTPgetData(ftp));
}

int
FTPread(ftp, buf, bufsize)
 FTP *ftp;
 char *buf;
 int bufsize;
{
    int len;

    len = read(FTPgetData(ftp), buf, bufsize);

    return(len);
}


/*--------------------------------*/
/* Used in xLateText below --------*/
boolean
NotText(buf)
 char * buf;
{
    int max;   char *c;

    if ((max = strlen(buf)) >= (BUFSIZ - 50)) max = BUFSIZ - 50;
    for (c = buf; c < (buf + max); c++) {
         if (*c > '~') return(TRUE);
    }
    return(FALSE);
}


static char *textnames[] =
{ "README", "READ.ME", "MIRROR.LOG", "WELCOME", "INDEX", ".TXT", 0, };

/*--------------------------------*/
/* return false if extension indicates text, true if binary */

boolean
IsBinaryType(path)
 char *path;
{
    char Gtype;
    Extobj *ext;
    char *slashp, **textn, *cp;

    if (GDCViewExtension(Config, path, &ext)) {
         Gtype = EXgetObjtype(ext);

         switch (Gtype) {
         case A_FILE:
         case A_MACHEX:
         case A_CSO:
         case A_INDEX:
         case A_TELNET:
         case A_TN3270:
              return FALSE;

/*        default:
              return TRUE;*/
         }
    }
    if ((slashp = strrchr(path, '/')) == NULL)
         slashp = path;
    else
         return(TRUE);

    for (textn = textnames; *textn; textn++)
         if (strcasestr(slashp, *textn))
              return FALSE;

    for (cp = slashp; *cp && isdigit(*cp); cp++)
         ;
    if (cp > slashp && *cp)
         for (textn = textnames; *textn; textn++)
              if (strcasecmp(slashp, *textn)==0)
                   return FALSE;
    return(TRUE);
}


/*--------------------------------*/
void
TrimEnd(bufptr)
 char *bufptr;
{
    int last;
    for (last = strlen(bufptr) - 1; isspace(bufptr[last]) ; bufptr[last--] = '\0')
         ;
}



int
Vaxinate(bufptr)
 char *bufptr;
{
    int last;
    char *cp;

    last = strlen(bufptr) - 1;

    /* strip off VMS version numbers */
    if (isdigit(bufptr[last]) && (cp=strrchr(bufptr, ';')) != NULL) {
         *cp = '\0';
         last = strlen(bufptr) - 1;
    }

    /* if bufptr ends in ".dir", it's a directory, replace ".dir" with "/" */
    if((last > 3) && ((strncmp(bufptr + last - 3, ".dir", 4) == 0) ||
                      (strncmp(bufptr + last - 3, ".DIR", 4) == 0)))
    {
         last -= 3;
         bufptr[last] = '/';
         bufptr[last+1]  = '\0';
         return(last);
    }

    /* for files, uppercase terminal .z's or _z's */
    if ((int)strlen(bufptr) > 1) {
         if (bufptr[last] == 'z' &&
            (bufptr[last-1] == '.' || bufptr[last-1] == '_'))
              bufptr[last] = 'Z';
    }

    return(last);
}

void
FTPchdir(ftp, path)
 FTP  *ftp;
 char *path;
{
    int result;
    char *ptr;

    if ((result = FTPcommand(ftp, "CWD %s", path, 599)) < 300)
         return;
    switch (result) {
    case 450:
    case 550:
    case 553:
         if (strcmp(path, "/") && (ptr = strrchr(path, '/'))) {
              if (ptr == path)
                   ptr++;
              *ptr = '\0';
              FTPchdir(ftp, path);
#ifdef VMS_SERVER
              if (FTPisErrored(ftp))
                   return;
#endif
         }
         /* else fall through */
    default:
#ifndef VMS_SERVER
         FTPabort(ftp);
         gopherd_exit(-1);
#else
         FTPsetErrored(ftp,TRUE);
         return;
#endif
         break;
    }
    return;
}


char *
FTPwalkchdir(ftp, path)
 FTP  *ftp;
 char *path;
{
    char *beg, *end, *cp;
    char vmspath[256], tmppath[256];
    int notascii;

    /* path looks like '/dir/dir.../dir/' now. Because Wollongong
     * wants "CWD dir/dir/dir" and Multinet wants "CWD dir.dir.dir"
     * so we do the CWD in a stepwise fashion. Oh well...
     */

    beg = path+1;
    for (end = beg, notascii = 0; (*end != '\0') && (*end != '/'); ++end)
         if (!isascii(*end) || *end == '?' || *end == '*')
              notascii++;

    while (*end != '\0')
    {
         bzero(vmspath, 256);
         strncpy(vmspath, beg, end-beg);

         if (notascii)
              for (cp = vmspath; *cp; cp++)
                   if (!isascii(*cp) || *cp == ' ')
                        *cp = '?';

         if (!notascii && strchr(vmspath, ' ')) {
              sprintf (tmppath, "\"%s\"", vmspath);
              FTPchdir(ftp, tmppath);
         } else
              FTPchdir(ftp, vmspath);
#ifdef VMS_SERVER
        if (FTPisErrored(ftp))
           return(beg);
#endif

         beg=end+1; /* Skip slash */

         for (end = beg, notascii = 0; (*end != '\0') && (*end != '/'); ++end)
              if (!isascii(*end) || *end == '?' || *end == '*')
                   notascii++;
    }

    return(beg);
}


/*--------------------------------*/

void
FTPcleanup()
{
    ;
}

int
FTPerrorMessage(ftp, message)
 FTP *ftp;
 char *message;
{
    char errmsg[256];
        char *cp;
        int writerr = 0;

    if (message == NULL || Gsockfd < 0)
         return(0);

    for (cp = message; *cp; cp++)
         if (*cp == '\t')
              *cp = ' ';

    ZapCRLF(message);

    sprintf(errmsg, "3%s\t\terror.host\t1\r\n", message);
    if (GFTPplus)
         writerr = writestring(Gsockfd, "+INFO: ");
    if (writerr || writestring(Gsockfd, errmsg)) {
         Debug("FTPerrorMessage gave errno %d", errno);
         Gsockfd = -1;
         return(-1);
    }
    return(0);
}

int
FTPinfoMessage(ftp, message)
 FTP *ftp;
 char *message;
{
    char errmsg[512];
    char *cp;
    int writerr = 0;

    if (message == NULL || Gsockfd < 0)
         return(0);

    for (cp = message; *cp; cp++)
         if (*cp == '\t')
              *cp = ' ';

    ZapCRLF(message);

    if (GETDIR) {
         if ((int)strlen(message) > 3 && message[3] == '-')
              sprintf(errmsg, "i%s\t\terror.host\t1\r\n", &message[4]);
         else
              sprintf(errmsg, "i%s\t\terror.host\t1\r\n", message);

         if (GFTPplus)
                writerr = writestring(Gsockfd, "+INFO: ");
         if (writerr || writestring(Gsockfd, errmsg)) {
                 Debug("FTPinfoMessage gave errno %d", errno);
                 Gsockfd = -1;
                 return(-1);
         }
         return(0);
    }
    return(0);

}

void
FTPabort(ftp)
 FTP *ftp;
{
    writestring(Gsockfd, ".\r\n");
    FTPdestroy(ftp);
}


/*
* Implement ftp--> gopher gateway
*/

int
GopherFTPgw(sockfd, ftpstr, cmd)
 int    sockfd;
 char   *ftpstr;
 CMDobj *cmd;
{
    FTP  *ftp;
    char *cp;
    char ftp_info_fname[256];
    char *ftphost, *ftpuser, ftppass[256], *ftppath;
    char buf[8192];
    int blen;
    char lastchar;
    static GopherObj   *Gopherstow = NULL;
    GopherObj          *gs;
    GopherDirObj       *gd;

    Gsockfd = sockfd;

    /* Find @ and parse out */
    cp = strchr(ftpstr, '@');
    if (cp == NULL)
         return(-1);

    *cp = '\0';

    ftppath = cp+1;
    ftphost = ftpstr;

    ftpuser = "anonymous";
    Gsockfd = sockfd;
    GFTPplus = CMDisGplus(cmd);
    SentGPHeader = FALSE;

    sprintf(ftppass, "-gopher@%s", Zehostname);
    if (Gopherstow == NULL)
         Gopherstow = GSnew();

    gs = Gopherstow;
    GSsetGplus(gs, TRUE);

    if (GFTPplus && GSgplusInited(gs) == FALSE)
         GSplusnew(gs);

    if (GFTPplus && *CMDgetCommand(cmd) == '!') {
         char *lastslash;
         char tmpstr[256];

         lastslash = strrchr(ftppath, '/');
         if (lastslash == NULL) {
#ifdef VMS_SERVER
              SetAbrtFile(RangeErr, NULL   /* AbortGS ??? */, KeepAbrtGS,
                                                                   NULL);
#endif
              Abortoutput(sockfd, ftppath);
              return(-1);
         }
         GSsetHost(gs, Zehostname);
         GSsetPort(gs, GopherPort);
         sprintf(tmpstr, "ftp:%s@%s", ftphost, ftppath);
         GSsetPath(gs, tmpstr);
         GSsetTitle(gs, tmpstr);

         if (*(lastslash+1) == '\0') {
              /** Item is a directory **/
              GSsetType(gs, '1');
              AddDefaultView(gs, 1, NULL);

              GSsendHeader(sockfd, -1);

              GSplustoNet(gs, sockfd, NULL, "");
              return(0);
         } else {
              *lastslash = '/';
              *(lastslash +1) = '\0';
              strcpy(ftp_info_fname, tmpstr);
         }
    }
    gd = GDnew(32);

    /** find the last character **/
    lastchar = ftppath[strlen(ftppath)-1];
    if (lastchar == '/')
         GETDIR = TRUE;
    else
         GETDIR = FALSE;

    /***  here we go...!***/
    ftp = FTPnew(ftphost);
    if (ftp == NULL || ftp->control_sock < 0)
         return(-1);

    if (FTPlogin(ftp, ftpuser, ftppass) < 0)
         return(-1);

    if (lastchar == '/') {
         char thename[256];

         /*** Do the directory ***/
         ftppath[strlen(ftppath)-1] = '\0';

         if ((int)strlen(ftppath) > 1) {
              if (FTPgetType(ftp) == FTP_VMS
                  || FTPgetType(ftp) == FTP_NOVELL
                  || FTPgetType(ftp) == FTP_MACOS
                  || FTPgetType(ftp) == FTP_OS2
                  || FTPgetType(ftp) == FTP_VM) {
                   int len = strlen(ftppath);

                   ftppath[len] =  '/'; /*** Ick!***/
                   ftppath[len+1] = '\0';

                   FTPwalkchdir(ftp, ftppath);
                   ftppath[len] = '\0';
              }
              else {
                   for (cp = ftppath; *cp; cp++)
                        if (!isascii(*cp))
                             *cp = '?';
                   if (strchr(ftppath, ' ')) {
                        sprintf (ftppass, "\"%s\"", ftppath);
                        FTPchdir(ftp, ftppass);
                   } else
                        FTPchdir(ftp, ftppath);
              }
#ifdef VMS_SERVER
       Errored:
               if (FTPisErrored(ftp)) {
                   FTPabort(ftp);
                   return(-1);
               }
#endif

         }

         switch(FTPgetType(ftp)) {

         case FTP_UNKNOWN:
         case FTP_UNIX:
         case FTP_WINNT:
              FTPopenData(ftp, "LIST -LF", NULL, 299);
              break;

         case FTP_NOVELL:
         case FTP_UNIX_L8:
         case FTP_MICRO_VAX:
         case FTP_VMS:
         case FTP_VM:
         case FTP_MACOS:
         case FTP_OS2:
              FTPopenData(ftp, "LIST", NULL, 299);
              break;

         default:
              FTPopenData(ftp, "NLST", NULL, 299);
              break;
         }
#ifdef VMS_SERVER
    if (FTPisErrored(ftp))
       goto Errored;
#endif

         if (GFTPplus && (SentGPHeader == FALSE)) {
                 GSsendHeader(Gsockfd, -1);
                 SentGPHeader = TRUE;
         }
         while (readline(FTPgetData(ftp), buf, sizeof(buf)) > 0) {

              ZapCRLF(buf);
              GSinit(gs);
              GSsetGplus(gs, TRUE);
              GSsetHost(gs, ftphost);
              GSsetPath(gs, ftppath);
              GSsetPort(gs, GopherPort);
              GSsetTTL(gs, GDCgetCachetime(Config));
              if (GFTPplus)
                   GSsetGplus(gs, TRUE);
              if (GopherList(ftp, buf, thename, gs) == -1)
                   continue;
              GSsetHost(gs, Zehostname);
              GSgetURL(gs);
              GDaddGS(gd, gs);
         }

         if (GFTPplus) {
              if (*CMDgetCommand(cmd) == '!') {
                   int fnum = GDSearch(gd, ftp_info_fname);
                   if (fnum >= 0)
                        GSplustoNet(GDgetEntry(gd, fnum), sockfd, NULL, "");
              } else
              if (strncasecmp(CMDgetView(cmd), "text/html", 9) == 0) {
                   GDtoNet(gd, sockfd, GSFORM_HTML, "", NULL);
              }
              else
                   GDplustoNet(gd, sockfd, NULL, "", NULL);
         } else
              GDtoNet(gd, sockfd, GSFORM_G0, "", NULL);
         writestring(sockfd, ".\r\n");
    }
    else {
         char *fname;
         if (FTPgetType(ftp) == FTP_VMS
             || FTPgetType(ftp) == FTP_NOVELL
             || FTPgetType(ftp) == FTP_MACOS
             || FTPgetType(ftp) == FTP_OS2
             || FTPgetType(ftp) == FTP_VM)
              fname = FTPwalkchdir(ftp, ftppath);
         else {
              for (cp = ftppath; *cp; cp++)
                   if (!isascii(*cp))
                        *cp = '?';
              if (strchr(ftppath, ' ')) {
                   sprintf (ftppass, "\"%s\"", ftppath);
                   fname = ftppass;
              } else
                   fname = ftppath;
#ifdef VMS_SERVER
              if (FTPisErrored(ftp))
                   goto Errored;
#endif
         }

         if (IsBinaryType(ftppath)) {
              Debug("Getting binary %s\n", ftppath);

              FTPbinary(ftp);
              FTPopenData(ftp, "RETR %s", fname, 299);
#ifdef VMS_SERVER
              if (FTPisErrored(ftp))
                 goto Errored;
#endif

              if (GFTPplus && (SentGPHeader == FALSE)) {
                   GSsendHeader(Gsockfd, -2);
                   SentGPHeader = TRUE;
              }
              while ((blen = FTPread(ftp, buf, sizeof(buf)))>0)
                     writen(sockfd, buf, blen);
         }
         else {
              Debug("Getting ascii %s\n", ftppath);

              FTPascii(ftp);
              FTPopenData(ftp, "RETR %s", fname, 199);
#ifdef VMS_SERVER
              if (FTPisErrored(ftp))
                  goto Errored;
#endif

              if (GFTPplus && (SentGPHeader == FALSE)) {
                   GSsendHeader(Gsockfd, -1);
                   SentGPHeader = TRUE;
              }
              blen = FTPread(ftp, buf, sizeof(buf));
              while (blen > 0) {
                   writen(sockfd, buf, blen);
                   blen = FTPread(ftp, buf, sizeof(buf));
              }
              writestring(sockfd, ".\r\n");
         }

    }
    FTPcloseData(ftp);
    FTPdestroy(ftp);

    return(0);
}


/*************************************************************
Takes the LIST output and
truncates it to just the name
Handles special cases for different
formats of LIST output
******************************/

int
GopherList(ftp, bufptr, theName, gs)
 FTP  *ftp;
 char *bufptr;
 char *theName;
 GopherObj *gs;
{
    char Gzerotype;
    static char *IntName = NULL;

    if (IntName == NULL)
         IntName = (char *)malloc(BUFSIZ);

    *IntName = '\0';

    /* Skip 'total' line */
    if (strncmp(bufptr, "total", 5) == 0)
         return (-1);
    if (strncmp(bufptr, "Total of", 8) == 0)
         return (-1);

    /* Trim whitespaces */
    TrimEnd(bufptr);

    switch (FTPgetType(ftp))
    {
    case FTP_MACOS:
         Debugmsg("Parsing MACOS List\n");
         Gzerotype = ParseUnixList(ftp, bufptr, IntName, theName, 6, gs);
         break;

    case FTP_VMS:
         Debugmsg("Parsing VMS List\n");
         Gzerotype = ParseVMSList(ftp, bufptr, IntName, theName, gs);
         break;

    case FTP_NOVELL:
         Debugmsg("Parsing Novell List\n");
         Gzerotype = ParseUnixList(ftp, bufptr, IntName, theName, 6, gs);
         break;

    case FTP_UNIX:
    case FTP_UNIX_L8:
    case FTP_WINNT:
         Debugmsg("Parsing Unix List\n");
         Gzerotype = ParseUnixList(ftp, bufptr, IntName, theName, 7, gs);
         break;

    case FTP_OS2:
         Debugmsg("Parsing OS/2 List\n");
         Gzerotype = ParseOS2List(ftp, bufptr, IntName, theName, gs);
         break;


    case FTP_VM:
    case FTP_UNKNOWN:
    default:
         Debugmsg("Parsing Unknown List\n");
         Gzerotype = ParseUnixList(ftp, bufptr, IntName, theName, 7, gs);
         break;
    }


    return(Gzerotype);
}


int
ParseUnixList(ftp, bufptr, IntName, theName, cols, gs)
 FTP  *ftp;
 char *bufptr;
 char *IntName;
 char *theName;
 int cols;
 GopherObj *gs;
{
    int i, end;
    int gap, objsize;
    char *dirname, *alias, *group=NULL, *size=NULL, *month=NULL,
         *novellsize=NULL;
    char tmpstr[64], sizestr[16], datestr[32];
    char *cp1;

    end = strlen(bufptr);
    while (isspace(bufptr[end-1]))
         end--;

    /*** Skip the permissions bits.. ***/
    i = 10;
    while (bufptr[i] == ' ' || bufptr[i] == '-' || bufptr[i] == ']')
         i++;
    gap =1;

    for ( ; (gap < cols) && (i < end); gap++)
    {
         switch (gap) {
         case 2:
              novellsize = &bufptr[i];
              break;

         case 3:
              group = &bufptr[i];
              break;

         case 4:
              size = &bufptr[i];
              break;

         case 5:
              month = &bufptr[i];
              break;
         }

         /* Skip chars to white */
         for (;(!isspace(bufptr[i])) && (i < end); i++);

         if (i >= end)
              FTPerrorMessage(ftp, "said Unix but wasn't");

         /* Skip white to chars */
         for (;isspace(bufptr[i]) && (i < end); i++);

         if (i >= end)
              FTPerrorMessage(ftp, "said Unix but wasn't");
    }

    if (gap < cols)
         FTPerrorMessage(ftp, "said Unix but wasn't (short cols)");

    if (isdigit(bufptr[i]) && isdigit(bufptr[i+1])) {
         if (isdigit(bufptr[i+2]) && isdigit(bufptr[i+3]) &&
             isspace(bufptr[i+4])) { /* probably the YEAR */
              for (i+=4;isspace(bufptr[i]); i++);
         } else if ((bufptr[i+2] == ':') && isdigit(bufptr[i+3]) &&
                    isdigit(bufptr[i+4]) && isspace(bufptr[i+5])) {
              /* probably the HOUR:MINUTE */
              for (i+=5;isspace(bufptr[i]); i++);
         }
    }

    /* Point at supposed start-of-fileIntName */
    dirname = alias = &bufptr[i];

    if (dirname[strlen(dirname)-1] == '/')
         dirname[strlen(dirname)-1] = '\0';

    /* Skip . and .. */
    if (!strcmp(dirname, ".") || !strcmp(dirname, ".."))
         return (-1);

    if (GFTPplus) {
         sprintf(tmpstr, "%s <%s>",
                 GDCgetAdmin(Config), GDCgetAdminEmail(Config));
         GSsetAdmin(gs, tmpstr);
    }

    switch (FTPgetType(ftp)) {
    case FTP_MACOS:
         switch(bufptr[0]) {
         case 'd':
              month = group;
              break;
         case '-':
              month = size;
              break;
         }
         size = novellsize;
         break;

    case FTP_NOVELL:
         month = group;
         size = novellsize;
         break;
    }

try_size_again:
    if (isdigit(*size)) {
         if (isupper(*month) && isalpha(*(month+1)) &&
             isalpha(*(month+2)) && isspace(*(month+3))) {
              objsize = (int) strtol(size, (char **)NULL, 10);
              if (objsize >= 1024)
                   sprintf(sizestr, "%dk", objsize/1024);
              else
                   sprintf(sizestr, ".%dk", (objsize*10)/1024);
              strncpy (datestr, month, alias-month-1);
              datestr[alias-month-1] = '\0';
         } else {
              LOGGopher(Gsockfd, "Couldn't parse size of %s", bufptr);
              LOGGopher(Gsockfd, "size is %s", size);
              LOGGopher(Gsockfd, "month is %s", month);
         }
    } else if (isupper(*size) && isalpha(*(size+1)) &&
               isalpha(*(size+2)) && isspace(*(size+3))) {
         month = size;
         size = group;
         goto try_size_again;
    } else {
         LOGGopher(Gsockfd, "Couldn't parse size of %s", bufptr);
         LOGGopher(Gsockfd, "size is %s", size);
    }

    switch(bufptr[0])
    {
    case 'l': /* Link? Skip to REAL IntName! */
         /* [mea] Or do you ? Handle the symlink semantics ??
            Is it really a directory, or a file ?       */

         /* Data is of   foobar@ -> barfoo format, that is, separator is
            5 characters "@ -> " */


         for (dirname = alias ; (*dirname != '\0') && (dirname != NULL);
              ++dirname) {
              if (strncmp(dirname, " -> ",4) == 0) {
                   gap = 4;
                   break;
              }
              if (strncmp(dirname, "@ -> ",5) == 0) {
                   gap = 5;
                   break;
              }
         }
         if (dirname == NULL || *dirname == '\0' || dirname[gap] == '\0')
              return(-1); /* No real DirName?  Hm.  Oh well */

         end = strlen(dirname);
         while (isspace(dirname[end-1]))
              end--;

         /*Internal name in 'IntName' */
         strncpy(IntName,alias,dirname-alias);
         IntName[dirname-alias] = 0; /* Make sure it terminates */
         if (IntName[strlen(IntName)-1] == '@')
              IntName[strlen(IntName)-1] = '\0';

         if (dirname[end-1] == '/'){
              /* Display name in theName */
              sprintf(theName, "%c%s", A_DIRECTORY, IntName );

              /* Tag slash on end */
              sprintf(bufptr, "%s/", IntName);

              GSsetType(gs, A_DIRECTORY);
              sprintf (tmpstr, "%s [%s]", IntName, datestr);
              GSsetTitle(gs, tmpstr);
              sprintf (tmpstr, "ftp:%s@%s/%s/", GSgetHost(gs),
                       GSgetPath(gs), IntName);
              GSsetPath(gs, tmpstr);
              if (GFTPplus) {
                   GSsetModDate(gs, datestr);
                   AddDefaultView(gs, objsize, NULL);
              }
              return(A_DIRECTORY);
         } else {
              /* Determine type of file */

              i = GopherType(ftp, dirname+gap, theName);
              strcpy(theName+1, IntName);
              sprintf(tmpstr, "%s@", dirname+gap);
              strcat(theName+1, tmpstr);
              strcpy(bufptr, IntName);
              GSsetType(gs, theName[0]);
              sprintf (tmpstr, "%s (%s) [%s]", IntName, sizestr, datestr);
              GSsetTitle(gs, tmpstr);
              sprintf(tmpstr, "ftp:%s@%s/%s", GSgetHost(gs),
                      GSgetPath(gs), theName+1);
              GSsetPath(gs, tmpstr);
              if (GFTPplus) {
                   GSsetModDate(gs, datestr);
                   AddDefaultView(gs, objsize, NULL);
              }
              return(i);
         }
         break;

    case 'd': /* Now treat as regular directory */
         /* Display name in theName */
         sprintf(theName, "%c%s", A_DIRECTORY, dirname);

         /*Internal name in 'IntName' */
         strcpy(IntName,dirname);

         /* Tag slash on end */
         sprintf(bufptr, "%s/", IntName);

         GSsetType(gs, A_DIRECTORY);
         sprintf (tmpstr, "%s [%s]", IntName, datestr);
         GSsetTitle(gs, tmpstr);
         if (cp1 = strchr(IntName, '/'))
              for ( ; *cp1; cp1++ ) {
                   if (*cp1 == '/')
                        *cp1 = '*';
              }

         sprintf (tmpstr, "ftp:%s@%s/%s/", GSgetHost(gs), GSgetPath(gs),
                  IntName);
         GSsetPath(gs, tmpstr);
         if (GFTPplus) {
                 GSsetModDate(gs, datestr);
                 AddDefaultView(gs, objsize, NULL);
         }

         return(A_DIRECTORY);
         break;

    default:
         /* Determine type of file */
         strcpy(IntName, dirname);
         strcpy(bufptr, theName+1);
         GopherType(ftp, IntName, theName);
         GSsetType(gs, theName[0]);
         sprintf (tmpstr, "%s (%s) [%s]", theName+1, sizestr, datestr);
         GSsetTitle(gs, tmpstr);
         sprintf(tmpstr, "ftp:%s@%s/%s", GSgetHost(gs), GSgetPath(gs), theName+1);
         GSsetPath(gs, tmpstr);
         if (GFTPplus) {
                 GSsetModDate(gs, datestr);
                 AddDefaultView(gs, objsize, NULL);
         }
         break;
    }
    return(i);
}

static char vmsprevline[64];

int
ParseVMSList(ftp, bufptr, IntName, theName, gs)
 FTP  *ftp;
 char *bufptr;
 char *IntName;
 char *theName;
 GopherObj *gs;
{
    int i, j;
    int objsize;
    char tmpstr[64], sizestr[16], datestr[32];

    if (bufptr[0] == '\0')
         return (-1);

    j = strlen(bufptr);
    while (isspace(bufptr[j-1]))
         j--;
    for (i = 0; bufptr[i] && !isspace(bufptr[i]); i++);
    if (bufptr[i] == '\0') {
         if (i)  strcpy (vmsprevline, bufptr);
         return (-1);
    }
    if (i == 0 && vmsprevline[0]) {
         strcpy(IntName,vmsprevline);
         vmsprevline[0] = '\0';
    } else {
         strncpy(IntName,bufptr,i);
         IntName[i] = '\0'; /* Make sure it terminates */
    }
    GopherType(ftp, IntName, theName);
    GSsetType(gs, theName[0]);
    if (GFTPplus) {
         sprintf(tmpstr, "%s <%s>",
                 GDCgetAdmin(Config), GDCgetAdminEmail(Config));
         GSsetAdmin(gs, tmpstr);
    }
    for ( ; bufptr[i] && isspace(bufptr[i]); i++);
    if (isdigit(bufptr[i])) {
         objsize = (int) strtol(&bufptr[i], (char **)NULL, 10);
         sprintf(sizestr, "%dk", objsize);
    } else if (!strncmp(&bufptr[i], "%RMS-E-PRV", strlen("%RMS-E-PRV")))
         return (-1);
    else {
         LOGGopher(Gsockfd, "Couldn't parse %s", bufptr);
         return (-1);
    }
    for ( ; bufptr[i] && !isspace(bufptr[i]); i++);
    for ( ; bufptr[i] &&  isspace(bufptr[i]); i++);
    for (j = i; bufptr[j] && bufptr[j] != '['; j++);
    j--;
    strncpy (datestr, &bufptr[i], j-i);
    datestr[j-i] = '\0';

    if (GFTPplus) {
         GSsetModDate(gs, datestr);
         AddDefaultView(gs, objsize*1024, NULL);
    }
    switch (i = GSgetType(gs)) {
    case A_DIRECTORY:
         sprintf(tmpstr, "ftp:%s@%s/%s/", GSgetHost(gs),
                 GSgetPath(gs), theName+1);
         GSsetPath(gs, tmpstr);
         sprintf (tmpstr, "%s [%s]", theName+1, datestr);
         GSsetTitle(gs, tmpstr);
         return(A_DIRECTORY);
         break;
    default:

         sprintf(tmpstr, "ftp:%s@%s/%s", GSgetHost(gs),
                 GSgetPath(gs), theName+1);
         GSsetPath(gs, tmpstr);
         sprintf (tmpstr, "%s (%s) [%s]", theName+1, sizestr, datestr);
         GSsetTitle(gs, tmpstr);

         break;
    }
    return(i);
}


int
ParseOS2List(ftp, bufptr, IntName, theName, gs)
 FTP  *ftp;
 char *bufptr;
 char *IntName;
 char *theName;
 GopherObj *gs;
{
    int i;
    int objsize;
    char tmpstr[64], sizestr[16], datestr[32];

    if (bufptr[0] == '\0')
         return (-1);
    for (i = 0; bufptr[i] && isspace(bufptr[i]); i++);
    if (isdigit(bufptr[i])) {
         objsize = (int) strtol(&bufptr[i], (char **)NULL, 10);
         if (objsize >= 1024)
              sprintf(sizestr, "%dk", objsize/1024);
         else
              sprintf(sizestr, ".%dk", (objsize*10)/1024);
    } else {
         LOGGopher(Gsockfd, "Couldn't parse %s", bufptr);
         return (-1);
    }
    for ( ; bufptr[i] && !isspace(bufptr[i]); i++);
    for ( ; bufptr[i] &&  isspace(bufptr[i]); i++);
    if (!strncmp(&bufptr[i], "DIR ", 4)) {
         GSsetType(gs, A_DIRECTORY);
         i += 4;
    } else if (!strncmp(&bufptr[i], "A ", 2)) {
         GSsetType(gs, A_FILE);
         i += 2;
    } else if (isdigit(bufptr[i])) {
         GSsetType(gs, A_FILE);
    } else {
         LOGGopher(Gsockfd, "Couldn't parse %s", bufptr);
         return (-1);
    }
    for ( ; bufptr[i] && isspace(bufptr[i]); i++);
    if (isdigit(bufptr[i])) {
         strncpy (datestr, &bufptr[i], 16);
         datestr[16] = '\0';
         i += 16;
    } else {
         LOGGopher(Gsockfd, "Couldn't parse %s", bufptr);
         return (-1);
    }
    for ( ; bufptr[i] && isspace(bufptr[i]); i++);
    if (!strcmp(&bufptr[i], ".") || !strcmp(&bufptr[i], ".."))
         return (-1);

    if (GFTPplus) {
         GSsetModDate(gs, datestr);
         AddDefaultView(gs, objsize*1024, NULL);
         sprintf(tmpstr, "%s <%s>",
                 GDCgetAdmin(Config), GDCgetAdminEmail(Config));
         GSsetAdmin(gs, tmpstr);
    }
    GopherType(ftp, &bufptr[i], theName);

    switch (i = GSgetType(gs)) {
    case A_DIRECTORY:
         sprintf(tmpstr, "ftp:%s@%s/%s/", GSgetHost(gs),
                 GSgetPath(gs), theName+1);
         GSsetPath(gs, tmpstr);
         sprintf (tmpstr, "%s [%s]", theName+1, datestr);
         GSsetTitle(gs, tmpstr);
         return(A_DIRECTORY);
         break;
    default:
         i = GSsetType(gs, theName[0]);
         sprintf(tmpstr, "ftp:%s@%s/%s", GSgetHost(gs),
                 GSgetPath(gs), theName+1);
         GSsetPath(gs, tmpstr);
         sprintf (tmpstr, "%s (%s) [%s]", theName+1, sizestr, datestr);
         GSsetTitle(gs, tmpstr);
         break;
    }
    return(i);
}





int
GopherType(ftp, bufptr, theName)
 FTP  *ftp;
 char *bufptr;
 char *theName;
{
    int last;

    if (FTPgetType(ftp) == FTP_VMS)
         last = Vaxinate(bufptr);
    else
         last = strlen(bufptr)-1;


    if (bufptr[last] == '/')
    {
         sprintf(theName, "%c%s", A_DIRECTORY, bufptr);
         theName[strlen(theName)-1] = '\0';
         return(A_DIRECTORY);
    }

    if ((bufptr[last] == '*') || (bufptr[last] == '@'))     /* Hack out * and @ */
         bufptr[last] = '\0';


    return(GopherFile(ftp, bufptr, theName));
}



/* At this point we're looking at a file */

int
GopherFile(ftp, buf, theName)
 FTP  *ftp;
 char *buf;
 char *theName;
{
    char       Gtype;
    int        last;
    char       tmpName[SLEN];
    Extobj     *ext;
    char *slashp, **textn;

    last = strlen(buf) -1;

    strcpy(tmpName, buf);
    if (buf[last] == '/') {
         tmpName[last] = '\0';
         sprintf(theName, "%c%s", A_DIRECTORY, tmpName);
         return(A_DIRECTORY);
    }
    if ((buf[last] == '*') || (buf[last] == '@')) {    /* Hack out * and @ */
         buf[last] = '\0';
         tmpName[last] = '\0';
    }

    /* At this point we're looking at a file */
    if (GDCViewExtension(Config, buf, &ext)) {
         Gtype = EXgetObjtype(ext);

         sprintf(theName, "%c%s", Gtype, tmpName);
         return(Gtype);
    }

    /** Hack for some notable looking text files **/

    if ((slashp = strrchr(buf, '/')) == NULL)
         slashp = buf;
    else
         slashp++;
    for (textn = textnames; *textn; textn++)
         if (strcasestr(slashp, *textn)==0) {
              sprintf(theName, "%c%s", A_FILE, tmpName);
              return(A_FILE);  /* text file */
         }
    sprintf(theName, "%c%s", A_UNIXBIN, tmpName);
    return(A_UNIXBIN); /* Some other and hopefully text file */
}

#else

GopherFTPgw(sockfd, Selstr)
 int sockfd;
 char *Selstr;
{
#ifdef VMS_SERVER
    SetAbrtFile(RangeErr, NULL, KeepAbrtGS, NULL);
#endif
    Abortoutput(sockfd, "Sorry, this server does not have ftp capabilities");
}


#endif  /** NO_FTP **/