/* 
 *                  FTPCAT v1.1
 *
 *      This is the first C++ example i wrote. If you have any comments on it
 *  please mail me or use the form on my site.
 *
 *  Ftpcat is a simple program, that allows users to upload and download
 *  files and dirlistings from a ftpserver. Check usage for the commands.
 *
 *  Have fun
 *  
 *  Changes since v1.0:
 *      Now it shows the actual error the ftpd returns.
 *
 *  -lamagra (access-granted@geocities.com)
 *  http://lamagra.seKure.de
 *
 * HOWTO compile: c++ ftpcat.cpp -o ftpcat
 */


/* INCLUDES */
#include <stdlib.h>
#include <iostream.h>
#include <stdio.h>
#include <stdarg.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h> // inet_addr()
#include <string.h> // strerror()
#include <ctype.h> // isdigit()
#include <fcntl.h>

/* DEFINES */
#define ANON_PASS "Ftpcat@lamagra.seKure.de"

/* PROTOCOLS + CLASSES */
void error_quit(char *msg,...);
extern int errno;
extern int optind;
extern char *optarg;
char *host, *user,*path;

class ftp
{
        int ftpsock;
  public:
        long port;
        int list;

        set_default();
        connectto(char *host);
        login(char *user);
        disconnect();
        unsigned long resolve(char *host);
        void sendcmd(char *text, ...);
        char *get_error();
        int get_response();
        int get_file();
        int put_file();
        int dataconn();
};

/* FUNCTIONS */
void error_quit(char *msg,...)
{
        va_list va;

        va_start(va, msg);
        vfprintf(stderr, msg, va);
        va_end(va);
        exit(-1);
}

usage(char *progname)
{
    printf("Ftpcat by lamagra (http://lamagra.seKure.de)\n");
    printf(
            "Usage: %s [options] user@host:port/path/(file/dir)\n"
            "\t port is optional\n"
            "\t -h and -?: this text\n"
            "\t -l: show dir-contents\n"
            "\t -p: put a file\n"
          );
    exit(0);
}

ftp::set_default()
{
  port = 21, list = 0;
}

