foo
authorJohn Tsiombikas <nuclear@member.fsf.org>
Sat, 21 Jan 2023 12:09:56 +0000 (14:09 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Sat, 21 Jan 2023 12:09:56 +0000 (14:09 +0200)
src/ftp.c
src/ftp.h

index 961eea3..6a29a77 100644 (file)
--- a/src/ftp.c
+++ b/src/ftp.c
@@ -4,6 +4,7 @@
 #include <stdarg.h>
 #include <errno.h>
 #include <ctype.h>
+#include <time.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #define fcntlsocket            fcntl
 #endif
 
+#define TIMEOUT        5
+
+static int newconn(struct ftp *ftp);
 static int sendcmd(struct ftp *ftp, const char *fmt, ...);
 static int handle_control(struct ftp *ftp);
 static int handle_data(struct ftp *ftp, int s);
 static void proc_control(struct ftp *ftp, const char *buf);
 
-static void cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls);
-static void cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls);
+static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls);
+static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls);
+static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls);
+
+static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls);
+static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls);
 
 
 struct ftp *ftp_alloc(void)
@@ -35,7 +43,7 @@ struct ftp *ftp_alloc(void)
        if(!(ftp = calloc(1, sizeof *ftp))) {
                return 0;
        }
-       ftp->ctl = ftp->data = -1;
+       ftp->ctl = ftp->data = ftp->lis = -1;
        return ftp;
 }
 
@@ -89,40 +97,205 @@ void ftp_close(struct ftp *ftp)
                ftp->ctl = -1;
                ftp->num_crecv = 0;
        }
