
These are some patches for the V1.1 and V1.12 gopherd so it can talk to
FTP servers that do not understand the PASV command.

It also can tell directories from files from file-listings issued by some
VMS FTP servers.

This has been tested with:
	- one VMS FTP server running Wollongong-FTP V 5.1,
	- one VMS FTP server running Multinet-FTP V 3.1.
	- one Ultrix-RISC FTP server running 4.2 and one with 4.1,
	- boombox.micro.umn.edu, which seems to be a Mac running some
	  version of A/UX.

Apparently, Multinet 3.2 does understand the PASV command, but this will
not help, since the format of directory listings is still not what the
original gopherd expects.


To apply this patch:

Gopher 1.12: cd to the gopher1.12 directory and say: patch -p < thisfile
Gopher 1.1 : cd to the gopher1.1  directory and say: patch -p -F3 < thisfile

(You will get some "with fuzz n" and "offset n lines" messages with gopher1.1,
ignore these).

Patched files are: object/GSgopherobj.c gopherd/ftp.c gopherd/gopherd.c

Then rebuild gopherlib.a and gopherd:

	% rm object/gopherlib.a
	% make server


To use the extensions for VMS FTP servers, you must say:

        Path=ftp-vms:vms.ftp.server@/

and for Ultrix or A/UX FTP servers ("nop" stands for "no-PASV"):

	Path=ftp-nop:ultrix.ftp.serv@/

where you would usually write

        Path=ftp:unix.ftp.server@/

in a .Links file.

+-----------
  Andi Karrer, Communication Systems, ETH Zuerich, Switzerland
  karrer@bernina.ethz.ch    - Objects in mirror are closer than they appear
+-----------