ftp::connectto(char *host)
{
        struct sockaddr_in sin;

        sin.sin_addr.s_addr = resolve(host);
        sin.sin_port = htons(port);
        sin.sin_family = AF_INET;

        if((ftpsock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1)
                error_quit("Can't open socket: %s\n",strerror(errno));

        if(connect(ftpsock,(struct sockaddr *)&sin,sizeof(struct sockaddr)) == -1)
                error_quit("Can't connect to %s.%ld: %s\n",host,port,strerror(errno));

    //    fcntl(ftpsock,F_SETFL,O_NONBLOCK);
}

ftp::disconnect()
{
        sendcmd("QUIT\r\n");
        close(ftpsock);
}

ftp::login(char *user)
{
    char *passwd;
    int gotpass = 0;

    if(!strcmp("ftp",user) || !strcmp("anonymous",user))
        passwd = ANON_PASS;
    else /* Prompt user for password */
        passwd = getpass("Please enter password: "),gotpass = 1;

    if(get_response() != 220) error_quit("No welcomebanner\n");
    sendcmd("USER %s\r\n",user);
    if(get_response() != 331) error_quit("USER %s failed: %s\n",user,get_error());
    sendcmd("PASS %s\r\n",passwd);
    if(get_response() != 230) error_quit("PASS **** failed: %s\n",get_error());
    if(gotpass) memset(passwd,0x0,strlen(passwd)); // zero passwd 
}

unsigned long ftp::resolve(char *name)
{
      struct hostent *hp;
      unsigned long ip;

      if((ip = inet_addr(name)) == -1)
      {
        if((hp = gethostbyname(name)) == NULL)
        {
            printf("Unable to resolve <%s>\n",name);
            exit(-1);
        }
        memcpy(&ip,hp->h_addr,4);
      }
      return ip;
}

void ftp::sendcmd(char *text, ...)
{
    va_list va;
    char buf[1024];

    va_start(va,text);
    vsnprintf(buf,1024,text,va);
    va_end(va);

    if(buf[strlen(buf) - 1] != '\n') 
        error_quit("Send: text doesn't end with \\n");
  
    if(write(ftpsock, buf, strlen(buf)) == -1)
        error_quit("Write error: %s\n",strerror(errno));
}

int ftp::get_response()
{
    char response[4];
    char tmp;
    int i = 0;

    while(read(ftpsock,(char *)&tmp,1) == 1)
    {
        response[i++] = tmp;
        if(i > 3)
        {
            if(response[3] != ' ' || !isdigit(response[0]) || !isdigit(response[1]) || !isdigit(response[2]))
            {
                while(read(ftpsock,(char *)&tmp,1) == 1 && tmp != '\n');
                i = 0;
            }
            else
            {
                response[3] =  0x0;
//                    error_quit("Server send bad response: %s\n",response);
                return atoi(response);
            }
        }
    }
}

char *ftp::get_error()
{
    static char errorbuf[1024];
    
    if(read(ftpsock, errorbuf, sizeof(errorbuf)) == -1)
        error_quit("Read error: %s\n",strerror(errno));

    return errorbuf;
}

int ftp::dataconn()
{
    int fd;
    unsigned int len = sizeof(struct sockaddr);
    struct sockaddr_in sin;
    char *a, *b;

    if((fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1)
        error_quit("Can't open socket: %s\n",strerror(errno));

    /* Get the address from the ftpsock */
    if(getsockname(ftpsock,(struct sockaddr *)&sin,&len) == -1)
        error_quit("Getsockname failed: %s\n",strerror(errno));
    sin.sin_port = 0;

    if(bind(fd,(struct sockaddr *)&sin,sizeof(struct sockaddr)) == -1)
        error_quit("Can't bind to port: %s",strerror(errno));

    if(getsockname(fd,(struct sockaddr *)&sin,&len) == -1)
        error_quit("Getsockname failed: %s\n",strerror(errno));

    listen(fd,1);

    a = (char *)&sin.sin_addr;
    b = (char *)&sin.sin_port;
#define UC(x) (((int)x)&0xff)
 
    sendcmd("PORT %d,%d,%d,%d,%d,%d\r\n",
                UC(a[0]),UC(a[1]),UC(a[2]),UC(a[3]),
                UC(b[0]),UC(b[1]));
    if(get_response() != 200) error_quit("PORT failed: %s\n",get_error());

    sendcmd("TYPE I\r\n");
    if(get_response() != 200) error_quit("TYPE failed: %s\n",get_error());

    return fd;
}

int ftp::get_file()
{
    char *file;
    struct sockaddr_in sin;
    int clientfd;
    unsigned int len =sizeof(struct sockaddr);
    int fd = dataconn();

    if(list) // Get dirlisting
    {
        sendcmd("CWD %s\r\n",path);
        if(get_response() != 250) error_quit("CWD failed: %s",get_error());
        sendcmd("LIST -al\r\n");
    }
    else
    {
        if((file = (char *)strrchr(path,'/')))
        {
            *file++ = 0x0;
            sendcmd("CWD %s\r\n",path);
            if(get_response() != 250) error_quit("CWD failed: %s",get_error());
        }else file = path;
        sendcmd("RETR %s\r\n",file); 
        if(get_response() == 550) error_quit ("RETR failed: %s",get_error());
    }
    
    if((clientfd = accept(fd,(struct sockaddr *)&sin,&len)) == -1)
        error_quit("Accept() failed: %s\n",strerror(errno));
    close(fd);

    //  fcntl(ftpsock,F_SETFL,O_NONBLOCK);
    return clientfd;
}

int ftp::put_file()
{
    int fd = dataconn();
    struct sockaddr_in sin;
    int clientfd;
    unsigned int len = sizeof(struct sockaddr);
    char *file;

    if((file = (char *)strrchr(path,'/')))
    {
        *file++ = 0x0;
        sendcmd("CWD %s\r\n",path);
        if(get_response() != 250) error_quit("CWD failed: %s",get_error());
    }else file = path;

    sendcmd("STOR %s\r\n",file); 
    if(get_response() == 550) error_quit ("STOR failed: %s",get_error());
    
    if((clientfd = accept(fd,(struct sockaddr *)&sin,&len)) == -1)
        error_quit("Accept() failed: %s\n",strerror(errno));
    close(fd);

    //  fcntl(ftpsock,F_SETFL,O_NONBLOCK);
    return clientfd;
}
int ftpdecode(char *string,ftp *obj)
{
   char *tmp;

   if((tmp = (char *)strchr(string,'/')))
       *tmp = 0x0, path = ++tmp;
   else return -1;
       
   if((tmp = (char *)strchr(string,':')))
       *tmp = 0x0, obj->port = atol(++tmp);
       
   if((tmp = (char *)strchr(string,'@')))
       *tmp = 0x0, host = ++tmp;
   else return -1;    
       
   user = string;
   return 0;   
}

int main(int argc,char **argv)
{
    ftp obj;
    char c, buf[1024];
    int datafd, len, cmd = 0;

    obj.set_default();
    
    while((c = getopt(argc,argv,"h?lp")) != EOF)
    {
        switch(c)
        {
            case 'h':
            case '?': usage(argv[0]);
                      break;
            case 'l': obj.list = 1;
                      break;
            case 'p': cmd = 1;
                      break;
            case 'd': cmd = 2;
            default: error_quit("Unknown option: %c\n",c);
        }
    }

    if((argc - optind) != 1) usage(argv[0]);
    if(ftpdecode(argv[optind],&obj) == -1)
        error_quit("Bad user@host:port/path string\n");

    obj.connectto(host);
    obj.login(user);
    
    if(!cmd)
    {
        datafd = obj.get_file();
        while(len = read(datafd,buf,1024))
        {
            if(len == -1) error_quit("Read() failed: %s\n",strerror(errno));
            write(STDOUT_FILENO,buf,len);
            memset(buf,0x0,1024);
        }    
    } else
    {
        datafd = obj.put_file();
        printf("[ Ready for datainput ]\n");
        while(len = read(STDIN_FILENO,buf,1024))
        {
            if(len == -1) error_quit("Read() failed: %s\n",strerror(errno));
            write(datafd,buf,len);
            memset(buf,0x0,1024);
        }    
    } 
    obj.disconnect();

    return 0;
}

