/********************************************************************
 * $Author: lindner $
 * $Revision: 1.2 $
 * $Date: 1993/01/11 23:18:09 $
 * $Source: /home/mudhoney/GopherSrc/release1.11/gopherd/RCS/ftp.c,v $
 * $Status: $
 *
 * 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.2  1993/01/11  23:18:09  lindner
 * Changed password to be the host of the remote client, not the gateway.
 *
 * Revision 1.1  1992/12/10  23:13:27  lindner
 * gopher 1.1 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.
 *
 ---------------------------------------------------- */

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

#include <stdio.h>


#define GFILE	0		/* Gopher item types */
#define GDIR	1
#define GBINHEX	4
#define GDOSB	5
#define GUNIXB	9
#define GIMAGE  "I"
#define GSOUND  "s"

#define SLEN	255	/* Generic small buffer length */
#define	TMOUT	180	/* 3 minutes is plenty long enough */

int     GLOBALsockfd;   /* For cleanup */
char	*appname;
char	ftptmp[SLEN] = LIST;
char	query[BUFSIZ];				/* input redirected by inetd */
char 	*host, *thing;				/* pointers into query */
int	gettingFile = 1;
int	gettingBinary = 0;
int	childpid;

/*** Some forward declarations ***/
boolean NotText();
int     Abort();
boolean IsBinaryType();
void    GenerateUniqueFiles();
void    GopherType();
void    Cleanup();
void    RoundEmUp();
void    FailErr();
int     getreply();
/**************************/

int
getreply(theStream,inputline,maxlen)
  int theStream;
  char *inputline;
  int maxlen;
{
     int code = 0;
     char origcode[4];
     

     readline(theStream, inputline, maxlen);
     if (DEBUG) printf(inputline);
     strncpy(origcode, inputline, 3);  /*** Stash away the original code ***/

     while (inputline[3] == '-') {
	  if (readline(theStream, inputline, maxlen)<=0)
	       return(-1);
	  if (DEBUG) printf(inputline);

	  if (!isdigit(inputline[0]) && !isdigit(inputline[1]) &&
	      !isdigit(inputline[2]))
	       inputline[3] = '-';
	  else
	       /*** Keep going if response code isn't the same as original **/
	       if (strncmp(origcode, inputline, 3) != 0)
		    inputline[3] = '-';
     }
     return (0);
}

