+ 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;
+ }
+
+ 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;
+ }