X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?a=blobdiff_plain;f=src%2Fftp.c;h=6a29a776bb10e85b070b65cc598b40beb639bbf4;hb=a606b2eade34c3d8e662e94a32af960b5d2911f1;hp=59bc284cd303cb87654e7ab74c9d37bc67eef875;hpb=4577dd26b3ecbf7ccc5ff55efa991334136866e1;p=oftp diff --git a/src/ftp.c b/src/ftp.c index 59bc284..6a29a77 100644 --- a/src/ftp.c +++ b/src/ftp.c @@ -1,7 +1,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -17,10 +20,22 @@ #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 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) { struct ftp *ftp; @@ -28,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; } @@ -82,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; + } + if(ftp->lis >= 0 && maxsize-- > 0) { + *sptr++ = ftp->lis; + } + if(ftp->data >= 0 && maxsize-- > 0) { + *sptr++ = ftp->data; } - return 1; + 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(maxfd == -1) return 0; - return select(maxfd + 1, &rdset, 0, 0, &tv) > 0 ? 1 : 0; + 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; + } + listen(ftp->lis, 1); + + 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) @@ -126,9 +306,35 @@ 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 sendcmd(struct ftp *ftp, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + if(ftp->ctl < 0) { + return -1; + } + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + strcat(buf, "\r\n"); + + return send(ftp->ctl, buf, strlen(buf), 0); +} + static int handle_control(struct ftp *ftp) { int i, sz, rd; @@ -165,11 +371,104 @@ 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) +{ + if(!isdigit(resp[0]) || !isdigit(resp[1]) || !isdigit(resp[2])) { + return 0; + } + + if(isspace(resp[3])) { + return atoi(resp); + } + if(resp[3] == '-') { + return -atoi(resp); + } + return 0; } static void proc_control(struct ftp *ftp, const char *buf) { + int code; + + while(*buf && isspace(*buf)) buf++; + + if((code = respcode(buf)) == 0) { + warnmsg("ignoring invalid response: %s\n", buf); + return; + } + if(code < 0) { + return; /* ignore continuations for now */ + } + + ftp->last_resp = code; + + if(ftp->cproc) { + 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; + } + + switch(code) { + case 220: + sendcmd(ftp, "user %s", ftp->user ? ftp->user : "anonymous"); + break; + case 331: + sendcmd(ftp, "pass %s", ftp->pass ? ftp->pass : "foobar"); + break; + case 230: + infomsg("login successful\n"); + if(newconn(ftp) == -1) { + ftp_close(ftp); + } + break; + case 530: + ftp->status = 0; + errmsg("login failed\n"); + break; + } +} + +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) @@ -177,7 +476,170 @@ int ftp_update(struct ftp *ftp) return -1; } +int ftp_pwd(struct ftp *ftp) +{ + sendcmd(ftp, "pwd"); + ftp->cproc = cproc_pwd; + return 0; +} + int ftp_chdir(struct ftp *ftp, const char *dirname) { + 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; + const char *src, *end; + + if(!(src = strchr(str, '"'))) { + return -1; + } + src++; + end = src; + while(*end && *end != '"') end++; + if(!*end) return -1; + + len = end - src; + memcpy(buf, src, len); + buf[len] = 0; + return 0; +} + +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 -1; + } + + dirname = alloca(strlen(buf) + 1); + if(get_quoted_text(buf, dirname) == -1) { + warnmsg("pwd: invalid response: %s\n", buf); + 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 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; +}