+ ftp->data = -1;
+ }
+}
+
+int ftp_sockets(struct ftp *ftp, int *sockv, int 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 sptr - sockv;
+}
+
+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->busy && !ftp->qhead) {
+ exec_op(ftp, op, arg);
+ return 0;
+ }
+
+ if(!(fop = malloc(sizeof *fop))) {
+ return -1;
+ }
+ if(arg) {
+ if(!(fop->arg = strdup(arg))) {
+ free(fop);
+ return -1;
+ }
+ } else {
+ fop->arg = 0;
+ }
+ fop->op = op;
+ fop->next = 0;
+
+ if(ftp->qhead) {
+ ftp->qtail->next = fop;
+ ftp->qtail = fop;
+ } else {
+ ftp->qhead = ftp->qtail = fop;
+ }
+ return 0;
+}
+
+int ftp_waitresp(struct ftp *ftp, time_t timeout)
+{
+ fd_set rdset;
+ struct timeval tv;
+ time_t start;
+
+ ftp->last_resp = -1;
+ start = time(0);
+
+ for(;;) {
+ FD_ZERO(&rdset);
+ FD_SET(ftp->ctl, &rdset);
+
+ 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) {
+ 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;
+ }
+
+ len = sizeof sa;
+ getsockname(ftp->ctl, (struct sockaddr*)&sa, &len);
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = 0;
+
+ 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->lis, (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);
+ infomsg("listening on %s port %d\n", inet_ntoa(sa.sin_addr), 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->ctl) {
+ return handle_control(ftp);
+ }
+ 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;
+ }
+
+ ftp->busy = 1;
+
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ va_end(ap);
+ infomsg("send: %s\n", buf);
+ strcat(buf, "\r\n");
+ return send(ftp->ctl, buf, strlen(buf), 0);
+}
+
+static int handle_control(struct ftp *ftp)
+{
+ int i, sz, rd;
+ char *buf, *start, *end;
+
+ while((sz = sizeof ftp->crecv - ftp->num_crecv) > 0) {
+ start = ftp->crecv + ftp->num_crecv;
+ if((rd = recv(ftp->ctl, start, sz, 0)) == -1) {
+ if(errno == EINTR) continue;
+ /* assume EWOULDBLOCK, try again next time */
+ return 0;
+ }
+ if(rd == 0) {
+ ftp_close(ftp);
+ return -1;
+ }
+
+ end = start + rd;
+ buf = ftp->crecv;
+ for(i=0; i<rd; i++) {
+ if(start[i] == '\n') {
+ start[i] = 0;
+ proc_control(ftp, buf);
+ buf = start + i + 1;
+ }
+ }
+ if(buf != ftp->crecv && buf < end) {
+ ftp->num_crecv = end - buf;
+ memmove(ftp->crecv, buf, ftp->num_crecv);
+ }
+ }
+ return 0;
+}
+
+static int handle_data(struct ftp *ftp, int s)
+{
+ 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);