+       if(ftp->lis >= 0) {
+               closesocket(ftp->lis);
+               ftp->lis = -1;
+       }
        if(ftp->data >= 0) {
                closesocket(ftp->data);
                ftp->data = -1;
-               ftp->num_drecv = 0;
        }
 }
 
 int ftp_sockets(struct ftp *ftp, int *sockv, int maxsize)
 {
-       if(ftp->ctl >= 0 && maxsize) {
-               *sockv++ = ftp->ctl;
-               maxsize--;
+       int *sptr = sockv;
+       if(ftp->ctl >= 0 && maxsize-- > 0) {
+               *sptr++ = ftp->ctl;
        }
-       return 1;
+       if(ftp->lis >= 0 && maxsize-- > 0) {
+               *sptr++ = ftp->lis;
+       }
+       if(ftp->data >= 0 && maxsize-- > 0) {
+               *sptr++ = ftp->data;
+       }
+       return sptr - sockv;
 }
 
-int ftp_pending(struct ftp *ftp)
+static void exec_op(struct ftp *ftp, int op, const char *arg)
+{
+       switch(op) {
+       case FTP_PWD:
+               ftp_pwd(ftp);
+               break;
+
+       case FTP_CHDIR:
+               ftp_chdir(ftp, arg);
+               break;
+
+       case FTP_MKDIR:
+               ftp_mkdir(ftp, arg);
+               break;
+
+       case FTP_RMDIR:
+               ftp_rmdir(ftp, arg);
+               break;
+
+       case FTP_DEL:
+               ftp_delete(ftp, arg);
+               break;
+
+       case FTP_LIST:
+               ftp_list(ftp);
+               break;
+
+       case FTP_RETR:
+               ftp_retrieve(ftp, arg);
+               break;
+
+       case FTP_STORE:
+               ftp_store(ftp, arg);
+               break;
+
+       default:
+               break;
+       }
+}
+
+static void exec_queued(struct ftp *ftp)
+{
+       struct ftp_op *fop;
+
+       if(!(fop = ftp->qhead)) {
+               return;
+       }
+
+       if(ftp->qtail == fop) {
+               ftp->qhead = ftp->qtail = 0;
+       } else {
+               ftp->qhead = ftp->qhead->next;
+       }
+
+       exec_op(ftp, fop->op, fop->arg);
+
+       free(fop->arg);
+       free(fop);
+}
+
+
+int ftp_queue(struct ftp *ftp, int op, const char *arg)
+{
+       struct ftp_op *fop;
+
+       if(!ftp->qhead) {
+               exec_op(ftp, op, arg);
+               return 0;
+       }
+
+       if(!(fop = malloc(sizeof *fop))) {
+               return -1;
+       }
+       if(!(fop->arg = strdup(arg))) {
+               free(fop);
+               return -1;
+       }
+       fop->op = op;
+       fop->next = 0;
+
+       ftp->qtail->next = fop;
+       ftp->qtail = fop;
+       return 0;
+}
+
+int ftp_waitresp(struct ftp *ftp, time_t timeout)
 {
-       int maxfd = -1;
        fd_set rdset;
-       struct timeval tv = {0, 0};
+       struct timeval tv;
+       time_t start;
 
-       FD_ZERO(&rdset);
-       if(ftp->ctl >= 0) {
+       ftp->last_resp = -1;
+       start = time(0);
+
+       for(;;) {
+               FD_ZERO(&rdset);
                FD_SET(ftp->ctl, &rdset);
-               maxfd = ftp->ctl;
+
+               if(timeout >= 0) {
+                       tv.tv_sec = timeout;
+                       tv.tv_usec = 0;
+               }
+
+               if(select(ftp->ctl + 1, &rdset, 0, 0, timeout >= 0 ? &tv : 0) == -1 && errno == EINTR) {
+                       continue;
+               }
+
+               if(FD_ISSET(ftp->ctl, &rdset)) {
+                       ftp->last_resp = -1;
+                       ftp_handle(ftp, ftp->ctl);
+                       if(ftp->last_resp) {
+                               break;
+                       }
+               }
+
+               if(timeout > 0) {
+                       timeout -= time(0) - start;
+                       if(timeout <= 0) {
+                               return -1;
+                       }
+               }
+
+       }
+       return ftp->last_resp;
+}
+
+static int ftp_active(struct ftp *ftp)
+{
+       struct sockaddr_in sa = {0};
+       socklen_t len;
+       unsigned long addr;
+       unsigned short port;
+
+       if(ftp->lis >= 0) {
+               closesocket(ftp->lis);
        }
        if(ftp->data >= 0) {
-               FD_SET(ftp->data, &rdset);
-               if(ftp->data > maxfd) maxfd = ftp->data;
+               closesocket(ftp->data);
+               ftp->data = -1;
+       }
+
+       if((ftp->lis = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
+               errmsg("ftp_active: failed to create listening socket\n");
+               return -1;
+       }
+
+       sa.sin_family = AF_INET;
+       sa.sin_port = 0;
+       sa.sin_addr.s_addr = htonl(INADDR_ANY);
+
+       if(bind(ftp->lis, (struct sockaddr*)&sa, sizeof sa) == -1) {
+               errmsg("ftp_active: failed to bind listening socket\n");
+               closesocket(ftp->lis);
+               ftp->lis = -1;
+               return -1;
        }
-       if(maxfd == -1) return 0;
+       listen(ftp->lis, 1);
 
-       return select(maxfd + 1, &rdset, 0, 0, &tv) > 0 ? 1 : 0;
+       len = sizeof sa;
+       if(getsockname(ftp->ctl, (struct sockaddr*)&sa, &len) == -1) {
+               errmsg("ftp_active: failed to retrieve listening socket address\n");
+               closesocket(ftp->lis);
+               ftp->lis = -1;
+               return -1;
+       }
+
+       addr = ntohl(sa.sin_addr.s_addr);
+       port = ntohs(sa.sin_port);
+
+       sendcmd(ftp, "PORT %d,%d,%d,%d,%d,%d", addr >> 24, (addr >> 16) & 0xff,
+                       (addr >> 8) & 0xff, addr & 0xff, port >> 8, port & 0xff);
+
+       ftp->cproc = cproc_active;
+       return 0;
 }
 
 int ftp_handle(struct ftp *ftp, int s)
@@ -133,6 +306,15 @@ int ftp_handle(struct ftp *ftp, int s)
        if(s == ftp->data) {
                return handle_data(ftp, s);
        }
+       if(s == ftp->lis) {
+               int ns = accept(s, 0, 0);
+               if(ftp->data >= 0) {
+                       closesocket(ns);
+               } else {
+                       ftp->data = ns;
+               }
+               return 0;
+       }
        return -1;
 }
 
@@ -189,7 +371,33 @@ static int handle_control(struct ftp *ftp)
 
 static int handle_data(struct ftp *ftp, int s)
 {
-       return -1;
+       int rd;
+
+       if(ftp->data == -1) {
+               return -1;
+       }
+
+       for(;;) {
+               if((rd = recv(ftp->data, ftp->drecv, sizeof ftp->drecv, 0)) == -1) {
+                       if(errno == EINTR) continue;
+                       /* assume EWOULDBLOCK, try again next time */
+                       break;
+               }
+
+               /* call the callback first, so that we'll get a 0-count call to indicate
+                * EOF when the server closes the data connection
+                */
+               if(ftp->dproc) {
+                       ftp->dproc(ftp, ftp->drecv, rd, ftp->dproc_cls);
+               }
+
+               if(rd == 0) {
+                       closesocket(ftp->data);
+                       ftp->data = -1;
+                       return -1;
+               }
+       }
+       return 0;
 }
 
 static int respcode(const char *resp)
@@ -221,9 +429,15 @@ static void proc_control(struct ftp *ftp, const char *buf)
                return; /* ignore continuations for now */
        }
 
+       ftp->last_resp = code;
+
        if(ftp->cproc) {
-               ftp->cproc(ftp, code, buf, ftp->cproc_cls);
-               ftp->cproc = 0;
+               if(ftp->cproc(ftp, code, buf, ftp->cproc_cls) <= 0) {
+                       ftp->cproc = 0;
+
+                       /* execute next operation if there's one queued */
+                       exec_queued(ftp);
+               }
                return;
        }
 
@@ -235,10 +449,10 @@ static void proc_control(struct ftp *ftp, const char *buf)
                sendcmd(ftp, "pass %s", ftp->pass ? ftp->pass : "foobar");
                break;
        case 230:
-               ftp->status = 1;
                infomsg("login successful\n");
-               ftp_pwd(ftp);
-               ftp->modified = 1;
+               if(newconn(ftp) == -1) {
+                       ftp_close(ftp);
+               }
                break;
        case 530:
                ftp->status = 0;
@@ -247,6 +461,16 @@ static void proc_control(struct ftp *ftp, const char *buf)
        }
 }
 
