#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)
if(!(ftp = calloc(1, sizeof *ftp))) {
return 0;
}
- ftp->ctl = ftp->data = -1;
+ ftp->ctl = ftp->data = ftp->lis = -1;
return 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)
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;
}
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)
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;
}
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;
}
}
+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;
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;
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;
}
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 {
};
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;
};
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_ */