void
SendFtpQuery(sockfd, query)
  char *query;
{
     int       sLen, termCh;
     char      *at, *cp;
     char      inputline[512];
     char      ipnum[64];
     char      buf[1600];     /*** Nice MTU size ***/
     int       tmpfd;
     int       ftp_control, ftp_data;
     int       ftp_dataport, nRead;
     GopherObj *gs;

     /*** 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");

     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@");
     inet_netnames(sockfd, inputline, ipnum);
     writestring(ftp_control, inputline);
     writestring(ftp_control, "\r\n");
     if (DEBUG) printf("Password was -gopher@%s\n", inputline);
     getreply(ftp_control,inputline,sizeof inputline);

     if (*inputline != '2')
	  /*** Some sort of error, urg! ***/
	  Abort(sockfd, inputline +3);

     /**** Send PASV and set up the data port ***/
     writestring(ftp_control, "PASV\r\n");

     getreply(ftp_control,inputline,sizeof inputline);

     if (strncmp(inputline, "227", 3))
	  Abort(sockfd,inputline +3);
     
     /*** Find out the port number of the data connection ***/
     inputline[strlen(inputline)] = '\0';  /** Zap the right paren **/
     cp = strrchr(inputline, ',');         /** lower order octet **/
     if (cp == NULL)  Abort(sockfd,"PASV failed- cannot ftp!");
     *cp = '\0';
     cp ++;
     ftp_dataport = atoi(cp);

     cp = strrchr(inputline, ',');         /** upper octet **/
     if (cp == NULL)  Abort(sockfd,"PASV failed- cannot ftp!");
     *cp = '\0';
     cp ++;
     ftp_dataport = atoi(cp) * 256 + ftp_dataport;
     if (DEBUG) printf("Data port is %d\n", ftp_dataport);

     GSsetPort(gs, ftp_dataport);
     ftp_data = GSconnect(gs);
     if (ftp_data < 0)
	  Abort(sockfd,"Unable to establish data connection!");

     
     if (termCh == '/') {	/* We have a directory */
	  gettingFile = 0;

	  writestring(ftp_control, "NLST -LF ");
	  if (strlen(thing) > 0)
	       writestring(ftp_control, thing);
	  writestring(ftp_control, "\r\n");

	  getreply(ftp_control,inputline,sizeof inputline);

	  if (*inputline != '1')
	       Abort(sockfd,inputline + 4);
     }
     else {	                /* We have a file */
	  gettingFile = 1;
	  if (gettingBinary = IsBinaryType(thing))  {
	       writestring(ftp_control, "TYPE I\r\n");
	       getreply(ftp_control,inputline,sizeof inputline);
	       if (*inputline != '2')
		    Abort(sockfd,"Unable to transfer files in binary");
	  }
	  writestring(ftp_control, "RETR ");
	  writestring(ftp_control, thing);
	  writestring(ftp_control, "\r\n");

	  getreply(ftp_control,inputline,sizeof inputline);
	  if (*inputline != '1')
	       Abort(sockfd,inputline + 4);
     }

     /*** Transfer the data... ***/
     while ((nRead = read(ftp_data, buf, sizeof buf)) > 0)
	  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);
}

/*--------------------------------*/
void
TranslateResults(sockfd)
  int sockfd;
{
     FILE  *fp, *OpenOrDie();
     char  buf[BUFSIZ], theName[SLEN];
     int   fd, nRead, checkIt;
     char  outputline[512];
     
     checkIt = 1;
     
     signal(SIGPIPE, Cleanup);
     signal(SIGINT, Cleanup);

     if (gettingFile) {
	  if (gettingBinary) {				/* icky binary file */
	       if (DEBUG)
		    printf("Whoa!  That's a binary file\n");
	       fp = OpenOrDie(ftptmp, "r");
	       fd = fileno(fp);
	       
	       if (DEBUG)
		    printf("fd %d\n",fd);
	       while ((nRead = read(fd, buf, sizeof buf)) > 0)
                    writen(sockfd, buf, nRead);
	       
	       writen(sockfd, buf, nRead);
	       fclose(fp);
	  } else {		    /* must be a nice texty file */
	       fp = OpenOrDie(ftptmp, "r");
	       while (fgets(buf, sizeof buf, fp) != NULL) {
		    if (checkIt) { /* Just peek at it once */
			 checkIt = 0;
			 if (NotText(buf)) {
			      fclose(fp);
			      Abort(sockfd,"Sorry.  File does not appear to contain text.");
			 }
		    }
		    ZapCRLF(buf);
		    FailErr(writestring(sockfd, buf));
		    FailErr(writestring(sockfd, "\r\n"));
	       }
	       fclose(fp);
	       FailErr(writestring(sockfd,".\r\n"));
	  }
     } else {					   /* Must be a directory */
	  fp = OpenOrDie(ftptmp, "r");
	  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));
	  }
	  fclose(fp);
	  FailErr(writestring(sockfd, ".\r\n"));
     }

     Cleanup();
}

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

FILE *OpenOrDie(file, mode)
  char *file, *mode;
{
     FILE *fp, *fopen();
     if ((fp = ufopen(file, mode,0755)) != NULL) {
	  return(fp);
     } else {
	  Cleanup();
	  exit(-1);
     }
     return(NULL);  /** Shouldn't get here **/
}


/*--------------------------------*/
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);
}

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