+static int newconn(struct ftp *ftp)
+{
+       if(ftp_active(ftp) == -1) {
+               return -1;
+       }
+       ftp_queue(ftp, FTP_PWD, 0);
+       ftp_queue(ftp, FTP_LIST, 0);
+       return 0;
+}
+
 int ftp_update(struct ftp *ftp)
 {
        return -1;
@@ -261,12 +485,64 @@ int ftp_pwd(struct ftp *ftp)
 
 int ftp_chdir(struct ftp *ftp, const char *dirname)
 {
-       // TODO queue
        sendcmd(ftp, "cwd %s", dirname);
        ftp->cproc = cproc_cwd;
        return 0;
 }
 
+int ftp_mkdir(struct ftp *ftp, const char *dirname)
+{
+       return -1;
+}
+
+int ftp_rmdir(struct ftp *ftp, const char *dirname)
+{
+       return -1;
+}
+
+int ftp_delete(struct ftp *ftp, const char *fname)
+{
+       return -1;
+}
+
+struct recvbuf {
+       char *buf;
+       long size, bufsz;
+};
+
+int ftp_list(struct ftp *ftp)
+{
+       struct recvbuf *rbuf;
+
+       if(!(rbuf = malloc(sizeof *rbuf))) {
+               errmsg("failed to allocate receive buffer\n");
+               return -1;
+       }
+       rbuf->size = 0;
+       rbuf->bufsz = 1024;
+       if(!(rbuf->buf = malloc(rbuf->bufsz))) {
+               free(rbuf);
+               errmsg("failed to allocate receive buffer\n");
+               return -1;
+       }
+
+       sendcmd(ftp, "list");
+       ftp->cproc = cproc_list;
+       ftp->dproc = dproc_list;
+       ftp->cproc_cls = ftp->dproc_cls = rbuf;
+       return 0;
+}
+
+int ftp_retrieve(struct ftp *ftp, const char *fname)
+{
+       return -1;
+}
+
+int ftp_store(struct ftp *ftp, const char *fname)
+{
+       return -1;
+}
+
 static int get_quoted_text(const char *str, char *buf)
 {
        int len;
@@ -286,26 +562,84 @@ static int get_quoted_text(const char *str, char *buf)
        return 0;
 }
 
-static void cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls)
+static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+       if(code != 200) {
+               errmsg("ftp_active failed\n");
+               ftp_close(ftp);
+       } else {
+               ftp->status = FTP_CONN_ACT;
+       }
+       return 0;
+}
+
+static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls)
 {
        char *dirname;
 
        if(code != 257) {
                warnmsg("pwd failed\n");
-               return;
+               return -1;
        }
 
        dirname = alloca(strlen(buf) + 1);
        if(get_quoted_text(buf, dirname) == -1) {
                warnmsg("pwd: invalid response: %s\n", buf);
-               return;
+               return -1;
        }
 
        free(ftp->curdir_rem);
        ftp->curdir_rem = strdup_nf(dirname);
        ftp->modified = 1;
+       return 0;
+}
+
+static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+       return -1;
+}
+
+static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls)
+{
+       if(code < 200) {
+               /* expect more */
+               return 1;
+       }
+
+       if(code >= 400) {
+               errmsg("failed to retrieve directory listing\n");
+       }
+       return 0;
 }
 
