/********************************************************************
 * $Author: jgoerzen $
 * $Revision: 1.5 $
 * $Date: 2002/02/12 21:20:14 $
 * $Source: /var/cvs/gopher/gopherd/ftp.c,v $
 * $State: 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:
 * $Log: ftp.c,v $
 * Revision 1.5  2002/02/12 21:20:14  jgoerzen
 * Made files using strcasecmp() include util.h
 *
 * Revision 1.4  2001/01/17 19:30:25  jgoerzen
 * Change many sprintf -> snprintf
 *
 * Revision 1.3  2000/12/21 05:33:20  s2mdalle
 *
 *
 * Miscellaneous code cleanups
 *
 * Revision 1.2  2000/12/20 01:19:20  jgoerzen
 * Added patches from David Allen <s2mdalle@titan.vcu.edu>
 *
 * Revision 1.1.1.1  2000/08/19 00:28:56  jgoerzen
 * Import from UMN Gopher 2.3.1 after GPLization
 *
 * Revision 3.52  1995/11/03  18:13:58  lindner
 * Coen: ftp gw fixes..
 *
 * Revision 3.51  1995/09/28  21:11:29  lindner
 * More fixes...
 *
 * Revision 3.50  1995/09/26  05:00:46  lindner
 * Minor fix..
 *
 * Revision 3.49  1995/09/26  04:59:44  lindner
 * Fix abort routines, change to Die/Warn hash over access routines..
 *
 * Revision 3.48  1995/09/25  22:06:56  lindner
 * Ansification bugs
 *
 * Revision 3.47  1995/09/25  05:02:33  lindner
 * Convert to ANSI C
 *
 * Revision 3.46  1995/09/09  03:20:06  lindner
 * FTP username and password support
 *
 * Revision 3.45  1995/08/29  07:10:49  lindner
 * URL code changes..
 *
 * Revision 3.44  1995/07/29  04:39:03  lindner
 * Add URL ftp support for the server
 *
 * 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 "url.h"
#include "util.h"
#include <signal.h>

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

#include "Debug.h"

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

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

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

static boolean GETDIR = FALSE;

/*** Some forward declarations ***/
boolean NotText(char *);

boolean FTPconnect(FTP *ftp, char *host);

FTP *
FTPnew(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;

     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)
{
     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 *ftp, char *host)
{
     int newcontrol;
     char message[256];
     void (*fptr_FTPcleanup)() = FTPcleanup;

     /** Get ready for some cleanup action **/
     signal(SIGPIPE, fptr_FTPcleanup);
     signal(SIGINT, fptr_FTPcleanup);
     signal(SIGALRM, fptr_FTPcleanup);
     
     newcontrol = SOCKconnect(host,21);

     if (newcontrol < 0) {
		 if (GFTPplus && (SentGPHeader == FALSE)) {
			 GSsendHeader(Gsockfd, -1);
			 SentGPHeader = TRUE;
		 }
		 snprintf(message, sizeof(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, "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);

     return(TRUE);
}


char *
FTPpwd(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)
{
     int ftpnum;
     char message[256];

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

     FTPsend(ftp, "SYST");
     ftpnum = FTPgetReply(ftp, 999, message, sizeof(message));
     
     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  *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 *ftp, char *message)
{
     int ftpnum;

     ftpnum = (int) strtol(message, (char **)NULL, 10);
     return(ftpnum);
}


boolean
FTPisContinuation(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  *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;
	   }
	  FTPabort(ftp);
	  gopherd_exit(-1);
     } else if (ftpnum <0) {
	  /** Really bad error **/
	  FTPabort(ftp);
	  gopherd_exit(-1);
     }

     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  *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  *ftp, char *command, char *parameter, int  errorlevel)
{
     char commandline[512];
     char message[256];
     
     snprintf(commandline, sizeof(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  *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);
     return (0);
}



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


/** Open a data connection **/
void
FTPopenData(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;
     
     snprintf(theline, sizeof(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));
     

     FTPcommand(ftp, command, file, errorlevel);

     ftp_data = SOCKaccept(ftp_dataport, we);

     if (ftp_data < -1)
	  return;

     FTPsetData(ftp, ftp_data);

}


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

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

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

     return(len);
}


/*--------------------------------*/
/* Used in xLateText below --------*/
boolean
NotText(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(char *path)
{
     char Gtype;
     Extobj *ext;
     char *slashp, **textn, *cp;
     
     ext = EXnew();


     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:
	       EXdestroy(ext);
	       return FALSE;

	  }
     }
     if (EXAcasedSearch(Config->Extensions, ext, path, EXT_DECODER)) {
	  EXdestroy(ext);
	  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(char *bufptr)
{
     int last;
     for (last = strlen(bufptr) - 1; isspace(bufptr[last]) ; bufptr[last--] = '\0')
	  ;
}



int
Vaxinate(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 *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);
	       return;
	  }
	  /* else fall through */
     default:
	  FTPabort(ftp);
	  gopherd_exit(-1);
	  break;
     }
     return;
}