int
Abort(sockfd, complaint)
  int sockfd;
  char *complaint;
{
     char errmsg[512];
     sprintf(errmsg, "3 Error: %s\r\n.\r\n", complaint); 
     writestring(sockfd, errmsg);
     fflush(stdout);
     Cleanup();
     exit(1);
}

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

boolean
IsBinaryType(thing)
  char *thing;
{
     static char *binExt[] = {
	  ".zip", ".zoo", ".arj", ".arc", ".lzh", ".hyp", ".pak", ".exe", ".com",
	  ".ps", ".gif", ".pict", ".pct", ".tiff", ".tif", ".tar", ".Z", ".pict", ".au"
	  };
     
     int extType, i;
     
     for (extType = 0; extType < 19; extType++) { 
	  i = strcasecmp(thing + strlen(thing) - strlen(binExt[extType]),
			 binExt[extType]);
	  if (i == 0) return(TRUE);
     }
     return(FALSE);
     
}

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

void
GenerateUniqueFiles(tmpList)
  char *tmpList;
{
     char *s;
     int pid;
     
     pid = getpid();
     s = strchr(tmpList, '+');
     sprintf(s, "%d", pid);
}

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

void
GopherType(buf, theName)
  char *buf, *theName;
{
     static char ext4[] = ".hqx";
     static char *exts[] = {".au", ".snd"};
     static char *extI[] = {".gif", ".pict", ".tiff", ".tif", ".pcx"};
     static char *ext5[] = {".zip", ".zoo", ".arj", ".arc", ".lzh", ".hyp", 
				 ".pak", ".exe", ".com", ".ps", ".pct"};
     static char *ext9[] = {".tar", ".Z"};
     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);
	  return;
     }
     if ((buf[last] == '*') || (buf[last] == '@')) {	/* Hack out * and @ */
	  buf[last] = '\0';
	  tmpName[last] = '\0';
     }
     
     /* At this point we're looking at a file */
     if (strcasecmp(buf + strlen(buf) - strlen(ext4), ext4) == 0) { /* BinHex? */
	  sprintf(theName, "%d%s", GBINHEX, tmpName);
	  return;
     }
     
     for (extType = 0; extType < 11; extType++) {  /* PC garbage? */ 
	  i = strcasecmp(buf + strlen(buf) - strlen(ext5[extType]), 
			 ext5[extType]);
	  if (i == 0) {
	       sprintf(theName, "%d%s", GDOSB, tmpName);
	       return;
	  }
     }
     
     for (extType = 0; extType < 2; extType++) {	/* unix binary? */ 
	  i = strcasecmp(buf + strlen(buf) - strlen(ext9[extType]), 
			 ext9[extType]);
	  if (i == 0) {
	       sprintf(theName, "%d%s", GUNIXB, tmpName);
	       return;
	  }
     }

     for (extType = 0; extType < 5; extType++) {  /** Image? **/
	  i = strcasecmp(buf + strlen(buf) - strlen(extI[extType]), 
			 extI[extType]);
	  if (i == 0) {
	       sprintf(theName, "%s%s", GIMAGE, tmpName);
	       return;
	  }
     }	  

     for (extType = 0; extType < 2; extType++) {  /** Image? **/
	  i = strcasecmp(buf + strlen(buf) - strlen(exts[extType]), 
			 exts[extType]);
	  if (i == 0) {
	       sprintf(theName, "%s%s", GSOUND, tmpName);
	       return;
	  }
     }	  
     
     sprintf(theName, "%d%s", GFILE, tmpName);
     return;		/* Some other and hopefully text file */
}

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

void
Cleanup()
{
     unlink(ftptmp);
     if (DEBUG)
	  printf("Cleaning up %s\n", ftptmp);

     exit(1);
}

/*--------------------------------*/
void
RoundEmUp()
{
     
     kill(childpid, SIGKILL);
     Cleanup();
}

/*--------------------------------*/
void
FailErr(result)
  int result;
{
     if (result < 0) {
	  Cleanup();
     }
}

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