/*********************************************************************
 *
 *   This is ADT, a user friendly frontend for Aminet FTP sites.
 *
 *********************************************************************/

#include <stdlib.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/telnet.h>
#include <arpa/ftp.h>
#include <errno.h>
#include <varargs.h>

int
  ftp_get(),
  ftp_getreply(),
  ftp_connect(),
  ftp_login(),
  ftp_initconn(),
  ftp_command();

void
  ftp_lostpeer();

FILE
  *ftp_dataconn();

FILE
  *din,
  *dout,
  *fin,
  *fout;

int
  connected = 0,
  fdata = -1,
  pgrp,
  code;

struct sockaddr_in
  myctladdr,
  data_addr;

extern char
  *login,
  *sys_errlist[],
  Result[],
  hostname[MAXHOSTNAMELEN];

extern void
  (* adt_status)(),
  (* adt_quit)();

int ftp_connect(host,path)
     char
       *host,
       *path;
{
  struct sockaddr_in server;
  struct servent *sp;
  struct hostent *hp;
  int s,len;

   if (connected) {        /* connected, close the connection */
      (void) ftp_command("QUIT");
      if (fout)
         (void) fclose(fout);
      fout = NULL;
      connected = 0;
      fdata = -1;
   }

  sp = getservbyname("ftp","tcp");
  if ((sp = getservbyname("ftp","tcp")) == NULL) {
    (* adt_quit)("This site does not support FTP/TCP"); /* Never returns */
  }
  hp=gethostbyname(host);
  if ((hp=gethostbyname(host)) == NULL) {
    sprintf(Result,"ftp_connect(): %s unknown",host);
    return(0);
  }
  bzero((char *)&server,sizeof(server));
  bcopy(hp->h_addr,(char *)&server.sin_addr,hp->h_length);
  server.sin_family = hp -> h_addrtype;
  server.sin_port = sp ->s_port;
  s = socket(AF_INET,SOCK_STREAM,0);
  if (s<0) {
    sprintf(Result,"ftp_connect(): %s",sys_errlist[errno]);
    return (0);
  }
  if (connect(s,(char *)&server,sizeof(server))<0) {
    sprintf(Result,"ftp_connect(): %s",sys_errlist[errno]);
    return (0);
  }
  len = sizeof (myctladdr);
  if (getsockname(s,(struct sockaddr *)&myctladdr, &len) <0 ) {
    sprintf(Result,"ftp_connect(): %s",sys_errlist[errno]);
    return(0);
  }
  fout = fdopen(s,"w");
  fin = fdopen(s,"r");
  if (fin == NULL || fout == NULL) {
    sprintf(Result,"ftp_connect(): %s",sys_errlist[errno]);
    if (fin)
      fclose(fin);
    if (fout)
      fclose(fout);
    return(0);
  }

  /* wow, made it through the entire connection, now to the login */

  connected = 1;

  if (ftp_getreply(0) >2 ) {
    ftp_lostpeer();
    strcpy(Result,"Remote protocol error, startup message wrong");
    return (0);
  }
  if (!ftp_login()) {
    ftp_lostpeer();
    sprintf(Result,"%s: login failed",host);
    return(0);
  }
  /* we're logged in */

  if (ftp_command("TYPE I")!=COMPLETE){
    ftp_lostpeer();
    sprintf(Result,"%s has closed the connection",host);
    return(0);
  }
  if (ftp_command("CWD %s",path) != COMPLETE) {
    ftp_lostpeer();
    sprintf(Result,"Could not CD to %s",path);
    return(0);
  }
  sprintf(Result,"%s: connection established.",host);
  return (1);
}

int ftp_getreply(expecteof)
     int
       expecteof;
{
   int c, n;
   int dig;
   int ocode = 0, cont = 0;

  for (;;) {
    dig = n = code = 0;
    while ((c = getc(fin)) != '\n') {
      if (c == IAC) {     /* Telnet */
	switch (c = getc(fin))
	  {
	  case WILL:
	  case WONT:
	    c = getc(fin);
	    fprintf(fout, "%c%c%c", IAC, DONT, c);
	    (void) fflush(fout);
	    break;
	  case DO:
	  case DONT:
	    c = getc(fin);
	    fprintf(fout, "%c%c%c", IAC, WONT, c);
	    (void) fflush(fout);
	    break;
	  default:
	    break;
	  }
	continue;
      }
      dig++;
      if (c == EOF) {
	if (expecteof) {
	  code = 221;
	  return (0);
	}
	ftp_lostpeer();
	code = 421;
	return(4);
      }
      if (dig < 4 && isdigit(c))
	code = code * 10 + (c - '0');
      if (dig == 4 && c == '-') {
	if (cont)
	  code = 0;
	cont++;
      }
      if (n == 0)
	n = c;
    }
    if (cont && code != ocode) {
      if (ocode == 0)
	ocode = code;
      continue;
    }
    if (code == 421 || ocode == 421)
      ftp_lostpeer();
    return (n - '0');
  }
}

