/*
 *  This code is pretty much the unix adt distribution ftp code
 *   (mostly because I have no idea how to do it myself :-))
 */

#include "adt.h"

#include <libraries/mui.h>
#include <clib/alib_protos.h>

#include <stdarg.h>
#include <stdlib.h>

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <sys/time.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/ftp.h>
#include <arpa/telnet.h>

#include <netdb.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <ios1.h>
#include <ctype.h>

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif

#undef BUFSIZ
#define BUFSIZ 1024

#define s_close(x) CloseSocket(x)
extern int sgetc(int);

char *ftp_connect(char *host, char *path);
int ftp_getreply(int);
int ftp_command(char *, ...);
void ftp_lostpeer(void);
int ftp_login(void);
int ftp_get(char *name, LONG size, char *local);
int ftp_initconn(void);
int ftp_dataconn(void);

int din, fin, fout;
FILE *dout;

BOOL connected = FALSE;
int fdata=-1, pgrp, code;
struct sockaddr_in myctladdr, data_addr;

extern APTR app, get_gauge;

char *login = "flounder", *hostname = "heaven.it.com.au";

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

    if (connected) {
	ftp_command("QUIT");
	if (fout)
	    s_close(fout);
	fout = -1;
	connected = FALSE;
	fdata = -1;
    }

    sp = getservbyname("ftp", "tcp");
    if ((sp = getservbyname("ftp", "tcp")) == NULL)
	return("*This site does not support FTP/TCP");
    hp = gethostbyname(host);
    if ((hp = gethostbyname(host)) == NULL)
	return("ftp_connect(): host unknown");

    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)
	return(sys_errlist[errno]);
    if (connect(s, (struct sockaddr const *)&server, sizeof(server)) < 0)
	return(sys_errlist[errno]);
    len = sizeof(myctladdr);
    if (getsockname(s, (struct sockaddr *)&myctladdr, &len) < 0)
	return(sys_errlist[errno]);
    fout = /*fdopen(s, "w")*/s;
    fin = /*fdopen(s, "r")*/s;
/*    if (fin == NULL ||  fout == NULL) {
	if (fin) fclose(fin);
	if (fout) fclose(fout);
	return(sys_errlist[errno]);
    } */

    /* connected it seems */

    connected = 1;

    if (ftp_getreply(0) > 2) {
	ftp_lostpeer();
	return("Remote protocol error, startup message wrong");
    }
    if (!ftp_login()) {
	ftp_lostpeer();
	return("login failed");
    }

    /* logged in it seems */

    if (ftp_command("TYPE I") != COMPLETE) {
	ftp_lostpeer();
	return("remote host has closed the connected");
    }
    if (ftp_command("CWD %s", path) != COMPLETE) {
	ftp_lostpeer();
	return("Could not CD to path");
    }
    return(NULL);
}

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

    for (;;) {
	dig = n = code = 0;
	while ((c = sgetc(fin)) != '\n') {
	    if (c == IAC) {                 /* Telnet? */
		switch(c = sgetc(fin)) {
		    case WILL:
		    case WONT: {
			char temp[20]; int x;
			c = sgetc(fin);
			x = sprintf(temp, "%c%c%c", IAC, DONT, c);
			send(fout, temp, x, NULL);
			break;
		    }

		    case DO:
		    case DONT: {
			char temp[20]; int x;
			c = sgetc(fin);
			x = sprintf(temp, "%c%c%c", IAC, WONT, c);
			send(fout, temp, x, NULL);
			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 an FTP command */

int ftp_command(char *fmt, ...)
{
    va_list args;
    char comm[256];
    int len;

    va_start(args, fmt);
    len = vsprintf(comm, fmt, args);
    strcpy(&comm[len], "\r\n");
    send(fout, comm, len+2, NULL);
    va_end(args);
    return ftp_getreply(!strncmp(fmt, "QUIT", 4));
}

void ftp_lostpeer()
{
    if (connected) {
	if (fout >= 0) {
	    shutdown(fout, 2);
	    fout = -1;
	}
	if (fdata >= 0) {
	    shutdown(fdata, 2);
	    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)
	n = ftp_command("ACCT %s@%s", login, hostname);
    if (n == COMPLETE)
	return(1);
    return(0);
}

/* return 0 on success */
int ftp_get(char *name, LONG size, char *local)
{
    char *buffer;
    int bytes=0, d=0, c, retval=1;
    ULONG signal;

    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;

    set(get_gauge, MUIA_Gauge_Current, 0);
    set(get_gauge, MUIA_Gauge_InfoText, name);
    set(get_gauge, MUIA_Gauge_Max, size);

    DoMethod(app, MUIM_Application_Input, &signal);
    while ((c = recv(din, buffer, BUFSIZ, NULL)) > 0) {
	if ((d = write(fileno(dout), buffer, c)) != c) break;
	bytes += c;
	if (size != 0)
	    set(get_gauge, MUIA_Gauge_Current, bytes);
	if(DoMethod(app, MUIM_Application_Input, &signal) == ID_GetAbort) {
	    retval = 2;
	    goto abort;
	}
    }
    if (d < c)
	set(get_gauge, MUIA_Gauge_InfoText, "Error Writing file");

    set(get_gauge, MUIA_Gauge_InfoText, NULL);
    free(buffer);
    fclose(dout);
    s_close(din);
    ftp_getreply(0);
    return(0);

abort:
    code = -1;
    free(buffer);
    if (fdata >= 0) {
	close(fdata);
	fdata = -1;
    }
    if (din)
       s_close(din);
    if (dout)
	fclose(dout);
    return(retval);
}

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

    data_addr = myctladdr;
    data_addr.sin_port = 0;
    if (fdata != -1)
	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:
    close(fdata);
    fdata = -1;
    return(1);
}

int ftp_dataconn()
{
    struct sockaddr_in from;
    int s;
    LONG fromlen = sizeof(from);

    s = accept(fdata, (struct sockaddr *)&from, &fromlen);
    if (s < 0) {
	s_close(fdata);
	fdata = -1;
	return(NULL);
    }
    s_close(fdata);
    fdata = s;
    return(fdata);
}