-static void cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls)
+static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls)
 {
+       struct recvbuf *rbuf = cls;
+
+       if(sz == 0) {
+               /* EOF condition, we got the whole list, update directory entries */
+               /* TODO */
+               rbuf->buf[rbuf->size] = 0;
+               fprintf(stderr, "%s\n", rbuf->buf);
+
+               free(rbuf->buf);
+               free(rbuf);
+               ftp->dproc = 0;
+               return;
+       }
+
+       if(rbuf->size + sz > rbuf->bufsz) {
+               char *tmp;
+               int newsz = rbuf->bufsz << 1;
+
+               if(!(tmp = realloc(rbuf->buf, newsz))) {
+                       errmsg("failed to resize receive buffer\n");
+                       return;
+               }
+               rbuf->buf = tmp;
+               rbuf->bufsz = newsz;
+       }
+
+       memcpy(rbuf->buf + rbuf->size, buf, sz);
+       rbuf->size += sz;
 }
index c9ebbe2..e8bcf10 100644 (file)
--- a/src/ftp.h
+++ b/src/ftp.h
@@ -14,9 +14,17 @@ enum {
        FTP_STORE
 };
 
+enum {
+       FTP_DISC,
+       FTP_CONN_PASV,
+       FTP_CONN_ACT
+};
+
 struct ftp_op {
        int op;
        char *arg;
+
+       struct ftp_op *next;
 };
 
 struct ftp_dirent {
@@ -25,24 +33,26 @@ struct ftp_dirent {
 };
 
 struct ftp {
-       int ctl, data;  /* sockets */
+       int ctl, lis, data;     /* sockets */
+       int lis_port;
 
        int status;
        char *user, *pass;
 
-       void (*cproc)(struct ftp *ftp, int code, const char *buf, void *cls);
+       struct ftp_op *qhead, *qtail;
+
+       int (*cproc)(struct ftp *ftp, int code, const char *buf, void *cls);
        void (*dproc)(struct ftp *ftp, const char *buf, int sz, void *cls);
        void *cproc_cls, *dproc_cls;
 
        char crecv[256];
        int num_crecv;
        char drecv[256];
-       int num_drecv;
 
        char *curdir_rem, *curdir_loc;
-       struct ftp_dirent *dent;
-       int num_dent;
+       struct ftp_dirent *dent_rem, *dent_loc;
 
+       int last_resp;
        int modified;
 };
 
@@ -55,13 +65,21 @@ int ftp_connect(struct ftp *ftp, const char *host, int port);
 void ftp_close(struct ftp *ftp);
 
 int ftp_sockets(struct ftp *ftp, int *sockv, int maxsize);
-int ftp_pending(struct ftp *ftp);
 
 int ftp_handle(struct ftp *ftp, int s);
 
+int ftp_queue(struct ftp *ftp, int op, const char *arg);
+int ftp_waitresp(struct ftp *ftp, time_t timeout);
+
 int ftp_update(struct ftp *ftp);
 int ftp_pwd(struct ftp *ftp);
 int ftp_chdir(struct ftp *ftp, const char *dirname);
+int ftp_mkdir(struct ftp *ftp, const char *dirname);
+int ftp_rmdir(struct ftp *ftp, const char *dirname);
+int ftp_delete(struct ftp *ftp, const char *fname);
+int ftp_list(struct ftp *ftp);
+int ftp_retrieve(struct ftp *ftp, const char *fname);
+int ftp_store(struct ftp *ftp, const char *fname);
 
 
 #endif /* FTP_H_ */