char *
FTPwalkchdir(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, ' ')) {
	       snprintf(tmppath, sizeof(tmppath),
			"\"%s\"", vmspath);
	       FTPchdir(ftp, tmppath);
 	  } else
	       FTPchdir(ftp, vmspath);
	  
	  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 *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);

     snprintf(errmsg, sizeof(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 *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] == '-')
	       snprintf(errmsg, sizeof(errmsg),
			"i%s\t\terror.host\t1\r\n", &message[4]);
	  else
	       snprintf(errmsg, sizeof(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)
{
     writestring(Gsockfd, ".\r\n");
     FTPdestroy(ftp);
}


Url *ftpurl;

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

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

     Gsockfd = sockfd;
     
     /* Find @ and parse out */
     if (strncmp(ftpstr, "//", 2) == 0) {
	  char *mystr;
	  /** Must be a url stylee string... **/
	  mystr  = (char*)malloc(strlen(ftpstr) + 5);
	  strcpy(mystr, "ftp:");
	  strcat(mystr, ftpstr);
	  ftpurl = URLnew();
	  URLset(ftpurl, mystr);

	  if (URLgetUser(ftpurl) != NULL)
	       ftpuser = strdup(URLgetUser(ftpurl));
	  if (URLgetPass(ftpurl) != NULL)
	       strcpy(ftppass, URLgetPass(ftpurl));
	  ftppath = strdup(URLgetPath(ftpurl));
	  ftphost = strdup(URLgetHost(ftpurl));

	  if (strlen(ftppath) == 0) {
	       ftppath[0] = '/';
	       ftppath[1] = '\0';
	  }
	  free(mystr);
	  
     } else {
	  cp = strchr(ftpstr, '@');
	  if (!cp)
	       return(-1);

	  *cp = '\0';
	  
	  ftppath = cp+1;
	  ftphost = ftpstr;
     }

     if (!ftpuser)
	  ftpuser = "anonymous";

     Gsockfd = sockfd;
/*     GFTPplus = CMDisGplus(cmd);*/
     GFTPplus = FALSE;
     SentGPHeader = FALSE;

     if (*ftppath == '/') 
	  ftppath++;
 
     if (*ftppath == '\0') {
	  *ftppath = '/';
 	  *(ftppath+1) = '\0';
     }
 


     if (*ftppass == '\0') {
	  snprintf(ftppass, sizeof(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)
	       Die(sockfd, 500, "Can't parse %s", ftppath);

	  GSsetHost(gs, Zehostname);
	  GSsetPort(gs, GopherPort);
	  if (strcmp(ftpuser, "anonymous") != 0) {
 	       snprintf(tmpstr, sizeof(tmpstr),
			"%s%s", URLget(ftpurl), ftppath);
	  } else {
	       snprintf(tmpstr, sizeof(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, ' ')) {
			 snprintf (ftppass, sizeof(ftppass), 
				   "\"%s\"", ftppath);
			 FTPchdir(ftp, ftppass);
		    } else
			 FTPchdir(ftp, ftppath);
	       }
	       
	  }

	  switch(FTPgetType(ftp)) {

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


	  case FTP_UNIX_L8:
	       FTPopenData(ftp, "LIST -F", NULL, 299);
	       break;

	  case FTP_NOVELL:
	  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;
	  }

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

	       ZapCRLF(buf);
	       GSinit(gs);
	       GSsetGplus(gs, FALSE);
	       GSsetHost(gs, ftphost);
	       GSsetPath(gs, ftppath);
	       GSsetPort(gs, GopherPort);
	       GSsetTTL(gs, GDCgetCachetime(Config));
	       if (GFTPplus)
		    GSsetGplus(gs, TRUE);
	       if (GopherList(ftp, buf, thename, gs, ftpuser, ftppass) == -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, "");
	  } 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, ' ')) {
		    snprintf (ftppass, sizeof(ftppass), "\"%s\"", ftppath);
		    fname = ftppass;
	       } else
		    fname = ftppath;
 	  }
	  
	  if (IsBinaryType(ftppath)) {
	       Debug("Getting binary %s\n", ftppath);

	       FTPbinary(ftp);
	       FTPopenData(ftp, "RETR %s", fname, 299);

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

	       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 *ftp, char *bufptr, char *theName, GopherObj *gs,
	   char *ftpuser, char *ftppass)
{
     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,
				    ftpuser, ftppass);
	  break;

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

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

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

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


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


     return(Gzerotype);
}