*** object/GSgopherobj.c.dist	Mon Jan 11 20:54:22 1993
--- object/GSgopherobj.c	Sun Feb 21 23:11:49 1993
***************
*** 1,3 ****
--- 1,10 ----
+ /*
+  * Added support for VMS FTP servers that do not understand the FTP PASV
+  * command (Wollongong, Multinet). Also present these servers output
+  * in a Unix fashion.
+  * Also works with Ultrix 4.2 FTP servers whose PASV command is broken.
+  * Andi Karrer, karrer@bernina.ethz.ch, 21. Feb 93
+  */
  /********************************************************************
   * $Author: lindner $
   * $Revision: 1.1.1.3 $
***************
*** 408,413 ****
--- 415,499 ----
  	  closenet(iSock);
  	  return (-4);
       }
+      return(iSock);
+ }
+ 
+ /* needed for FTP servers without PASV */
+ #include <errno.h>
+ 
+ /* GSlisten opens a port and listens to it */
+ 
+ int
+ GSlisten(We)
+ 	struct sockaddr_in *	We;
+ {
+      int iSock = 0;
+      struct hostent *HostPtr;
+      int len = sizeof(struct sockaddr_in);
+      char name[100];
+ 
+      if ((iSock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+ 	  return (-3);
+ 
+      /* Cargo cult; no idea if these options are good here */
+ 
+ #ifndef UCX
+      setsockopt(iSock, SOL_SOCKET, ~SO_LINGER, 0, 0);
+ #endif
+      setsockopt(iSock, SOL_SOCKET, SO_REUSEADDR, 0, 0);
+      setsockopt(iSock, SOL_SOCKET, SO_KEEPALIVE, 0, 0);
+ 
+      if (listen(iSock, 5) || getsockname(iSock, We, &len)) {
+           closenet(iSock);
+           return(-4);
+      }
+ 
+      gethostname(name, 100);
+      if (HostPtr = gethostbyname(name)) 
+           bcopy(HostPtr->h_addr, (char *) &We->sin_addr, HostPtr->h_length);
+      return(iSock);
+ }
+ 
+ /* GSaccept accepts a connection from some socket. The parameter 
+  * full_hostname will, on return,
+  * contain the expanded hostname (if possible).  Note that full_hostname is a
+  * pointer to a char *, and is allocated by connect_to_gopher()
+  *
+  * Errors:
+  *
+  * -1 get service failed
+  *
+  * -2 get host failed
+  *
+  * -3 socket call failed
+  *
+  * -4 connect call failed
+  */
+ 
+ int
+ GSaccept(s, gs)
+   int s;
+   GopherObj *gs;
+ {
+      struct sockaddr_in Server;
+      struct hostent *HostPtr;
+      int ERRinet = -1;
+      int iSock = 0;
+      int len = sizeof(struct sockaddr_in);
+ 
+ #ifdef _CRAY
+      ERRinet = 0xFFFFFFFF;  /* -1 doesn't sign extend on 64 bit machines */
+ #endif
+ 
+      if ((iSock = accept(s, &Server, &len)) < 0) {
+ 	printf("Aaaargh! %d", errno);
+        return -4;
+      }
+      
+      GSsetPort(gs, ntohs(Server.sin_port));
+      
+      close(s); /* Der Mohr hat seine Schuldigkeit getan */
+ 
       return(iSock);
  }
  
*** gopherd/ftp.c.dist	Tue Jan 12 00:16:39 1993
--- gopherd/ftp.c	Sun Feb 21 23:12:01 1993
***************
*** 1,3 ****
--- 1,10 ----
+ /*
+  * Added support for VMS FTP servers that do not understand the FTP PASV
+  * command (Wollongong, Multinet). Also present these servers output
+  * in a Unix fashion.
+  * Also works with Ultrix 4.2 FTP servers whose PASV command is broken.
+  * Andi Karrer, karrer@bernina.ethz.ch, 21. Feb 93
+  */
  /********************************************************************
   * $Author: lindner $
   * $Revision: 1.2 $
***************
*** 62,67 ****
--- 69,76 ----
  int	gettingBinary = 0;
  int	childpid;
  
+ extern int  no_pasv, is_vms;
+ 
  /*** Some forward declarations ***/
  boolean NotText();
  int     Abort();
***************
*** 323,329 ****
  	  while (fgets(buf, sizeof buf, fp) != NULL) {
  	       ZapCRLF(buf);
  	       GopherType(buf, theName);
! 	       sprintf(outputline, "%s\tftp:%s@%s%s\t%s\t%d\r\n", theName,
  		       host, thing, buf, Zehostname, GopherPort);
  	       FailErr(writestring(sockfd, outputline));
  	  }
--- 332,339 ----
  	  while (fgets(buf, sizeof buf, fp) != NULL) {
  	       ZapCRLF(buf);
  	       GopherType(buf, theName);
! 	       sprintf(outputline, "%s\t%s:%s@%s%s\t%s\t%d\r\n", theName,
! 		       no_pasv ? is_vms ? "ftp-vms" : "ftp-nop" : "ftp", 
  		       host, thing, buf, Zehostname, GopherPort);
  	       FailErr(writestring(sockfd, outputline));
  	  }
***************
*** 430,438 ****
       int extType, i, last;
       char	tmpName[SLEN];	
       
!      last = strlen(buf) -1;
! 
       strcpy(tmpName, buf);
       if (buf[last] == '/') {
  	  tmpName[last] = '\0';
  	  sprintf(theName, "%d%s", GDIR, tmpName);
--- 440,468 ----
       int extType, i, last;
       char	tmpName[SLEN];	
       
!      last = strlen(buf) - 1;
!      while (buf[last] == ' ') {
!         buf[last] = '\0';
!         last--;
!      }
!      if (is_vms) {
!           /* strip ugly VMS version numbers */
!           last = strlen(buf) - 1;
!           while(last>1 && isdigit(buf[last])) last--;
!           if (last>1 && buf[last] == ';') {
!                buf[last] = '\0';
!                last--;
!           }
!      }
       strcpy(tmpName, buf);
+      /* if buf ends in ".dir", it's a directory, replace ".dir" with "/" */
+      if (is_vms && (last > 3) && (strncmp(buf + last - 3, ".dir", 4) == 0)) {
+           last -= 3;
+           tmpName[last] = '\0';
+           sprintf(buf, "%s/", tmpName);
+           sprintf(theName, "%d%s", GDIR, tmpName);
+           return;
+      }
       if (buf[last] == '/') {
  	  tmpName[last] = '\0';
  	  sprintf(theName, "%d%s", GDIR, tmpName);
***************
*** 521,523 ****
--- 551,758 ----
  }
  
  /*--------------------------------*/
+ 
+ void
+ SendNoPASVFtpQuery(sockfd, query)
+   char *query;
+ {
+      int       sLen, termCh, i;
+      char      *at, *cp;
+      char      inputline[512];
+      char      buf[1600];     /*** Nice MTU size ***/
+      int       tmpfd;
+      int       ftp_control, ftp_data;
+      int       ftp_dataport, nRead;
+      GopherObj *gs;
+      struct sockaddr_in we;
+      char vmspath[256], vmsfile[80];
+      char *beg, *end;
+      int len;
+      int makelower;
+ 
+      /*** hook onto the gs code to do our ftp socket connects ***/
+      gs = GSnew();
+ 
+      /** Get ready for some cleanup action **/
+      signal(SIGPIPE, Cleanup);
+      signal(SIGINT, Cleanup);
+      signal(SIGALRM, Cleanup);
+ 
+      if (DEBUG) printf("The full query was %s\n", query);
+      
+      if ((sLen = strlen(query)) <= 2) Abort(sockfd,"No host name specified.");
+      host = query;
+      at = strchr(query, '@');
+      
+      if (at == NULL) Abort(sockfd, "Not a valid ftp query.");
+      
+      GenerateUniqueFiles(ftptmp);
+      tmpfd = uopen(ftptmp, O_RDWR|O_CREAT,0755);
+      
+      if (tmpfd < 0)
+ 	  Abort(sockfd, "Sorry, out of tmp transfer space!");
+ 
+      thing = at + 1;
+      *at = '\0'; 			/*Sneakily chop it into two strings*/
+ 
+      sLen = strlen(thing);
+      termCh = thing[sLen - 1];		    /* Grab possible end char: / etc */
+      
+      if ((termCh == '*') || (termCh == '@'))  /*  || (termCh == '/')  */
+ 	  thing[sLen - 1] = '\0';
+      
+      if (DEBUG)
+ 	  printf("At this point host: %s   thing: %s\n", host, thing);
+      
+ 
+      /*** Open an ftp control connection ***/
+      GSsetHost(gs, host);
+      GSsetPort(gs, 21);
+      
+      ftp_control = GSconnect(gs);
+      if (ftp_control < 0)
+ 	  Abort(sockfd, "unable to connect to ftp server!");
+ 
+      /*** Strip off the connection message ***/
+      getreply(ftp_control,inputline,sizeof inputline);
+ 
+      if (*inputline != '2')
+ 	  /*** Some sort of error, urg! ***/
+ 	  Abort(sockfd, inputline +3);
+ 
+      /*** Send username ***/
+      writestring(ftp_control, "USER anonymous\r\n");
+      if (DEBUG) printf("Send: USER anonymous CRLF\n");
+ 
+      getreply(ftp_control,inputline,sizeof inputline);
+ 
+      if (*inputline != '3')
+ 	  /*** Some sort of error, urg! ***/
+ 	  Abort(sockfd, inputline +3);
+ 
+      /** Send password***/
+      writestring(ftp_control, "PASS -gopher@");
+      writestring(ftp_control, Zehostname);
+      writestring(ftp_control, "\r\n");
+      if (DEBUG) printf("Send: PASS -gopher@%s\n", Zehostname);
+ 
+      getreply(ftp_control,inputline,sizeof inputline);
+ 
+      if (*inputline != '2')
+ 	  /*** Some sort of error, urg! ***/
+ 	  Abort(sockfd, inputline +3);
+ 
+      if ((ftp_dataport = GSlisten(&we)) < 0)
+        Abort(sockfd, "no clue what socket error!");
+ 
+      sprintf(inputline, "PORT %d,%d,%d,%d,%d,%d\r\n",
+ 	     (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);
+      if (DEBUG) printf("Send: %s", inputline);
+ 
+      writestring(ftp_control, inputline);
+ 
+      getreply(ftp_control,inputline,sizeof inputline);
+ 
+      if (*inputline != '2')
+ 	  /*** Some sort of error, urg! ***/
+ 	  Abort(sockfd, inputline +3);
+ 
+      if (termCh == '/') {	/* We have a directory */
+           if (DEBUG) printf("(It's a directory)\n");
+ 	  gettingFile = 0;
+           /* thing looks like '/dir/dir.../dir/' now. Because Wollongong
+            * wants "CWD dir/dir/dir" and Multinet wants "CWD dir.dir.dir"
+            * we do the CWD in a stepwise fashion. Oh well...
+            */
+           beg = thing + 1;
+ 	  while (beg < thing + strlen(thing) - 2) {
+                end = strchr(beg, '/');
+                strcpy(vmspath, beg);
+                vmspath[end-beg] = '\0';
+ 	       writestring(ftp_control, "CWD ");
+ 	       writestring(ftp_control, vmspath);
+ 	       writestring(ftp_control, "\r\n");
+                if (DEBUG) printf("Send: CWD %s CRLF\n", vmspath);
+ 	       getreply(ftp_control,inputline,sizeof inputline);
+ 	       if (*inputline != '2')
+ 	            Abort(sockfd,inputline + 4);
+                beg = end + 1;
+ 	  }
+           if (DEBUG) printf("Send: %s\n", is_vms ? "NLST" : "NLST -LF");
+ 	  writestring(ftp_control, is_vms ? "NLST\r\n" : "NLST -LF\r\n");
+ 	  getreply(ftp_control,inputline,sizeof inputline);
+           makelower = 1;
+ 
+ 	  if (*inputline != '1')
+ 	       Abort(sockfd,inputline + 4);
+      }
+      else {	                /* We have a file */
+           if (DEBUG) printf("(It's a file)\n");
+ 	  gettingFile = 1;
+ 	  if (gettingBinary = IsBinaryType(thing))  {
+ 	       writestring(ftp_control, "TYPE I\r\n");
+                if (DEBUG) printf("Send: TYPE I\n");
+ 	       getreply(ftp_control,inputline,sizeof inputline);
+ 	       if (*inputline != '2')
+ 		    Abort(sockfd,"Unable to transfer files in binary");
+ 	  }
+ 	  /* thing now looks like '/dir/dir/file.ext' */
+ 	  strcpy(vmspath, thing);
+ 	  len = strlen(vmspath) - 1;
+ 	  while (len > 1 && vmspath[len] != '/') len--;
+           beg = vmspath + 1;
+ 	  while ((end = strchr(beg, '/')) != NULL) {
+                strcpy(vmsfile, beg);
+                vmsfile[end-beg] = '\0';
+ 	       writestring(ftp_control, "CWD ");
+ 	       writestring(ftp_control, vmsfile);
+ 	       writestring(ftp_control, "\r\n");
+                if (DEBUG) printf("Send: CWD %s CRLF\n", vmsfile);
+ 	       getreply(ftp_control,inputline,sizeof inputline);
+                if (*inputline != '2')
+                     Abort(sockfd,inputline + 4);
+                beg = end + 1;
+           }
+           beg = strrchr(thing, '/') + 1;
+ 	  writestring(ftp_control, "RETR ");
+ 	  writestring(ftp_control, beg);
+ 	  writestring(ftp_control, "\r\n");
+           if (DEBUG) printf("Send: RETR %s CRLF\n", beg);
+           makelower = 0;
+ 
+ 	  getreply(ftp_control,inputline,sizeof inputline);
+ 	  if (*inputline != '1')
+ 	       Abort(sockfd,inputline + 4);
+      }
+      ftp_data = GSaccept(ftp_dataport, gs);
+      if (ftp_data < 0)
+ 	  Abort(sockfd,"Unable to establish data connection!");
+      /*** Transfer the data... ***/
+      while ((nRead = read(ftp_data, buf, sizeof buf)) > 0) {
+ 	  if (makelower && is_vms) {
+ 		for (i=0; i<nRead; i++) buf[i] = tolower(buf[i]);
+           }
+ 	  writen(tmpfd, buf, nRead);
+      }
+ 
+      close(ftp_data);
+      
+      getreply(ftp_control,inputline,sizeof inputline);
+ 
+      if (*inputline != '2')
+ 	  Abort(sockfd,inputline + 3);
+ 
+      writestring(ftp_control, "QUIT\r\n");
+ 
+      getreply(ftp_control,inputline,sizeof inputline);
+ 
+      if (*inputline != '2')
+ 	  Abort(sockfd,inputline + 3);
+ 
+      close(ftp_control);
+ }
*** gopherd/gopherd.c.dist	Mon Jan 11 20:07:42 1993
--- gopherd/gopherd.c	Sun Feb 21 23:12:13 1993
***************
*** 1,3 ****
--- 1,10 ----
+ /*
+  * Added support for VMS FTP servers that do not understand the FTP PASV
+  * command (Wollongong, Multinet). Also present these servers output
+  * in a Unix fashion.
+  * Also works with Ultrix 4.2 FTP servers whose PASV command is broken.
+  * Andi Karrer, karrer@bernina.ethz.ch, 21. Feb 93
+  */
  /********************************************************************
   * $Author: lindner $
   * $Revision: 1.2.1.6 $
***************
*** 56,61 ****
--- 63,70 ----
  void LOGGopher();
  
  static	int	uid = -2;
+ int no_pasv = 0;
+ int is_vms  = 0;
  
  extern char *getdesc();
  extern double  maxload;
***************
*** 655,660 ****
--- 664,670 ----
  do_command(sockfd)
    int sockfd;
  {
+      extern int no_pasv;
       char inputline[MAXLINE];
       int length;		/* Length of the command line */
       char logline[MAXLINE];
***************
*** 916,921 ****
--- 926,932 ----
  	  }
  
  	  if (strncmp(selstr, "ftp:",4)==0){
+                no_pasv = 0;
  	       sprintf(logline, "retrieved %s", selstr);
  	       LOGGopher(sockfd, logline);
  
***************
*** 923,928 ****
--- 934,949 ----
  	       TranslateResults(sockfd);
  	       break;
  	  }
+           is_vms = (strncmp(selstr, "ftp-vms:",8) == 0);
+           if ((is_vms) || (strncmp(selstr, "ftp-nop:",8) == 0)) {
+                no_pasv = 1;
+ 	       sprintf(logline, "retrieved %s", selstr);
+ 	       LOGGopher(sockfd, logline);
+ 
+                SendNoPASVFtpQuery(sockfd, selstr+8);
+                TranslateResults(sockfd);
+                break;
+           }
  	  break;
  
       case 'e':