/* send out an FTP command */

int ftp_command(va_alist)
     va_dcl
{
  va_list args;
  char *fmt;

  va_start(args);
  fmt = va_arg(args, char *);
  vfprintf(fout,fmt,args);
  va_end(args);
  fprintf(fout, "\r\n");
  (void) fflush(fout);
  return ftp_getreply(!strncmp(fmt,"QUIT",4));
}

void
  ftp_lostpeer()
{
  if (connected) {
    if (fout != NULL) {
      (void) shutdown(fileno(fout),2);
      (void) fclose(fout);
      fout=NULL;
    }
    if (fdata >= 0) {
      (void) shutdown(fdata,2);
      (void) close(fdata);
      fdata = -1;
    }
    connected =0;
  }
}

int ftp_login()
{
  int n;

  n = ftp_command("USER ftp");
  if (n == CONTINUE)
    n = ftp_command ("PASS %s@%s",login,hostname);
  if (n == CONTINUE)       /* rarely needed */
    n = ftp_command ("ACCT %s@%s",login,hostname);
  if (n == COMPLETE)
    return (1);
  return (0);
}

/* return 0 on success */
int ftp_get(name, size, local)
     char
       *local,   /* local name */
       *name;    /* FULL path to file */
     long size;     /* size, 0 = size unknown, don't do progress report */
{
  char
    *buffer;
  int
    bytes = 0,
    d = 0,
    c;

  if (ftp_initconn()) {
    code = -1;
    return 1;
  }
  if (ftp_command("RETR %s",name) != PRELIM)
    return 1;
  din = ftp_dataconn();
  if (din == NULL)
    goto abort;
  dout = fopen(local, "w");
  if (dout == NULL)
    goto abort;
  buffer = malloc(BUFSIZ);
  if (buffer == NULL)
    goto abort;
  while ((c = read(fileno(din), buffer, BUFSIZ)) > 0) {
    if ((d = write(fileno(dout), buffer, c)) != c)
      break;
    bytes += c;
    if (size != 0 )
      (* adt_status)("Downloading %s, %3dK of %3dK",
		  local,(bytes+512)/1024,(size+512)/1024);
  }
  if (d < c)
    (* adt_status)("Error downloading %s",name);
  (void) fclose(dout);
  (void) fclose(din);
  (void) ftp_getreply(0);   /* Get report msg, trash it */
  return 0;
 abort:
  code = -1;
  if (fdata >= 0) {
    (void) close(fdata);
    fdata = -1;
  }
  if (din)
    (void) fclose(din);
  if (dout)
    (void) fclose(dout);
  return 1;
}

int ftp_initconn()
{
  register char *p, *a;
  int result, len;

  data_addr = myctladdr;
  data_addr.sin_port = 0;	/* let system pick one */
  if (fdata != -1)
    (void) close(fdata);
  fdata = socket(AF_INET, SOCK_STREAM, 0);
  if (fdata < 0)
    return (1);
  if (bind(fdata, (struct sockaddr *)&data_addr, sizeof (data_addr)) < 0)
    goto bad;
  len = sizeof (data_addr);
  if (getsockname(fdata, (struct sockaddr *)&data_addr, &len) < 0)
    goto bad;
  listen(fdata, 1);
  a = (char *)&data_addr.sin_addr;
  p = (char *)&data_addr.sin_port;

#define	UC(b)	(((int)b)&0xff)

  result = ftp_command("PORT %d,%d,%d,%d,%d,%d",
            UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
	         UC(p[0]), UC(p[1]));
  return (result != COMPLETE);

 bad:
  (void) close(fdata);
  fdata = -1;
  return (1);
}

FILE *
  ftp_dataconn()
{
  struct sockaddr_in from;
  int s, fromlen = sizeof (from);

  s = accept(fdata, (struct sockaddr *) &from, &fromlen);
  if (s < 0) {
    (void) close(fdata), fdata = -1;
    return (NULL);
  }
  (void) close(fdata);
  fdata = s;
  return (fdopen(fdata, "r"));
}