/* NOTE: 
 * arg 8 has been changed from char **ftppass to char *ftppass given its
 * similarity to ParseVMSList and ParseOS2List.  But what gives?  It isn't
 * even used in the function body.  
 */
int
ParseUnixList(FTP *ftp, char *bufptr, char *IntName, char *theName,
	      int cols, GopherObj *gs, char *ftpuser, char *ftppass)
{
     int i=0, end=0;
     int gap=0, objsize=0;
     char *dirname, *alias, *group=NULL, *size=NULL, *month=NULL, 
          *novellsize=NULL;
     char tmpstr[64], sizestr[16], datestr[32];
     char *cp1=NULL;

     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])) {
	  if (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++);
	       }
	  } else if ((bufptr[i+1] == ':') && isdigit(bufptr[i+2]) &&
		     isdigit(bufptr[i+3])) {
	       /* probably the HOUR:MINUTE */
	       for (i+=4;isspace(bufptr[i]); i++);
	  }
	  
     }     
     /* Point at supposed start-of-fileIntName */
     dirname = alias = &bufptr[i]; 
     
     
     /* Skip . and .. */
     if (!strcmp(dirname, ".") || !strcmp(dirname, ".."))
	  return (-1);

     if (GFTPplus) {
	  snprintf(tmpstr, sizeof(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;
          /* Extra unused cases added to clarify assumptions and so the 
           * compiler won't complain.
           */
     case FTP_UNKNOWN:
     case FTP_VMS:
     case FTP_UNIX:
     case FTP_MTS:
     case FTP_WINNT:
     case FTP_VM:
     case FTP_UNIX_L8:
     case FTP_MICRO_VAX:
     case FTP_OS2:
     }

 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)
		    snprintf(sizestr, sizeof(sizestr), "%dk", objsize/1024);
	       else
		    snprintf(sizestr, sizeof(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);
	       snprintf (tmpstr, sizeof(tmpstr), "%s [%s]", IntName, datestr);
	       GSsetTitle(gs, tmpstr);

	       if (strcmp(ftpuser, "anonymous") != 0)
 		    snprintf(tmpstr, sizeof(tmpstr),
			     "%s%s/", URLget(ftpurl), IntName);
	       else
		    snprintf (tmpstr, sizeof(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); 
	       snprintf(tmpstr, sizeof(tmpstr),
			"%s@%s", dirname+gap, theName + 1);
	       strcpy(bufptr, IntName);
	       GSsetType(gs, theName[0]);
	       snprintf(tmpstr, sizeof(tmpstr),
			"%s (%s) [%s]", IntName, sizestr, datestr);
	       GSsetTitle(gs, tmpstr);
	       if (strcmp(ftpuser, "anonymous") != 0)
 		    snprintf(tmpstr, sizeof(tmpstr),
			     "%s%s",URLget(ftpurl), theName+1);
	       else
		    snprintf(tmpstr, sizeof(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 */
 	  if (dirname[strlen(dirname)-1] == '/')
	       dirname[strlen(dirname)-1] = '\0';
      

	  /* 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);
	  snprintf (tmpstr, sizeof(tmpstr), "%s [%s]", IntName, datestr);
	  GSsetTitle(gs, tmpstr);
 	  if ((cp1 = strchr(IntName, '/')))
	       for ( ; *cp1; cp1++ ) {
		    if (*cp1 == '/')
			 *cp1 = '*';
	       }
	  
	  if (strcmp(ftpuser, "anonymous") != 0) 
 	       snprintf (tmpstr, sizeof(tmpstr),
			 "%s%s/", URLget(ftpurl), IntName);
	  else
	       snprintf (tmpstr, sizeof(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]);
	  snprintf (tmpstr, sizeof(tmpstr),
		    "%s (%s) [%s]", theName+1, sizestr, datestr);
	  GSsetTitle(gs, tmpstr);
	  if (strcmp(ftpuser, "anonymous") != 0)
 	       snprintf(tmpstr, sizeof(tmpstr),
			"%s%s", URLget(ftpurl), theName+1);
	  else
	       snprintf(tmpstr, sizeof(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  *ftp, char *bufptr, char *IntName, char *theName,
	     GopherObj *gs, char *ftpuser, char *ftppass)
{
     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) {
	  snprintf(tmpstr, sizeof(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);
	  snprintf(sizestr, sizeof(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:
	  if (strcmp(ftpuser, "anonymous") != 0)
 	       snprintf(tmpstr, sizeof(tmpstr),
			"%s%s/", URLget(ftpurl), theName+1);
	  else
	       snprintf(tmpstr, sizeof(tmpstr), "ftp:%s@%s/%s/", GSgetHost(gs),
		       GSgetPath(gs), theName+1);

	  GSsetPath(gs, tmpstr);
	  snprintf(tmpstr, sizeof(tmpstr), "%s [%s]", theName+1, datestr);
	  GSsetTitle(gs, tmpstr);
	  return(A_DIRECTORY);
	  break;
     default: 
	  
	  if (strcmp(ftpuser, "anonymous") != 0)
 	       snprintf(tmpstr, sizeof(tmpstr),
			"%s%s", URLget(ftpurl), theName+1);
	  else
	       snprintf(tmpstr, sizeof(tmpstr),
			"ftp:%s@%s/%s", GSgetHost(gs),
			GSgetPath(gs), theName+1);
	  GSsetPath(gs, tmpstr);
	  snprintf (tmpstr, sizeof(tmpstr),
		    "%s (%s) [%s]", theName+1, sizestr, datestr);
	  GSsetTitle(gs, tmpstr);
	  
	  break;
     }
     return(i);
}


int
ParseOS2List(FTP *ftp, char *bufptr, char *IntName, char *theName,
	     GopherObj *gs, char *ftpuser, char *ftppass)
{
     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)
	       snprintf(sizestr, sizeof(sizestr), "%dk", objsize/1024);
	  else
	       snprintf(sizestr, sizeof(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);
	  snprintf(tmpstr, sizeof(tmpstr), "%s <%s>", 
		   GDCgetAdmin(Config), GDCgetAdminEmail(Config));
	  GSsetAdmin(gs, tmpstr);
     }
     GopherType(ftp, &bufptr[i], theName);

     switch (i = GSgetType(gs)) {
     case A_DIRECTORY:
	  if (strcmp(ftpuser, "anonymous") != 0)
 	       snprintf(tmpstr, sizeof(tmpstr), 
			"%s%s/", URLget(ftpurl), theName+1);
	  else
	       snprintf(tmpstr, sizeof(tmpstr), "ftp:%s@%s/%s/", GSgetHost(gs),
			GSgetPath(gs), theName+1);

	  GSsetPath(gs, tmpstr);
	  snprintf (tmpstr, sizeof(tmpstr), "%s [%s]", theName+1, datestr);
	  GSsetTitle(gs, tmpstr);
	  return(A_DIRECTORY);
	  break;

     default: 
	  i = GSsetType(gs, theName[0]);
	  if (strcmp(ftpuser, "anonymous") != 0)
 	       snprintf(tmpstr, sizeof(tmpstr), 
			"%s%s", URLget(ftpurl), theName+1);
	  else
	       snprintf(tmpstr, sizeof(tmpstr), "ftp:%s@%s/%s", GSgetHost(gs),
			GSgetPath(gs), theName+1);
	  GSsetPath(gs, tmpstr);
	  snprintf (tmpstr, sizeof(tmpstr),
		    "%s (%s) [%s]", theName+1, sizestr, datestr);
	  GSsetTitle(gs, tmpstr);
	  break;
     }
     return(i);
}




int
GopherType(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  *ftp, char *buf, char *theName)
{
     char	Gtype;
     int	last;
     char	tmpName[256];	
     Extobj     *ext;
     char *slashp, **textn;
     
     last = strlen(buf) -1;
     
     strncpy(tmpName, buf,sizeof(tmpName)-1);
     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';
     }
     
     ext = EXnew();
     /* At this point we're looking at a file */
     if (GDCViewExtension(Config, buf, &ext)) {  
	  Gtype = EXgetObjtype(ext);
	  
	  sprintf(theName, "%c%s", Gtype, tmpName);
	  EXdestroy(ext);
	  return(Gtype);
     }

     if (EXAcasedSearch(Config->Extensions, ext, buf, EXT_DECODER)) {
	  EXdestroy(ext);
	  sprintf(theName, "%c%s", A_UNIXBIN, tmpName);
	  return(A_UNIXBIN);
     }

     /** 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
#include "command.h"

GopherFTPgw(int sockfd, char *ftpstr, CMDobj *cmd)
{
     Die(sockfd, 501, "Sorry, this server does not have ftp capabilities");
}

   
#endif  /** NO_FTP **/
