static CURLcode ftp_3rdparty_transfer(struct connectdata *conn);
static CURLcode ftp_parse_url_path(struct connectdata *conn);
static CURLcode ftp_cwd_and_create_path(struct connectdata *conn);
-static CURLcode ftp_regular_transfer(struct connectdata *conn);
+static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done);
static CURLcode ftp_3rdparty(struct connectdata *conn);
+static void ftp_pasv_verbose(struct connectdata *conn,
+ Curl_addrinfo *ai,
+ char *newhost, /* ascii version */
+ int port);
+static CURLcode ftp_state_post_rest(struct connectdata *conn);
+static CURLcode ftp_state_post_cwd(struct connectdata *conn);
+static CURLcode ftp_state_quote(struct connectdata *conn,
+ bool init, ftpstate instate);
/* easy-to-use macro: */
#define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z))) return result
+#define NBFTPSENDF(x,y,z) if((result = Curl_nbftpsendf(x,y,z))) return result
static void freedirs(struct FTP *ftp)
{
}
+static CURLcode ftp_readresp(curl_socket_t sockfd,
+ struct connectdata *conn,
+ int *ftpcode, /* return the ftp-code if done */
+ size_t *size) /* size of the response */
+{
+ int perline; /* count bytes per line */
+ bool keepon=TRUE;
+ ssize_t gotbytes;
+ char *ptr;
+ struct SessionHandle *data = conn->data;
+ char *line_start;
+ char *buf = data->state.buffer;
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ int code = 0;
+
+ if (ftpcode)
+ *ftpcode = 0; /* 0 for errors or not done */
+
+ ptr=buf;
+ line_start = buf;
+
+ perline=0;
+ keepon=TRUE;
+
+ while((ftp->nread_resp<BUFSIZE) && (keepon && !result)) {
+
+ if(ftp->cache) {
+ /* we had data in the "cache", copy that instead of doing an actual
+ * read
+ *
+ * ftp->cache_size is cast to int here. This should be safe,
+ * because it would have been populated with something of size
+ * int to begin with, even though its datatype may be larger
+ * than an int.
+ */
+ memcpy(ptr, ftp->cache, (int)ftp->cache_size);
+ gotbytes = (int)ftp->cache_size;
+ free(ftp->cache); /* free the cache */
+ ftp->cache = NULL; /* clear the pointer */
+ ftp->cache_size = 0; /* zero the size just in case */
+ }
+ else {
+ int res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftp->nread_resp,
+ &gotbytes);
+ if(res < 0)
+ /* EWOULDBLOCK */
+ return CURLE_OK; /* return */
+
+ if(CURLE_OK != res)
+ keepon = FALSE;
+ }
+
+ if(!keepon)
+ ;
+ else if(gotbytes <= 0) {
+ keepon = FALSE;
+ result = CURLE_RECV_ERROR;
+ failf(data, "FTP response reading failed");
+ }
+ else {
+ /* we got a whole chunk of data, which can be anything from one
+ * byte to a set of lines and possible just a piece of the last
+ * line */
+ int i;
+
+ conn->headerbytecount += gotbytes;
+
+ ftp->nread_resp += gotbytes;
+ for(i = 0; i < gotbytes; ptr++, i++) {
+ perline++;
+ if(*ptr=='\n') {
+ /* a newline is CRLF in ftp-talk, so the CR is ignored as
+ the line isn't really terminated until the LF comes */
+
+ /* output debug output if that is requested */
+ if(data->set.verbose)
+ Curl_debug(data, CURLINFO_HEADER_IN, line_start, perline, conn);
+
+ /*
+ * We pass all response-lines to the callback function registered
+ * for "headers". The response lines can be seen as a kind of
+ * headers.
+ */
+ result = Curl_client_write(data, CLIENTWRITE_HEADER,
+ line_start, perline);
+ if(result)
+ return result;
+
+#define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
+ isdigit((int)line[2]) && (' ' == line[3]))
+
+ if(perline>3 && lastline(line_start)) {
+ /* This is the end of the last line, copy the last line to the
+ start of the buffer and zero terminate, for old times sake (and
+ krb4)! */
+ char *meow;
+ int n;
+ for(meow=line_start, n=0; meow<ptr; meow++, n++)
+ buf[n] = *meow;
+ *meow=0; /* zero terminate */
+ keepon=FALSE;
+ line_start = ptr+1; /* advance pointer */
+ i++; /* skip this before getting out */
+
+ *size = ftp->nread_resp; /* size of the response */
+ ftp->nread_resp = 0; /* restart */
+ break;
+ }
+ perline=0; /* line starts over here */
+ line_start = ptr+1;
+ }
+ }
+ if(!keepon && (i != gotbytes)) {
+ /* We found the end of the response lines, but we didn't parse the
+ full chunk of data we have read from the server. We therefore need
+ to store the rest of the data to be checked on the next invoke as
+ it may actually contain another end of response already! */
+ ftp->cache_size = gotbytes - i;
+ ftp->cache = (char *)malloc((int)ftp->cache_size);
+ if(ftp->cache)
+ memcpy(ftp->cache, line_start, (int)ftp->cache_size);
+ else
+ return CURLE_OUT_OF_MEMORY; /**BANG**/
+ }
+ } /* there was data */
+
+ } /* while there's buffer left and loop is requested */
+
+ if(!result)
+ code = atoi(buf);
+
+#ifdef HAVE_KRB4
+ /* handle the security-oriented responses 6xx ***/
+ /* FIXME: some errorchecking perhaps... ***/
+ switch(code) {
+ case 631:
+ Curl_sec_read_msg(conn, buf, prot_safe);
+ break;
+ case 632:
+ Curl_sec_read_msg(conn, buf, prot_private);
+ break;
+ case 633:
+ Curl_sec_read_msg(conn, buf, prot_confidential);
+ break;
+ default:
+ /* normal ftp stuff we pass through! */
+ break;
+ }
+#endif
+
+ *ftpcode=code; /* return the initial number like this */
+
+
+ /* store the latest code for later retrieval */
+ conn->data->info.httpcode=code;
+
+ return result;
+}
+
/* --- parse FTP server responses --- */
/*
struct connectdata *conn,
int *ftpcode) /* return the ftp-code */
{
- /* Brand new implementation.
- * We cannot read just one byte per read() and then go back to select()
- * as it seems that the OpenSSL read() stuff doesn't grok that properly.
+ /*
+ * We cannot read just one byte per read() and then go back to select() as
+ * the OpenSSL read() doesn't grok that properly.
*
* Alas, read as much as possible, split up into lines, use the ending
* line in a response or continue reading. */
return result;
}
-/*
- * Curl_ftp_connect() should do everything that is to be considered a part of
- * the connection phase.
- */
-CURLcode Curl_ftp_connect(struct connectdata *conn)
+/* This is the ONLY way to change FTP state! */
+static void state(struct connectdata *conn,
+ ftpstate state)
{
- /* this is FTP and no proxy */
- ssize_t nread;
- struct SessionHandle *data=conn->data;
- char *buf = data->state.buffer; /* this is our buffer */
- struct FTP *ftp;
- CURLcode result;
- int ftpcode, trynum;
- static const char * const ftpauth[] = {
- "SSL", "TLS", NULL
+#ifdef CURLDEBUG
+ /* for debug purposes */
+ const char *names[]={
+ "STOP",
+ "WAIT220",
+ "AUTH",
+ "USER",
+ "PASS",
+ "ACCT",
+ "PBSZ",
+ "PROT",
+ "PWD",
+ "QUOTE",
+ "RETR_PREQUOTE",
+ "STOR_PREQUOTE",
+ "POSTQUOTE",
+ "CWD",
+ "MKD",
+ "MDTM",
+ "TYPE",
+ "LIST_TYPE",
+ "RETR_TYPE",
+ "STOR_TYPE",
+ "SIZE",
+ "RETR_SIZE",
+ "STOR_SIZE",
+ "REST",
+ "RETR_REST",
+ "PORT",
+ "PASV",
+ "LIST",
+ "RETR",
+ "STOR",
+ "QUIT"
};
-
- ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
- if(!ftp)
- return CURLE_OUT_OF_MEMORY;
-
- conn->proto.ftp = ftp;
-
- /* We always support persistant connections on ftp */
- conn->bits.close = FALSE;
-
- /* get some initial data into the ftp struct */
- ftp->bytecountp = &conn->bytecount;
-
- /* no need to duplicate them, this connectdata struct won't change */
- ftp->user = conn->user;
- ftp->passwd = conn->passwd;
- if (isBadFtpString(ftp->user) || isBadFtpString(ftp->passwd)) {
- return CURLE_URL_MALFORMAT;
- }
- ftp->response_time = 3600; /* set default response time-out */
-
-#ifndef CURL_DISABLE_HTTP
- if (conn->bits.tunnel_proxy) {
- /* We want "seamless" FTP operations through HTTP proxy tunnel */
- result = Curl_ConnectHTTPProxyTunnel(conn, FIRSTSOCKET,
- conn->host.name, conn->remote_port);
- if(CURLE_OK != result)
- return result;
- }
-#endif /* CURL_DISABLE_HTTP */
-
- if(conn->protocol & PROT_FTPS) {
- /* FTPS is simply ftp with SSL for the control channel */
- /* now, perform the SSL initialization for this socket */
- result = Curl_SSLConnect(conn, FIRSTSOCKET);
- if(result)
- return result;
- }
-
- /* The first thing we do is wait for the "220*" line: */
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
-
- if(ftpcode != 220) {
- failf(data, "This doesn't seem like a nice ftp-server response");
- return CURLE_FTP_WEIRD_SERVER_REPLY;
- }
-
-#ifdef HAVE_KRB4
- /* if not anonymous login, try a secure login */
- if(data->set.krb4) {
-
- /* request data protection level (default is 'clear') */
- Curl_sec_request_prot(conn, "private");
-
- /* We set private first as default, in case the line below fails to
- set a valid level */
- Curl_sec_request_prot(conn, data->set.krb4_level);
-
- if(Curl_sec_login(conn) != 0)
- infof(data, "Logging in with password in cleartext!\n");
- else
- infof(data, "Authentication successful\n");
- }
#endif
+ struct FTP *ftp = conn->proto.ftp;
+#ifdef CURLDEBUG
+ if(ftp->state != state)
+ infof(conn->data, "FTP %p state change from %s to %s\n",
+ ftp, names[ftp->state], names[state]);
+#endif
+ ftp->state = state;
+}
- if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
- /* we don't have a SSL/TLS connection, try a FTPS connection now */
- int start;
- int trynext;
- int count=0;
-
- switch(data->set.ftpsslauth) {
- case CURLFTPAUTH_DEFAULT:
- case CURLFTPAUTH_SSL:
- start = 0;
- trynext = 1;
- break;
- case CURLFTPAUTH_TLS:
- start = 1;
- trynext = 0;
- break;
- default:
- failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d\n",
- data->set.ftpsslauth);
- return CURLE_FAILED_INIT; /* we don't know what to do */
- }
+static CURLcode ftp_state_user(struct connectdata *conn)
+{
+ CURLcode result;
+ struct FTP *ftp = conn->proto.ftp;
+ /* send USER */
+ NBFTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
- for (trynum = start; ftpauth[count]; trynum=trynext, count++) {
+ state(conn, FTP_USER);
- FTPSENDF(conn, "AUTH %s", ftpauth[trynum]);
+ return CURLE_OK;
+}
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+static CURLcode ftp_state_pwd(struct connectdata *conn)
+{
+ CURLcode result;
- if(result)
- return result;
+ /* send PWD to discover our entry point */
+ NBFTPSENDF(conn, "PWD", NULL);
+ state(conn, FTP_PWD);
- /* RFC2228 (page 5) says:
- *
- * If the server is willing to accept the named security mechanism, and
- * does not require any security data, it must respond with reply code
- * 234/334.
- */
+ return CURLE_OK;
+}
- if((ftpcode == 234) || (ftpcode == 334)) {
- result = Curl_SSLConnect(conn, FIRSTSOCKET);
- if(result)
- return result;
- conn->protocol |= PROT_FTPS;
- conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */
- break;
- }
- }
- }
+/* For the FTP "protocol connect" and "doing" phases only */
+CURLcode Curl_ftp_fdset(struct connectdata *conn,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ int *max_fdp)
+{
+ struct FTP *ftp = conn->proto.ftp;
+ curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
- /* send USER */
- FTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
+ if(ftp->sendleft)
+ /* write mode */
+ FD_SET(sockfd, write_fd_set);
+ else
+ /* read mode */
+ FD_SET(sockfd, read_fd_set);
- /* wait for feedback */
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ if((int)sockfd > *max_fdp)
+ *max_fdp = (int)sockfd;
- if(ftpcode == 530) {
- /* 530 User ... access denied
- (the server denies to log the specified user) */
- failf(data, "Access denied: %03d", ftpcode);
- return CURLE_FTP_ACCESS_DENIED;
- }
- else if(ftpcode == 331) {
- /* 331 Password required for ...
- (the server requires to send the user's password too) */
- FTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:"");
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ return CURLE_OK;
+}
- if(ftpcode == 530) {
- /* 530 Login incorrect.
- (the username and/or the password are incorrect)
- or
- 530 Sorry, the maximum number of allowed users are already connected
- */
- failf(data, "not logged in: %03d", ftpcode);
- return CURLE_FTP_USER_PASSWORD_INCORRECT;
- }
- else if(ftpcode/100 == 2) {
- /* 230 User ... logged in.
- (user successfully logged in)
+/* This is called after the FTP_QUOTE state is passed.
- Apparently, proftpd with SSL returns 232 here at times. */
+ ftp_state_cwd() sends the range of PWD commands to the server to change to
+ the correct directory. It may also need to send MKD commands to create
+ missing ones, if that option is enabled.
+*/
+static CURLcode ftp_state_cwd(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
- infof(data, "We have successfully logged in\n");
+ if(ftp->cwddone)
+ /* already done and fine */
+ result = ftp_state_post_cwd(conn);
+ else {
+ ftp->count2 = 0;
+ if (conn->bits.reuse && ftp->entrypath) {
+ /* This is a re-used connection. Since we change directory to where the
+ transfer is taking place, we must first get back to the original dir
+ where we ended up after login: */
+ ftp->count1 = 0; /* we count this as the first path, then we add one
+ for all upcoming ones in the ftp->dirs[] array */
+ NBFTPSENDF(conn, "CWD %s", ftp->entrypath);
+ state(conn, FTP_CWD);
}
- else if(ftpcode == 332) {
- /* 332 Please provide account info */
- if(data->set.ftp_account) {
- FTPSENDF(conn, "ACCT %s", data->set.ftp_account);
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(!result && (ftpcode != 230)) {
- failf(data, "ACCT rejected by server: %03d", ftpcode);
- result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */
- }
+ else {
+ if(ftp->dirdepth) {
+ ftp->count1 = 1;
+ /* issue the first CWD, the rest is sent when the CWD responses are
+ received... */
+ NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->count1 -1]);
+ state(conn, FTP_CWD);
}
else {
- failf(data, "ACCT requested by none available");
- result = CURLE_FTP_WEIRD_PASS_REPLY;
+ /* No CWD necessary */
+ result = ftp_state_post_cwd(conn);
}
- if(result)
- return result;
- }
- else {
- failf(data, "Odd return code after PASS");
- return CURLE_FTP_WEIRD_PASS_REPLY;
}
}
- else if(buf[0] == '2') {
- /* 230 User ... logged in.
- (the user logged in without password) */
- infof(data, "We have successfully logged in\n");
- if (conn->ssl[FIRSTSOCKET].use) {
-#ifdef HAVE_KRB4
- /* We are logged in with Kerberos, now set the requested protection
- * level
- */
- if(conn->sec_complete)
- Curl_sec_set_protection_level(conn);
+ return result;
+}
- /* We may need to issue a KAUTH here to have access to the files
- * do it if user supplied a password
- */
- if(conn->passwd && *conn->passwd) {
- result = Curl_krb_kauth(conn);
- if(result)
- return result;
- }
-#endif
- }
- }
- else {
- failf(data, "Odd return code after USER");
- return CURLE_FTP_WEIRD_USER_REPLY;
- }
+typedef enum { EPRT, LPRT, PORT, DONE } ftpport;
- if(conn->ssl[FIRSTSOCKET].use) {
- /* PBSZ = PROTECTION BUFFER SIZE.
+static CURLcode ftp_state_use_port(struct connectdata *conn,
+ ftpport fcmd) /* start with this */
- The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says:
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data=conn->data;
+ curl_socket_t portsock= CURL_SOCKET_BAD;
- Specifically, the PROT command MUST be preceded by a PBSZ command
- and a PBSZ command MUST be preceded by a successful security data
- exchange (the TLS negotiation in this case)
+#ifdef ENABLE_IPV6
+ /******************************************************************
+ * IPv6-specific section
+ */
- ... (and on page 8):
+ struct addrinfo *res, *ai;
+ struct sockaddr_storage ss;
+ socklen_t sslen;
+ char hbuf[NI_MAXHOST];
+ struct sockaddr *sa=(struct sockaddr *)&ss;
+ unsigned char *ap;
+ unsigned char *pp;
+ char portmsgbuf[1024], tmp[1024];
+ const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
+ int rc;
+ int error;
+ char *host=NULL;
+ struct Curl_dns_entry *h=NULL;
- Thus the PBSZ command must still be issued, but must have a parameter
- of '0' to indicate that no buffering is taking place and the data
- connection should not be encapsulated.
- */
- FTPSENDF(conn, "PBSZ %d", 0);
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ if(data->set.ftpport && (strlen(data->set.ftpport) > 1)) {
+ /* attempt to get the address of the given interface name */
+ if(!Curl_if2ip(data->set.ftpport, hbuf, sizeof(hbuf)))
+ /* not an interface, use the given string as host name instead */
+ host = data->set.ftpport;
+ else
+ host = hbuf; /* use the hbuf for host name */
+ } /* data->set.ftpport */
- /* For TLS, the data connection can have one of two security levels.
+ if(!host) {
+ /* not an interface and not a host name, get default by extracting
+ the IP from the control connection */
- 1)Clear (requested by 'PROT C')
+ sslen = sizeof(ss);
+ rc = getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen);
+ if(rc < 0) {
+ failf(data, "getsockname() returned %d\n", rc);
+ return CURLE_FTP_PORT_FAILED;
+ }
+
+ rc = getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL,
+ 0, NIFLAGS);
+ if(rc) {
+ failf(data, "getnameinfo() returned %d\n", rc);
+ return CURLE_FTP_PORT_FAILED;
+ }
+ host = hbuf; /* use this host name */
+ }
+
+ rc = Curl_resolv(conn, host, 0, &h);
+ if(rc == CURLRESOLV_PENDING)
+ rc = Curl_wait_for_resolv(conn, &h);
+ if(h) {
+ res = h->addr;
+ /* when we return from this function, we can forget about this entry
+ to we can unlock it now already */
+ Curl_resolv_unlock(data, h);
+ } /* (h) */
+ else
+ res = NULL; /* failure! */
+
+ portsock = CURL_SOCKET_BAD;
+ error = 0;
+ for (ai = res; ai; ai = ai->ai_next) {
+ /*
+ * Workaround for AIX5 getaddrinfo() problem (it doesn't set ai_socktype):
+ */
+ if (ai->ai_socktype == 0)
+ ai->ai_socktype = SOCK_STREAM;
+
+ portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (portsock == CURL_SOCKET_BAD) {
+ error = Curl_ourerrno();
+ continue;
+ }
+
+ if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
+ error = Curl_ourerrno();
+ sclose(portsock);
+ portsock = CURL_SOCKET_BAD;
+ continue;
+ }
+
+ if (listen(portsock, 1) < 0) {
+ error = Curl_ourerrno();
+ sclose(portsock);
+ portsock = CURL_SOCKET_BAD;
+ continue;
+ }
+
+ break;
+ }
+
+ if (portsock == CURL_SOCKET_BAD) {
+ failf(data, "%s", Curl_strerror(conn,error));
+ return CURLE_FTP_PORT_FAILED;
+ }
+
+ sslen = sizeof(ss);
+ if (getsockname(portsock, sa, &sslen) < 0) {
+ failf(data, "%s", Curl_strerror(conn,Curl_ourerrno()));
+ return CURLE_FTP_PORT_FAILED;
+ }
+
+#ifdef PF_INET6
+ if(!conn->bits.ftp_use_eprt && conn->bits.ipv6)
+ /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the
+ request and enable EPRT again! */
+ conn->bits.ftp_use_eprt = TRUE;
+#endif
+
+ for (; fcmd != DONE; fcmd++) {
+ int lprtaf, eprtaf;
+ int alen=0, plen=0;
+
+ if(!conn->bits.ftp_use_eprt && (EPRT == fcmd))
+ /* if disabled, goto next */
+ continue;
+
+ if(!conn->bits.ftp_use_lprt && (LPRT == fcmd))
+ /* if disabled, goto next */
+ continue;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr;
+ alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr);
+ pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port;
+ plen = sizeof(((struct sockaddr_in *)&ss)->sin_port);
+ lprtaf = 4;
+ eprtaf = 1;
+ break;
+ case AF_INET6:
+ ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
+ alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr);
+ pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port;
+ plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port);
+ lprtaf = 6;
+ eprtaf = 2;
+ break;
+ default:
+ ap = pp = NULL;
+ lprtaf = eprtaf = -1;
+ break;
+ }
+
+ if (EPRT == fcmd) {
+ if (eprtaf < 0)
+ continue;
+ if (getnameinfo((struct sockaddr *)&ss, sslen,
+ portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp),
+ NIFLAGS))
+ continue;
+
+ /* do not transmit IPv6 scope identifier to the wire */
+ if (sa->sa_family == AF_INET6) {
+ char *q = strchr(portmsgbuf, '%');
+ if (q)
+ *q = '\0';
+ }
+
+ result = Curl_nbftpsendf(conn, "%s |%d|%s|%s|", mode[fcmd], eprtaf,
+ portmsgbuf, tmp);
+ if(result)
+ return result;
+ break;
+ }
+ else if ((LPRT == fcmd) || (PORT == fcmd)) {
+ int i;
+
+ if ((LPRT == fcmd) && lprtaf < 0)
+ continue;
+ if ((PORT == fcmd) && sa->sa_family != AF_INET)
+ continue;
+
+ portmsgbuf[0] = '\0';
+ if (LPRT == fcmd) {
+ snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen);
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+ sizeof(portmsgbuf)) {
+ continue;
+ }
+ }
+
+ for (i = 0; i < alen; i++) {
+ if (portmsgbuf[0])
+ snprintf(tmp, sizeof(tmp), ",%u", ap[i]);
+ else
+ snprintf(tmp, sizeof(tmp), "%u", ap[i]);
+
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+ sizeof(portmsgbuf)) {
+ continue;
+ }
+ }
+
+ if (LPRT == fcmd) {
+ snprintf(tmp, sizeof(tmp), ",%d", plen);
+
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf))
+ continue;
+ }
+
+ for (i = 0; i < plen; i++) {
+ snprintf(tmp, sizeof(tmp), ",%u", pp[i]);
+
+ if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
+ sizeof(portmsgbuf)) {
+ continue;
+ }
+ }
+
+ result = Curl_nbftpsendf(conn, "%s %s", mode[fcmd], portmsgbuf);
+ if(result)
+ return result;
+ break;
+ }
+ }
+
+ /* store which command was sent */
+ ftp->count1 = fcmd;
+
+ /* we set the secondary socket variable to this for now, it is only so that
+ the cleanup function will close it in case we fail before the true
+ secondary stuff is made */
+ if(-1 != conn->sock[SECONDARYSOCKET])
+ sclose(conn->sock[SECONDARYSOCKET]);
+ conn->sock[SECONDARYSOCKET] = portsock;
+
+#else
+ /******************************************************************
+ * IPv4-specific section
+ */
+ struct sockaddr_in sa;
+ unsigned short porttouse;
+ char myhost[256] = "";
+ bool sa_filled_in = FALSE;
+ Curl_addrinfo *addr = NULL;
+ unsigned short ip[4];
+ (void)fcmd; /* not used in the IPv4 code */
+ if(data->set.ftpport) {
+ in_addr_t in;
+
+ /* First check if the given name is an IP address */
+ in=inet_addr(data->set.ftpport);
+
+ if(in != CURL_INADDR_NONE)
+ /* this is an IPv4 address */
+ addr = Curl_ip2addr(in, data->set.ftpport, 0);
+ else {
+ if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
+ /* The interface to IP conversion provided a dotted address */
+ in=inet_addr(myhost);
+ addr = Curl_ip2addr(in, myhost, 0);
+ }
+ else if(strlen(data->set.ftpport)> 1) {
+ /* might be a host name! */
+ struct Curl_dns_entry *h=NULL;
+ int rc = Curl_resolv(conn, myhost, 0, &h);
+ if(rc == CURLRESOLV_PENDING)
+ /* BLOCKING */
+ rc = Curl_wait_for_resolv(conn, &h);
+ if(h) {
+ addr = h->addr;
+ /* when we return from this function, we can forget about this entry
+ so we can unlock it now already */
+ Curl_resolv_unlock(data, h);
+ } /* (h) */
+ } /* strlen */
+ } /* CURL_INADDR_NONE */
+ } /* data->set.ftpport */
+
+ if(!addr) {
+ /* pick a suitable default here */
+
+ socklen_t sslen;
+
+ sslen = sizeof(sa);
+ if (getsockname(conn->sock[FIRSTSOCKET],
+ (struct sockaddr *)&sa, &sslen) < 0) {
+ failf(data, "getsockname() failed");
+ return CURLE_FTP_PORT_FAILED;
+ }
+
+ sa_filled_in = TRUE; /* the sa struct is filled in */
+ }
+
+ if (addr || sa_filled_in) {
+ portsock = socket(AF_INET, SOCK_STREAM, 0);
+ if(CURL_SOCKET_BAD != portsock) {
+ socklen_t size;
+
+ /* we set the secondary socket variable to this for now, it
+ is only so that the cleanup function will close it in case
+ we fail before the true secondary stuff is made */
+ if(-1 != conn->sock[SECONDARYSOCKET])
+ sclose(conn->sock[SECONDARYSOCKET]);
+ conn->sock[SECONDARYSOCKET] = portsock;
+
+ if(!sa_filled_in) {
+ memcpy(&sa, addr->ai_addr, sizeof(sa));
+ sa.sin_addr.s_addr = INADDR_ANY;
+ }
+
+ sa.sin_port = 0;
+ size = sizeof(sa);
+
+ if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
+ /* we succeeded to bind */
+ struct sockaddr_in add;
+ socklen_t socksize = sizeof(add);
+
+ if(getsockname(portsock, (struct sockaddr *) &add,
+ &socksize)<0) {
+ failf(data, "getsockname() failed");
+ return CURLE_FTP_PORT_FAILED;
+ }
+ porttouse = ntohs(add.sin_port);
+
+ if ( listen(portsock, 1) < 0 ) {
+ failf(data, "listen(2) failed on socket");
+ return CURLE_FTP_PORT_FAILED;
+ }
+ }
+ else {
+ failf(data, "bind(2) failed on socket");
+ return CURLE_FTP_PORT_FAILED;
+ }
+ }
+ else {
+ failf(data, "socket(2) failed (%s)");
+ return CURLE_FTP_PORT_FAILED;
+ }
+ }
+ else {
+ failf(data, "could't find IP address to use");
+ return CURLE_FTP_PORT_FAILED;
+ }
+
+ if(sa_filled_in)
+ Curl_inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr,
+ myhost, sizeof(myhost));
+ else
+ Curl_printable_address(addr, myhost, sizeof(myhost));
+
+ if(4 == sscanf(myhost, "%hu.%hu.%hu.%hu",
+ &ip[0], &ip[1], &ip[2], &ip[3])) {
+
+ infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n",
+ ip[0], ip[1], ip[2], ip[3], porttouse);
+
+ result=Curl_nbftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d",
+ ip[0], ip[1], ip[2], ip[3],
+ porttouse >> 8, porttouse & 255);
+ if(result)
+ return result;
+ }
+ else
+ return CURLE_FTP_PORT_FAILED;
+
+ Curl_freeaddrinfo(addr);
+
+ ftp->count1 = PORT;
+
+#endif /* end of ipv4-specific code */
+
+ state(conn, FTP_PORT);
+ return result;
+}
+
+static CURLcode ftp_state_use_pasv(struct connectdata *conn)
+{
+ struct FTP *ftp = conn->proto.ftp;
+ CURLcode result = CURLE_OK;
+ /*
+ Here's the excecutive summary on what to do:
+
+ PASV is RFC959, expect:
+ 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
+
+ LPSV is RFC1639, expect:
+ 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2)
+
+ EPSV is RFC2428, expect:
+ 229 Entering Extended Passive Mode (|||port|)
+
+ */
+
+ const char *mode[] = { "EPSV", "PASV", NULL };
+ int modeoff;
+
+#ifdef PF_INET6
+ if(!conn->bits.ftp_use_epsv && conn->bits.ipv6)
+ /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the
+ request and enable EPSV again! */
+ conn->bits.ftp_use_epsv = TRUE;
+#endif
+
+ modeoff = conn->bits.ftp_use_epsv?0:1;
+
+ result = Curl_nbftpsendf(conn, "%s", mode[modeoff]);
+ if(result)
+ return result;
+
+ ftp->count1 = modeoff;
+ state(conn, FTP_PASV);
+ infof(conn->data, "Connect data stream passively\n");
+
+ return result;
+}
+
+/* REST is the last command in the chain of commands when a "head"-like
+ request is made. Thus, if an actual transfer is to be made this is where
+ we take off for real. */
+static CURLcode ftp_state_post_rest(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data = conn->data;
+
+ if(ftp->no_transfer || conn->bits.no_body) {
+ /* then we're done with a "head"-like request, goto STOP */
+ state(conn, FTP_STOP);
+
+ /* doesn't transfer any data */
+ ftp->no_transfer = TRUE;
+ }
+ else if(data->set.ftp_use_port) {
+ /* We have chosen to use the PORT (or similar) command */
+ result = ftp_state_use_port(conn, EPRT);
+ }
+ else {
+ /* We have chosen (this is default) to use the PASV (or similar) command */
+ result = ftp_state_use_pasv(conn);
+ }
+ return result;
+}
+
+static CURLcode ftp_state_post_size(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+
+ if(ftp->no_transfer) {
+ /* if a "head"-like request is being made */
+
+ /* Determine if server can respond to REST command and therefore
+ whether it supports range */
+ NBFTPSENDF(conn, "REST %d", 0);
+
+ state(conn, FTP_REST);
+ }
+ else
+ result = ftp_state_post_rest(conn);
+
+ return result;
+}
+
+static CURLcode ftp_state_post_type(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+
+ if(ftp->no_transfer) {
+ /* if a "head"-like request is being made */
+
+ /* we know ftp->file is a valid pointer to a file name */
+ NBFTPSENDF(conn, "SIZE %s", ftp->file);
+
+ state(conn, FTP_SIZE);
+ }
+ else
+ result = ftp_state_post_size(conn);
+
+ return result;
+}
+
+static CURLcode ftp_state_post_listtype(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct SessionHandle *data = conn->data;
+
+ /* If this output is to be machine-parsed, the NLST command might be better
+ to use, since the LIST command output is not specified or standard in any
+ way. It has turned out that the NLST list output is not the same on all
+ servers either... */
+
+ NBFTPSENDF(conn, "%s",
+ data->set.customrequest?data->set.customrequest:
+ (data->set.ftp_list_only?"NLST":"LIST"));
+
+ state(conn, FTP_LIST);
+
+ return result;
+}
+
+static CURLcode ftp_state_post_retrtype(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+
+ /* We've sent the TYPE, now we must send the list of prequote strings */
+
+ result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE);
+
+ return result;
+}
+
+static CURLcode ftp_state_post_stortype(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+
+ /* We've sent the TYPE, now we must send the list of prequote strings */
- 2)Private (requested by 'PROT P')
- */
- if(!conn->ssl[SECONDARYSOCKET].use) {
- FTPSENDF(conn, "PROT %c", 'P');
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE);
- if(ftpcode/100 == 2)
- /* We have enabled SSL for the data connection! */
- conn->ssl[SECONDARYSOCKET].use = TRUE;
- /* FTP servers typically responds with 500 if they decide to reject
- our 'P' request */
- else if(data->set.ftp_ssl> CURLFTPSSL_CONTROL)
- /* we failed and bails out */
- return CURLE_FTP_SSL_FAILED;
+ return result;
+}
+
+static CURLcode ftp_state_post_mdtm(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data = conn->data;
+
+ /* If we have selected NOBODY and HEADER, it means that we only want file
+ information. Which in FTP can't be much more than the file size and
+ date. */
+ if(conn->bits.no_body && data->set.include_header && ftp->file) {
+ /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
+ may not support it! It is however the only way we have to get a file's
+ size! */
+
+ ftp->no_transfer = TRUE; /* this means no actual transfer will be made */
+
+ /* Some servers return different sizes for different modes, and thus we
+ must set the proper type before we check the size */
+ NBFTPSENDF(conn, "TYPE %c",
+ data->set.ftp_ascii?'A':'I');
+ state(conn, FTP_TYPE);
+ }
+ else
+ result = ftp_state_post_type(conn);
+
+ return result;
+}
+
+/* This is called after the CWD commands have been done in the beginning of
+ the DO phase */
+static CURLcode ftp_state_post_cwd(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data = conn->data;
+
+ /* Requested time of file or time-depended transfer? */
+ if((data->set.get_filetime || data->set.timecondition) && ftp->file) {
+
+ /* we have requested to get the modified-time of the file, this is a white
+ spot as the MDTM is not mentioned in RFC959 */
+ NBFTPSENDF(conn, "MDTM %s", ftp->file);
+
+ state(conn, FTP_MDTM);
+ }
+ else
+ result = ftp_state_post_mdtm(conn);
+
+ return result;
+}
+
+
+/* This is called after the TYPE and possible quote commands have been sent */
+static CURLcode ftp_state_ul_setup(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data = conn->data;
+ curl_off_t passed=0;
+
+ if(conn->resume_from) {
+ /* we're about to continue the uploading of a file */
+ /* 1. get already existing file's size. We use the SIZE command for this
+ which may not exist in the server! The SIZE command is not in
+ RFC959. */
+
+ /* 2. This used to set REST. But since we can do append, we
+ don't another ftp command. We just skip the source file
+ offset and then we APPEND the rest on the file instead */
+
+ /* 3. pass file-size number of bytes in the source file */
+ /* 4. lower the infilesize counter */
+ /* => transfer as usual */
+
+ if(conn->resume_from < 0 ) {
+ /* Got no given size to start from, figure it out */
+ NBFTPSENDF(conn, "SIZE %s", ftp->file);
+ state(conn, FTP_STOR_SIZE);
+ return result;
+ }
+
+ /* enable append */
+ data->set.ftp_append = TRUE;
+
+ /* Let's read off the proper amount of bytes from the input. If we knew it
+ was a proper file we could've just fseek()ed but we only have a stream
+ here */
+
+ /* TODO: allow the ioctlfunction to provide a fast forward function that
+ can be used here and use this method only as a fallback! */
+ do {
+ curl_off_t readthisamountnow = (conn->resume_from - passed);
+ curl_off_t actuallyread;
+
+ if(readthisamountnow > BUFSIZE)
+ readthisamountnow = BUFSIZE;
+
+ actuallyread = (curl_off_t)
+ conn->fread(data->state.buffer, 1, (size_t)readthisamountnow,
+ conn->fread_in);
+
+ passed += actuallyread;
+ if(actuallyread != readthisamountnow) {
+ failf(data, "Could only read %" FORMAT_OFF_T
+ " bytes from the input", passed);
+ return CURLE_FTP_COULDNT_USE_REST;
+ }
+ } while(passed != conn->resume_from);
+
+ /* now, decrease the size of the read */
+ if(data->set.infilesize>0) {
+ data->set.infilesize -= conn->resume_from;
+
+ if(data->set.infilesize <= 0) {
+ infof(data, "File already completely uploaded\n");
+
+ /* no data to transfer */
+ result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
+ /* Set no_transfer so that we won't get any error in
+ * Curl_ftp_done() because we didn't transfer anything! */
+ ftp->no_transfer = TRUE;
+
+ state(conn, FTP_STOP);
+ return CURLE_OK;
+ }
}
+ /* we've passed, proceed as normal */
+ } /* resume_from */
+
+ NBFTPSENDF(conn, data->set.ftp_append?"APPE %s":"STOR %s",
+ ftp->file);
+
+ state(conn, FTP_STOR);
+
+ return result;
+}
+
+static CURLcode ftp_state_quote(struct connectdata *conn,
+ bool init,
+ ftpstate instate)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data = conn->data;
+ bool quote=FALSE;
+ struct curl_slist *item;
+
+ switch(instate) {
+ case FTP_QUOTE:
+ default:
+ item = data->set.quote;
+ break;
+ case FTP_RETR_PREQUOTE:
+ case FTP_STOR_PREQUOTE:
+ item = data->set.prequote;
+ break;
+ case FTP_POSTQUOTE:
+ item = data->set.postquote;
+ break;
}
- /* send PWD to discover our entry point */
- FTPSENDF(conn, "PWD", NULL);
+ if(init)
+ ftp->count1 = 0;
+ else
+ ftp->count1++;
+
+ if(item) {
+ int i = 0;
+
+ /* Skip count1 items in the linked list */
+ while((i< ftp->count1) && item) {
+ item = item->next;
+ i++;
+ }
+ if(item) {
+ NBFTPSENDF(conn, "%s", item->data);
+ state(conn, instate);
+ quote = TRUE;
+ }
+ }
+
+ if(!quote) {
+ /* No more quote to send, continue to ... */
+ switch(instate) {
+ case FTP_QUOTE:
+ default:
+ result = ftp_state_cwd(conn);
+ break;
+ case FTP_RETR_PREQUOTE:
+ NBFTPSENDF(conn, "SIZE %s", ftp->file);
+ state(conn, FTP_RETR_SIZE);
+ break;
+ case FTP_STOR_PREQUOTE:
+ result = ftp_state_ul_setup(conn);
+ break;
+ case FTP_POSTQUOTE:
+ break;
+ }
+ }
+
+ return result;
+}
+
+static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
+ int ftpcode)
+{
+ struct FTP *ftp = conn->proto.ftp;
+ CURLcode result;
+ struct SessionHandle *data=conn->data;
+ Curl_addrinfo *conninfo;
+ struct Curl_dns_entry *addr=NULL;
+ int rc;
+ unsigned short connectport; /* the local port connect() should use! */
+ unsigned short newport=0; /* remote port */
+ bool connected;
+
+ /* newhost must be able to hold a full IP-style address in ASCII, which
+ in the IPv6 case means 5*8-1 = 39 letters */
+#define NEWHOST_BUFSIZE 48
+ char newhost[NEWHOST_BUFSIZE];
+ char *str=&data->state.buffer[4]; /* start on the first letter */
+
+ if((ftp->count1 == 0) &&
+ (ftpcode == 229)) {
+ /* positive EPSV response */
+ char *ptr = strchr(str, '(');
+ if(ptr) {
+ unsigned int num;
+ char separator[4];
+ ptr++;
+ if(5 == sscanf(ptr, "%c%c%c%u%c",
+ &separator[0],
+ &separator[1],
+ &separator[2],
+ &num,
+ &separator[3])) {
+ char sep1 = separator[0];
+ int i;
+
+ /* The four separators should be identical, or else this is an oddly
+ formatted reply and we bail out immediately. */
+ for(i=1; i<4; i++) {
+ if(separator[i] != sep1) {
+ ptr=NULL; /* set to NULL to signal error */
+ break;
+ }
+ }
+ if(ptr) {
+ newport = num;
+
+ /* use the same IP we are already connected to */
+ snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str, newhost);
+ }
+ }
+ else
+ ptr=NULL;
+ }
+ if(!ptr) {
+ failf(data, "Weirdly formatted EPSV reply");
+ return CURLE_FTP_WEIRD_PASV_REPLY;
+ }
+ }
+ else if((ftp->count1 == 1) &&
+ (ftpcode == 227)) {
+ /* positive PASV response */
+ int ip[4];
+ int port[2];
+
+ /*
+ * Scan for a sequence of six comma-separated numbers and use them as
+ * IP+port indicators.
+ *
+ * Found reply-strings include:
+ * "227 Entering Passive Mode (127,0,0,1,4,51)"
+ * "227 Data transfer will passively listen to 127,0,0,1,4,51"
+ * "227 Entering passive mode. 127,0,0,1,4,51"
+ */
+ while(*str) {
+ if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
+ &ip[0], &ip[1], &ip[2], &ip[3],
+ &port[0], &port[1]))
+ break;
+ str++;
+ }
+
+ if(!*str) {
+ failf(data, "Couldn't interpret the 227-response");
+ return CURLE_FTP_WEIRD_227_FORMAT;
+ }
+
+ snprintf(newhost, sizeof(newhost),
+ "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
+ newport = (port[0]<<8) + port[1];
+ }
+ else if(ftp->count1 == 0) {
+ /* EPSV failed, move on to PASV */
+
+ /* disable it for next transfer */
+ conn->bits.ftp_use_epsv = FALSE;
+ infof(data, "disabling EPSV usage\n");
+
+ NBFTPSENDF(conn, "PASV", NULL);
+ ftp->count1++;
+ /* remain in the FTP_PASV state */
+ return result;
+ }
+ else {
+ failf(data, "Bad PASV/EPSV response: %03d", ftpcode);
+ return CURLE_FTP_WEIRD_PASV_REPLY;
+ }
+
+ /* we got OK from server */
+
+ if(data->change.proxy && *data->change.proxy) {
+ /*
+ * This is a tunnel through a http proxy and we need to connect to the
+ * proxy again here.
+ *
+ * We don't want to rely on a former host lookup that might've expired
+ * now, instead we remake the lookup here and now!
+ */
+ rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr);
+ if(rc == CURLRESOLV_PENDING)
+ /* BLOCKING */
+ rc = Curl_wait_for_resolv(conn, &addr);
+
+ connectport =
+ (unsigned short)conn->port; /* we connect to the proxy's port */
+
+ }
+ else {
+ /* normal, direct, ftp connection */
+ rc = Curl_resolv(conn, newhost, newport, &addr);
+ if(rc == CURLRESOLV_PENDING)
+ /* BLOCKING */
+ rc = Curl_wait_for_resolv(conn, &addr);
+
+ if(!addr) {
+ failf(data, "Can't resolve new host %s:%d", newhost, newport);
+ return CURLE_FTP_CANT_GET_HOST;
+ }
+ connectport = newport; /* we connect to the remote port */
+ }
+
+ result = Curl_connecthost(conn,
+ addr,
+ &conn->sock[SECONDARYSOCKET],
+ &conninfo,
+ &connected);
+
+ Curl_resolv_unlock(data, addr); /* we're done using this address */
- /* wait for feedback */
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
if(result)
return result;
- if(ftpcode == 257) {
- char *dir = (char *)malloc(nread+1);
- char *store=dir;
- char *ptr=&buf[4]; /* start on the first letter */
+ conn->bits.tcpconnect = connected; /* simply TRUE or FALSE */
- if(!dir)
- return CURLE_OUT_OF_MEMORY;
+ /*
+ * When this is used from the multi interface, this might've returned with
+ * the 'connected' set to FALSE and thus we are now awaiting a non-blocking
+ * connect to connect and we should not be "hanging" here waiting.
+ */
+
+ if(data->set.verbose)
+ /* this just dumps information about this second connection */
+ ftp_pasv_verbose(conn, conninfo, newhost, connectport);
+
+#ifndef CURL_DISABLE_HTTP
+ if(conn->bits.tunnel_proxy) {
+ /* FIX: this MUST wait for a proper connect first if 'connected' is
+ * FALSE */
+
+ /* BLOCKING */
+ /* We want "seamless" FTP operations through HTTP proxy tunnel */
+ result = Curl_ConnectHTTPProxyTunnel(conn, SECONDARYSOCKET,
+ newhost, newport);
+ if(CURLE_OK != result)
+ return result;
+ }
+#endif /* CURL_DISABLE_HTTP */
+
+ state(conn, FTP_STOP); /* this phase is completed */
+
+ return result;
+}
+
+static CURLcode ftp_state_port_resp(struct connectdata *conn,
+ int ftpcode)
+{
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data = conn->data;
+ ftpport fcmd = ftp->count1;
+ CURLcode result = CURLE_OK;
- /* Reply format is like
- 257<space>"<directory-name>"<space><commentary> and the RFC959 says
+ if(ftpcode != 200) {
+ /* the command failed */
- The directory name can contain any character; embedded double-quotes
- should be escaped by double-quotes (the "quote-doubling" convention).
- */
- if('\"' == *ptr) {
- /* it started good */
- ptr++;
- while(ptr && *ptr) {
- if('\"' == *ptr) {
- if('\"' == ptr[1]) {
- /* "quote-doubling" */
- *store = ptr[1];
- ptr++;
- }
- else {
- /* end of path */
- *store = '\0'; /* zero terminate */
- break; /* get out of this loop */
- }
- }
- else
- *store = *ptr;
- store++;
- ptr++;
- }
- ftp->entrypath =dir; /* remember this */
- infof(data, "Entry path is '%s'\n", ftp->entrypath);
+ if (EPRT == fcmd) {
+ infof(data, "disabling EPRT usage\n");
+ conn->bits.ftp_use_eprt = FALSE;
}
- else {
- /* couldn't get the path */
- free(dir);
- infof(data, "Failed to figure out path\n");
+ else if (LPRT == fcmd) {
+ infof(data, "disabling LPRT usage\n");
+ conn->bits.ftp_use_lprt = FALSE;
}
+ fcmd++;
+ if(fcmd == DONE) {
+ failf(data, "Failed to do PORT");
+ result = CURLE_FTP_PORT_FAILED;
+ }
+ else
+ /* try next */
+ result = ftp_state_use_port(conn, fcmd);
}
else {
- /* We couldn't read the PWD response! */
+ infof(data, "Connect data stream actively\n");
+ state(conn, FTP_STOP); /* end of DO phase */
}
- return CURLE_OK;
+ return result;
}
-/***********************************************************************
- *
- * Curl_ftp_done()
- *
- * The DONE function. This does what needs to be done after a single DO has
- * performed.
- *
- * Input argument is already checked for validity.
- */
-CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status)
+static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
+ int ftpcode)
{
- struct SessionHandle *data = conn->data;
+ CURLcode result = CURLE_OK;
struct FTP *ftp = conn->proto.ftp;
- ssize_t nread;
- int ftpcode;
- CURLcode result=CURLE_OK;
- bool was_ctl_valid = ftp->ctl_valid;
- size_t flen;
- size_t dlen;
- char *path;
-
- /* now store a copy of the directory we are in */
- if(ftp->prevpath)
- free(ftp->prevpath);
-
- path = curl_unescape(conn->path, 0); /* get the "raw" path */
- if(!path)
- return CURLE_OUT_OF_MEMORY;
+ struct SessionHandle *data=conn->data;
- flen = ftp->file?strlen(ftp->file):0; /* file is "raw" already */
- dlen = strlen(path)-flen;
- if(dlen) {
- ftp->prevpath = path;
- if(flen)
- /* if 'path' is not the whole string */
- ftp->prevpath[dlen]=0; /* terminate */
- infof(data, "Remembering we are in dir %s\n", ftp->prevpath);
- }
- else {
- ftp->prevpath = NULL; /* no path */
- free(path);
- }
- /* free the dir tree and file parts */
- freedirs(ftp);
+ switch(ftpcode) {
+ case 213:
+ {
+ /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the
+ last .sss part is optional and means fractions of a second */
+ int year, month, day, hour, minute, second;
+ char *buf = data->state.buffer;
+ if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d",
+ &year, &month, &day, &hour, &minute, &second)) {
+ /* we have a time, reformat it */
+ time_t secs=time(NULL);
+ /* using the good old yacc/bison yuck */
+ snprintf(buf, sizeof(conn->data->state.buffer),
+ "%04d%02d%02d %02d:%02d:%02d GMT",
+ year, month, day, hour, minute, second);
+ /* now, convert this into a time() value: */
+ data->info.filetime = curl_getdate(buf, &secs);
+ }
- ftp->ctl_valid = FALSE;
+ /* If we asked for a time of the file and we actually got one as well,
+ we "emulate" a HTTP-style header in our output. */
- if(data->set.upload) {
- if((-1 != data->set.infilesize) &&
- (data->set.infilesize != *ftp->bytecountp) &&
- !data->set.crlf) {
- failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T
- " out of %" FORMAT_OFF_T " bytes)",
- *ftp->bytecountp, data->set.infilesize);
- conn->bits.close = TRUE; /* close this connection since we don't
- know what state this error leaves us in */
- return CURLE_PARTIAL_FILE;
+#ifdef HAVE_STRFTIME
+ if(data->set.get_filetime && (data->info.filetime>=0) ) {
+ struct tm *tm;
+ time_t clock = (time_t)data->info.filetime;
+#ifdef HAVE_GMTIME_R
+ struct tm buffer;
+ tm = (struct tm *)gmtime_r(&clock, &buffer);
+#else
+ tm = gmtime(&clock);
+#endif
+ /* format: "Tue, 15 Nov 1994 12:45:26" */
+ strftime(buf, BUFSIZE-1,
+ "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n", tm);
+ result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
+ if(result)
+ return result;
+ }
+#endif
}
+ break;
+ default:
+ infof(data, "unsupported MDTM reply format\n");
+ break;
+ case 550: /* "No such file or directory" */
+ failf(data, "Given file does not exist");
+ result = CURLE_FTP_COULDNT_RETR_FILE;
+ break;
}
- else {
- if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
- (conn->maxdownload != *ftp->bytecountp)) {
- failf(data, "Received only partial file: %" FORMAT_OFF_T " bytes",
- *ftp->bytecountp);
- conn->bits.close = TRUE; /* close this connection since we don't
- know what state this error leaves us in */
- return CURLE_PARTIAL_FILE;
+
+ if(data->set.timecondition) {
+ if((data->info.filetime > 0) && (data->set.timevalue > 0)) {
+ switch(data->set.timecondition) {
+ case CURL_TIMECOND_IFMODSINCE:
+ default:
+ if(data->info.filetime < data->set.timevalue) {
+ infof(data, "The requested document is not new enough\n");
+ ftp->no_transfer = TRUE; /* mark this to not transfer data */
+ state(conn, FTP_STOP);
+ return CURLE_OK;
+ }
+ break;
+ case CURL_TIMECOND_IFUNMODSINCE:
+ if(data->info.filetime > data->set.timevalue) {
+ infof(data, "The requested document is not old enough\n");
+ ftp->no_transfer = TRUE; /* mark this to not transfer data */
+ state(conn, FTP_STOP);
+ return CURLE_OK;
+ }
+ break;
+ } /* switch */
}
- else if(!ftp->dont_check &&
- !*ftp->bytecountp &&
- (conn->size>0)) {
- /* We consider this an error, but there's no true FTP error received
- why we need to continue to "read out" the server response too.
- We don't want to leave a "waiting" server reply if we'll get told
- to make a second request on this same connection! */
- failf(data, "No data was received!");
- result = CURLE_FTP_COULDNT_RETR_FILE;
+ else {
+ infof(data, "Skipping time comparison\n");
}
}
- switch(status) {
- case CURLE_BAD_DOWNLOAD_RESUME:
- case CURLE_FTP_WEIRD_PASV_REPLY:
- case CURLE_FTP_PORT_FAILED:
- case CURLE_FTP_COULDNT_SET_BINARY:
- case CURLE_FTP_COULDNT_RETR_FILE:
- case CURLE_FTP_ACCESS_DENIED:
- /* the connection stays alive fine even though this happened */
- /* fall-through */
- case CURLE_OK: /* doesn't affect the control connection's status */
- ftp->ctl_valid = was_ctl_valid;
- break;
- default: /* by default, an error means the control connection is
- wedged and should not be used anymore */
- ftp->ctl_valid = FALSE;
- break;
- }
+ if(!result)
+ result = ftp_state_post_mdtm(conn);
-#ifdef HAVE_KRB4
- Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]);
-#endif
+ return result;
+}
- /* shut down the socket to inform the server we're done */
+static CURLcode ftp_state_type_resp(struct connectdata *conn,
+ int ftpcode,
+ ftpstate instate)
+{
+ CURLcode result = CURLE_OK;
+ struct SessionHandle *data=conn->data;
-#ifdef _WIN32_WCE
- shutdown(conn->sock[SECONDARYSOCKET],2); /* SD_BOTH */
-#endif
+ if(ftpcode != 200) {
+ failf(data, "Couldn't set desired mode");
+ return CURLE_FTP_COULDNT_SET_BINARY; /* FIX */
+ }
+ if(instate == FTP_TYPE)
+ result = ftp_state_post_type(conn);
+ else if(instate == FTP_LIST_TYPE)
+ result = ftp_state_post_listtype(conn);
+ else if(instate == FTP_RETR_TYPE)
+ result = ftp_state_post_retrtype(conn);
+ else if(instate == FTP_STOR_TYPE)
+ result = ftp_state_post_stortype(conn);
- sclose(conn->sock[SECONDARYSOCKET]);
+ return result;
+}
- conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
+static CURLcode ftp_state_post_retr_size(struct connectdata *conn,
+ curl_off_t filesize)
+{
+ CURLcode result = CURLE_OK;
+ struct SessionHandle *data=conn->data;
+ struct FTP *ftp = conn->proto.ftp;
- if(!ftp->no_transfer && !status) {
- /* Let's see what the server says about the transfer we just performed,
- * but lower the timeout as sometimes this connection has died while the
- * data has been transfered. This happens when doing through NATs etc that
- * abandon old silent connections.
- */
- ftp->response_time = 60; /* give it only a minute for now */
+ if (data->set.max_filesize && (filesize > data->set.max_filesize)) {
+ failf(data, "Maximum file size exceeded");
+ return CURLE_FILESIZE_EXCEEDED;
+ }
+ ftp->downloadsize = filesize;
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+ if(conn->resume_from) {
+ /* We always (attempt to) get the size of downloads, so it is done before
+ this even when not doing resumes. */
+ if(filesize == -1) {
+ infof(data, "ftp server doesn't support SIZE\n");
+ /* We couldn't get the size and therefore we can't know if there really
+ is a part of the file left to get, although the server will just
+ close the connection when we start the connection so it won't cause
+ us any harm, just not make us exit as nicely. */
+ }
+ else {
+ /* We got a file size report, so we check that there actually is a
+ part of the file left to get, or else we go home. */
+ if(conn->resume_from< 0) {
+ /* We're supposed to download the last abs(from) bytes */
+ if(filesize < -conn->resume_from) {
+ failf(data, "Offset (%" FORMAT_OFF_T
+ ") was beyond file size (%" FORMAT_OFF_T ")",
+ conn->resume_from, filesize);
+ return CURLE_BAD_DOWNLOAD_RESUME;
+ }
+ /* convert to size to download */
+ ftp->downloadsize = -conn->resume_from;
+ /* download from where? */
+ conn->resume_from = filesize - ftp->downloadsize;
+ }
+ else {
+ if(filesize < conn->resume_from) {
+ failf(data, "Offset (%" FORMAT_OFF_T
+ ") was beyond file size (%" FORMAT_OFF_T ")",
+ conn->resume_from, filesize);
+ return CURLE_BAD_DOWNLOAD_RESUME;
+ }
+ /* Now store the number of bytes we are expected to download */
+ ftp->downloadsize = filesize-conn->resume_from;
+ }
+ }
- ftp->response_time = 3600; /* set this back to one hour waits */
+ if(ftp->downloadsize == 0) {
+ /* no data to transfer */
+ result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+ infof(data, "File already completely downloaded\n");
- if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) {
- failf(data, "control connection looks dead");
- return result;
+ /* Set no_transfer so that we won't get any error in Curl_ftp_done()
+ * because we didn't transfer the any file */
+ ftp->no_transfer = TRUE;
+ state(conn, FTP_STOP);
+ return CURLE_OK;
}
- if(result)
- return result;
+ /* Set resume file transfer offset */
+ infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
+ "\n", conn->resume_from);
- if(!ftp->dont_check) {
- /* 226 Transfer complete, 250 Requested file action okay, completed. */
- if((ftpcode != 226) && (ftpcode != 250)) {
- failf(data, "server did not report OK, got %d", ftpcode);
- return CURLE_FTP_WRITE_ERROR;
- }
- }
- }
+ NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, conn->resume_from);
- /* clear these for next connection */
- ftp->no_transfer = FALSE;
- ftp->dont_check = FALSE;
+ state(conn, FTP_RETR_REST);
- if (!result && conn->sec_conn) { /* 3rd party transfer */
- /* "done" with the secondary connection */
- result = Curl_ftp_done(conn->sec_conn, status);
}
-
- /* Send any post-transfer QUOTE strings? */
- if(!status && !result && data->set.postquote)
- result = ftp_sendquote(conn, data->set.postquote);
+ else {
+ /* no resume */
+ NBFTPSENDF(conn, "RETR %s", ftp->file);
+ state(conn, FTP_RETR);
+ }
return result;
}
-/***********************************************************************
- *
- * ftp_sendquote()
- *
- * Where a 'quote' means a list of custom commands to send to the server.
- * The quote list is passed as an argument.
- */
-
-static
-CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote)
+static CURLcode ftp_state_size_resp(struct connectdata *conn,
+ int ftpcode,
+ ftpstate instate)
{
- struct curl_slist *item;
- ssize_t nread;
- int ftpcode;
- CURLcode result;
+ CURLcode result = CURLE_OK;
+ struct SessionHandle *data=conn->data;
+ curl_off_t filesize;
+ char *buf = data->state.buffer;
- item = quote;
- while (item) {
- if (item->data) {
- FTPSENDF(conn, "%s", item->data);
+ /* get the size from the ascii string: */
+ filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1;
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if (result)
+ if(instate == FTP_SIZE) {
+ if(-1 != filesize) {
+ snprintf(buf, sizeof(data->state.buffer),
+ "Content-Length: %" FORMAT_OFF_T "\r\n", filesize);
+ result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
+ if(result)
return result;
-
- if (ftpcode >= 400) {
- failf(conn->data, "QUOT string not accepted: %s", item->data);
- return CURLE_FTP_QUOTE_ERROR;
- }
}
-
- item = item->next;
+ result = ftp_state_post_size(conn);
+ }
+ else if(instate == FTP_RETR_SIZE)
+ result = ftp_state_post_retr_size(conn, filesize);
+ else if(instate == FTP_STOR_SIZE) {
+ conn->resume_from = filesize;
+ result = ftp_state_ul_setup(conn);
}
- return CURLE_OK;
+ return result;
}
-/***********************************************************************
- *
- * ftp_getfiletime()
- *
- * Get the timestamp of the given file.
- */
-static
-CURLcode ftp_getfiletime(struct connectdata *conn, char *file)
+static CURLcode ftp_state_rest_resp(struct connectdata *conn,
+ int ftpcode,
+ ftpstate instate)
{
- CURLcode result=CURLE_OK;
- int ftpcode; /* for ftp status */
- ssize_t nread;
- char *buf = conn->data->state.buffer;
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
- /* we have requested to get the modified-time of the file, this is yet
- again a grey area as the MDTM is not kosher RFC959 */
- FTPSENDF(conn, "MDTM %s", file);
+ switch(instate) {
+ case FTP_REST:
+ default:
+ if (ftpcode == 350) {
+ result = Curl_client_write(conn->data, CLIENTWRITE_BOTH,
+ (char *)"Accept-ranges: bytes\r\n", 0);
+ if(result)
+ return result;
+ }
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ result = ftp_state_post_rest(conn);
+ break;
- switch(ftpcode) {
- case 213:
- {
- /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the
- last .sss part is optional and means fractions of a second */
- int year, month, day, hour, minute, second;
- if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d",
- &year, &month, &day, &hour, &minute, &second)) {
- /* we have a time, reformat it */
- time_t secs=time(NULL);
- /* using the good old yacc/bison yuck */
- snprintf(buf, sizeof(conn->data->state.buffer),
- "%04d%02d%02d %02d:%02d:%02d GMT",
- year, month, day, hour, minute, second);
- /* now, convert this into a time() value: */
- conn->data->info.filetime = curl_getdate(buf, &secs);
- }
+ case FTP_RETR_REST:
+ if (ftpcode != 350) {
+ failf(conn->data, "Couldn't use REST");
+ result = CURLE_FTP_COULDNT_USE_REST;
+ }
+ else {
+ NBFTPSENDF(conn, "RETR %s", ftp->file);
+ state(conn, FTP_RETR);
}
- break;
- default:
- infof(conn->data, "unsupported MDTM reply format\n");
- break;
- case 550: /* "No such file or directory" */
- failf(conn->data, "Given file does not exist");
- result = CURLE_FTP_COULDNT_RETR_FILE;
break;
}
- return result;
+
+ return result;
}
-/***********************************************************************
- *
- * ftp_transfertype()
- *
- * Set transfer type. We only deal with ASCII or BINARY so this function
- * sets one of them.
- */
-static CURLcode ftp_transfertype(struct connectdata *conn,
- bool ascii)
+static CURLcode ftp_state_stor_resp(struct connectdata *conn,
+ int ftpcode)
{
+ CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
- int ftpcode;
- ssize_t nread;
- CURLcode result;
+ struct FTP *ftp = conn->proto.ftp;
- FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
+ if(ftpcode>=400) {
+ failf(data, "Failed FTP upload: %0d", ftpcode);
+ /* oops, we never close the sockets! */
+ return CURLE_FTP_COULDNT_STOR_FILE;
+ }
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ if(data->set.ftp_use_port) {
+ /* BLOCKING */
+ /* PORT means we are now awaiting the server to connect to us. */
+ result = AllowServerConnect(conn);
+ if( result )
+ return result;
+ }
- if(ftpcode != 200) {
- failf(data, "Couldn't set %s mode",
- ascii?"ASCII":"binary");
- return ascii? CURLE_FTP_COULDNT_SET_ASCII:CURLE_FTP_COULDNT_SET_BINARY;
+ if(conn->ssl[SECONDARYSOCKET].use) {
+ /* since we only have a plaintext TCP connection here, we must now
+ do the TLS stuff */
+ infof(data, "Doing the SSL/TLS handshake on the data stream\n");
+ /* BLOCKING */
+ result = Curl_SSLConnect(conn, SECONDARYSOCKET);
+ if(result)
+ return result;
}
- return CURLE_OK;
-}
+ *(ftp->bytecountp)=0;
-/***********************************************************************
- *
- * ftp_getsize()
- *
- * Returns the file size (in bytes) of the given remote file.
- */
+ /* When we know we're uploading a specified file, we can get the file
+ size prior to the actual upload. */
-static
-CURLcode ftp_getsize(struct connectdata *conn, char *file,
- curl_off_t *size)
+ Curl_pgrsSetUploadSize(data, data->set.infilesize);
+
+ result = Curl_Transfer(conn, -1, -1, FALSE, NULL, /* no download */
+ SECONDARYSOCKET, ftp->bytecountp);
+ state(conn, FTP_STOP);
+
+ return result;
+}
+
+/* for LIST and RETR responses */
+static CURLcode ftp_state_get_resp(struct connectdata *conn,
+ int ftpcode,
+ ftpstate instate)
{
+ CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
- int ftpcode;
- ssize_t nread;
- char *buf=data->state.buffer;
- CURLcode result;
+ struct FTP *ftp = conn->proto.ftp;
+ char *buf = data->state.buffer;
- FTPSENDF(conn, "SIZE %s", file);
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ if((ftpcode == 150) || (ftpcode == 125)) {
- if(ftpcode == 213) {
- /* get the size from the ascii string: */
- *size = curlx_strtoofft(buf+4, NULL, 0);
- }
- else
- return CURLE_FTP_COULDNT_GET_SIZE;
+ /*
+ A;
+ 150 Opening BINARY mode data connection for /etc/passwd (2241
+ bytes). (ok, the file is being transfered)
- return CURLE_OK;
-}
+ B:
+ 150 Opening ASCII mode data connection for /bin/ls
-/***************************************************************************
- *
- * ftp_pasv_verbose()
- *
- * This function only outputs some informationals about this second connection
- * when we've issued a PASV command before and thus we have connected to a
- * possibly new IP address.
- *
- */
-static void
-ftp_pasv_verbose(struct connectdata *conn,
- Curl_addrinfo *ai,
- char *newhost, /* ascii version */
- int port)
-{
- char buf[256];
- Curl_printable_address(ai, buf, sizeof(buf));
- infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port);
-}
+ C:
+ 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
-/***********************************************************************
- *
- * ftp_use_port()
- *
- * Send the proper PORT command. PORT is the ftp client's way of telling the
- * server that *WE* open a port that we listen on an awaits the server to
- * connect to. This is the opposite of PASV.
- */
+ D:
+ 150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
-static
-CURLcode ftp_use_port(struct connectdata *conn)
-{
- struct SessionHandle *data=conn->data;
- curl_socket_t portsock= CURL_SOCKET_BAD;
- ssize_t nread;
- int ftpcode; /* receive FTP response codes in this */
- CURLcode result;
+ E:
+ 125 Data connection already open; Transfer starting. */
-#ifdef ENABLE_IPV6
- /******************************************************************
- *
- * Here's a piece of IPv6-specific code coming up
- *
- */
+ curl_off_t size=-1; /* default unknown size */
- struct addrinfo *res, *ai;
- struct sockaddr_storage ss;
- socklen_t sslen;
- char hbuf[NI_MAXHOST]="";
- struct sockaddr *sa=(struct sockaddr *)&ss;
- unsigned char *ap;
- unsigned char *pp;
- char portmsgbuf[1024], tmp[1024];
- enum ftpcommand { EPRT, LPRT, PORT, DONE } fcmd;
- const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
- int rc;
- int error;
- char *host=NULL;
- struct Curl_dns_entry *h=NULL;
- if(data->set.ftpport && (strlen(data->set.ftpport) > 1)) {
- /* attempt to get the address of the given interface name */
- if(!Curl_if2ip(data->set.ftpport, hbuf, sizeof(hbuf)))
- /* not an interface, use the given string as host name instead */
- host = data->set.ftpport;
- else
- host = hbuf; /* use the hbuf for host name */
- } /* data->set.ftpport */
+ /*
+ * It appears that there are FTP-servers that return size 0 for files when
+ * SIZE is used on the file while being in BINARY mode. To work around
+ * that (stupid) behavior, we attempt to parse the RETR response even if
+ * the SIZE returned size zero.
+ *
+ * Debugging help from Salvatore Sorrentino on February 26, 2003.
+ */
- if(!host) {
- /* not an interface and not a host name, get default by extracting
- the IP from the control connection */
+ if((instate != FTP_LIST) &&
+ !data->set.ftp_ascii &&
+ (ftp->downloadsize < 1)) {
+ /*
+ * It seems directory listings either don't show the size or very
+ * often uses size 0 anyway. ASCII transfers may very well turn out
+ * that the transfered amount of data is not the same as this line
+ * tells, why using this number in those cases only confuses us.
+ *
+ * Example D above makes this parsing a little tricky */
+ char *bytes;
+ bytes=strstr(buf, " bytes");
+ if(bytes--) {
+ long in=bytes-buf;
+ /* this is a hint there is size information in there! ;-) */
+ while(--in) {
+ /* scan for the left parenthesis and break there */
+ if('(' == *bytes)
+ break;
+ /* skip only digits */
+ if(!isdigit((int)*bytes)) {
+ bytes=NULL;
+ break;
+ }
+ /* one more estep backwards */
+ bytes--;
+ }
+ /* if we have nothing but digits: */
+ if(bytes++) {
+ /* get the number! */
+ size = curlx_strtoofft(bytes, NULL, 0);
+ }
+ }
+ }
+ else if(ftp->downloadsize > -1)
+ size = ftp->downloadsize;
- sslen = sizeof(ss);
- rc = getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen);
- if(rc < 0) {
- failf(data, "getsockname() returned %d\n", rc);
- return CURLE_FTP_PORT_FAILED;
+ if(data->set.ftp_use_port) {
+ /* BLOCKING */
+ result = AllowServerConnect(conn);
+ if( result )
+ return result;
}
- rc = getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL,
- 0, NIFLAGS);
- if(rc) {
- failf(data, "getnameinfo() returned %d\n", rc);
- return CURLE_FTP_PORT_FAILED;
+ if(conn->ssl[SECONDARYSOCKET].use) {
+ /* since we only have a plaintext TCP connection here, we must now
+ do the TLS stuff */
+ infof(data, "Doing the SSL/TLS handshake on the data stream\n");
+ result = Curl_SSLConnect(conn, SECONDARYSOCKET);
+ if(result)
+ return result;
}
- host = hbuf; /* use this host name */
- }
- rc = Curl_resolv(conn, host, 0, &h);
- if(rc == CURLRESOLV_PENDING)
- rc = Curl_wait_for_resolv(conn, &h);
- if(h) {
- res = h->addr;
- /* when we return from this function, we can forget about this entry
- to we can unlock it now already */
- Curl_resolv_unlock(data, h);
- } /* (h) */
- else
- res = NULL; /* failure! */
+ if(size > conn->maxdownload && conn->maxdownload > 0)
+ size = conn->size = conn->maxdownload;
- portsock = CURL_SOCKET_BAD;
- error = 0;
- for (ai = res; ai; ai = ai->ai_next) {
- /*
- * Workaround for AIX5 getaddrinfo() problem (it doesn't set ai_socktype):
- */
- if (ai->ai_socktype == 0)
- ai->ai_socktype = SOCK_STREAM;
+ if(instate != FTP_LIST)
+ infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
- portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (portsock == CURL_SOCKET_BAD) {
- error = Curl_ourerrno();
- continue;
+ /* FTP download: */
+ result=Curl_Transfer(conn, SECONDARYSOCKET, size, FALSE,
+ ftp->bytecountp,
+ -1, NULL); /* no upload here */
+ if(result)
+ return result;
+
+ state(conn, FTP_STOP);
+ }
+ else {
+ if((instate == FTP_LIST) && (ftpcode == 450)) {
+ /* simply no matching files in the dir listing */
+ ftp->no_transfer = TRUE; /* don't download anything */
+ state(conn, FTP_STOP); /* this phase is over */
}
- if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
- error = Curl_ourerrno();
- sclose(portsock);
- portsock = CURL_SOCKET_BAD;
- continue;
+ else {
+ failf(data, "%s", buf+4);
+ return CURLE_FTP_COULDNT_RETR_FILE;
}
+ }
- if (listen(portsock, 1) < 0) {
- error = Curl_ourerrno();
- sclose(portsock);
- portsock = CURL_SOCKET_BAD;
- continue;
+ return result;
+}
+
+/* after USER, PASS and ACCT */
+static CURLcode ftp_state_loggedin(struct connectdata *conn)
+{
+ CURLcode result = CURLE_OK;
+ struct SessionHandle *data = conn->data;
+ infof(data, "We have successfully logged in\n");
+
+#ifdef HAVE_KRB4
+ /* We are logged in with Kerberos, now set the requested
+ * protection level
+ */
+ if(conn->sec_complete)
+ /* BLOCKING */
+ Curl_sec_set_protection_level(conn);
+
+ /* We may need to issue a KAUTH here to have access to the files
+ * do it if user supplied a password
+ */
+ if(conn->passwd && *conn->passwd) {
+ /* BLOCKING */
+ result = Curl_krb_kauth(conn);
+ if(result)
+ return result;
+ }
+#endif
+ if(conn->ssl[FIRSTSOCKET].use) {
+ /* PBSZ = PROTECTION BUFFER SIZE.
+
+ The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says:
+
+ Specifically, the PROT command MUST be preceded by a PBSZ
+ command and a PBSZ command MUST be preceded by a successful
+ security data exchange (the TLS negotiation in this case)
+
+ ... (and on page 8):
+
+ Thus the PBSZ command must still be issued, but must have a
+ parameter of '0' to indicate that no buffering is taking place
+ and the data connection should not be encapsulated.
+ */
+ NBFTPSENDF(conn, "PBSZ %d", 0);
+ state(conn, FTP_PBSZ);
+ }
+ else {
+ result = ftp_state_pwd(conn);
+ }
+ return result;
+}
+
+/* for USER and PASS responses */
+static CURLcode ftp_state_user_resp(struct connectdata *conn,
+ int ftpcode,
+ ftpstate instate)
+{
+ CURLcode result = CURLE_OK;
+ struct SessionHandle *data = conn->data;
+ struct FTP *ftp = conn->proto.ftp;
+ (void)instate; /* no use for this yet */
+
+ if((ftpcode == 331) && (ftp->state == FTP_USER)) {
+ /* 331 Password required for ...
+ (the server requires to send the user's password too) */
+ NBFTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:"");
+ state(conn, FTP_PASS);
+ }
+ else if(ftpcode/100 == 2) {
+ /* 230 User ... logged in.
+ (the user logged in with or without password) */
+ result = ftp_state_loggedin(conn);
+ }
+ else if(ftpcode == 332) {
+ if(data->set.ftp_account) {
+ NBFTPSENDF(conn, "ACCT %s", data->set.ftp_account);
+ state(conn, FTP_ACCT);
+ }
+ else {
+ failf(data, "ACCT requested by none available");
+ result = CURLE_LOGIN_DENIED;
}
-
- break;
}
+ else {
+ /* All other response codes, like:
- if (portsock == CURL_SOCKET_BAD) {
- failf(data, "%s", Curl_strerror(conn,error));
- return CURLE_FTP_PORT_FAILED;
+ 530 User ... access denied
+ (the server denies to log the specified user) */
+ failf(data, "Access denied: %03d", ftpcode);
+ result = CURLE_LOGIN_DENIED;
}
+ return result;
+}
- sslen = sizeof(ss);
- if (getsockname(portsock, sa, &sslen) < 0) {
- failf(data, "%s", Curl_strerror(conn,Curl_ourerrno()));
- return CURLE_FTP_PORT_FAILED;
+/* for ACCT response */
+static CURLcode ftp_state_acct_resp(struct connectdata *conn,
+ int ftpcode)
+{
+ CURLcode result = CURLE_OK;
+ struct SessionHandle *data = conn->data;
+ if(ftpcode != 230) {
+ failf(data, "ACCT rejected by server: %03d", ftpcode);
+ result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */
}
+ else
+ result = ftp_state_loggedin(conn);
-#ifdef PF_INET6
- if(!conn->bits.ftp_use_eprt && conn->bits.ipv6)
- /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the
- request and enable EPRT again! */
- conn->bits.ftp_use_eprt = TRUE;
-#endif
+ return result;
+}
- for (fcmd = EPRT; fcmd != DONE; fcmd++) {
- int lprtaf, eprtaf;
- int alen=0, plen=0;
- if(!conn->bits.ftp_use_eprt && (EPRT == fcmd))
- /* if disabled, goto next */
- continue;
+static CURLcode ftp_statemach_act(struct connectdata *conn)
+{
+ CURLcode result;
+ curl_socket_t sock = conn->sock[FIRSTSOCKET];
+ struct SessionHandle *data=conn->data;
+ int ftpcode;
+ struct FTP *ftp = conn->proto.ftp;
+ static const char * const ftpauth[] = {
+ "SSL", "TLS"
+ };
+ size_t nread;
- if(!conn->bits.ftp_use_lprt && (LPRT == fcmd))
- /* if disabled, goto next */
- continue;
+ if(ftp->sendleft) {
+ /* we have a piece of a command still left to send */
+ ssize_t written;
+ result = Curl_write(conn, sock, ftp->sendthis + ftp->sendsize -
+ ftp->sendleft, ftp->sendleft, &written);
+ if(result)
+ return result;
- switch (sa->sa_family) {
- case AF_INET:
- ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr;
- alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr);
- pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port;
- plen = sizeof(((struct sockaddr_in *)&ss)->sin_port);
- lprtaf = 4;
- eprtaf = 1;
- break;
- case AF_INET6:
- ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
- alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr);
- pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port;
- plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port);
- lprtaf = 6;
- eprtaf = 2;
- break;
- default:
- ap = pp = NULL;
- lprtaf = eprtaf = -1;
- break;
+ if(written != (ssize_t)ftp->sendleft) {
+ /* only a fraction was sent */
+ ftp->sendleft -= written;
}
+ else {
+ free(ftp->sendthis);
+ ftp->sendthis=NULL;
+ ftp->sendleft = ftp->sendsize = 0;
+ ftp->response = Curl_tvnow();
+ }
+ return CURLE_OK;
+ }
- if (EPRT == fcmd) {
- if (eprtaf < 0)
- continue;
- if (getnameinfo((struct sockaddr *)&ss, sslen,
- portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp),
- NIFLAGS))
- continue;
+ /* we read a piece of response */
+ result = ftp_readresp(sock, conn, &ftpcode, &nread);
+ if(result)
+ return result;
- /* do not transmit IPv6 scope identifier to the wire */
- if (sa->sa_family == AF_INET6) {
- char *q = strchr(portmsgbuf, '%');
- if (q)
- *q = '\0';
+ if(ftpcode) {
+ /* we have now received a full FTP server response */
+ switch(ftp->state) {
+ case FTP_WAIT220:
+ if(ftpcode != 220) {
+ failf(data, "This doesn't seem like a nice ftp-server response");
+ return CURLE_FTP_WEIRD_SERVER_REPLY;
}
- result = Curl_ftpsendf(conn, "%s |%d|%s|%s|", mode[fcmd], eprtaf,
- portmsgbuf, tmp);
- if(result)
- return result;
- }
- else if ((LPRT == fcmd) || (PORT == fcmd)) {
- int i;
-
- if ((LPRT == fcmd) && lprtaf < 0)
- continue;
- if ((PORT == fcmd) && sa->sa_family != AF_INET)
- continue;
+ /* We have received a 220 response fine, now we proceed. */
+#ifdef HAVE_KRB4
+ if(data->set.krb4) {
+ /* If not anonymous login, try a secure login. Note that this
+ procedure is still BLOCKING. */
- portmsgbuf[0] = '\0';
- if (LPRT == fcmd) {
- snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen);
- if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
- sizeof(portmsgbuf)) {
- continue;
- }
- }
+ Curl_sec_request_prot(conn, "private");
+ /* We set private first as default, in case the line below fails to
+ set a valid level */
+ Curl_sec_request_prot(conn, data->set.krb4_level);
- for (i = 0; i < alen; i++) {
- if (portmsgbuf[0])
- snprintf(tmp, sizeof(tmp), ",%u", ap[i]);
+ if(Curl_sec_login(conn) != 0)
+ infof(data, "Logging in with password in cleartext!\n");
else
- snprintf(tmp, sizeof(tmp), "%u", ap[i]);
+ infof(data, "Authentication successful\n");
+ }
+#endif
- if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
- sizeof(portmsgbuf)) {
- continue;
+ if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
+ /* We don't have a SSL/TLS connection yet, but FTPS is
+ requested. Try a FTPS connection now */
+
+ ftp->count3=0;
+ switch(data->set.ftpsslauth) {
+ case CURLFTPAUTH_DEFAULT:
+ case CURLFTPAUTH_SSL:
+ ftp->count2 = 1; /* add one to get next */
+ ftp->count1 = 0;
+ break;
+ case CURLFTPAUTH_TLS:
+ ftp->count2 = -1; /* subtract one to get next */
+ ftp->count1 = 1;
+ break;
+ default:
+ failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d\n",
+ data->set.ftpsslauth);
+ return CURLE_FAILED_INIT; /* we don't know what to do */
}
+ NBFTPSENDF(conn, "AUTH %s", ftpauth[ftp->count1]);
+ state(conn, FTP_AUTH);
}
-
- if (LPRT == fcmd) {
- snprintf(tmp, sizeof(tmp), ",%d", plen);
-
- if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf))
- continue;
+ else {
+ ftp_state_user(conn);
+ if(result)
+ return result;
}
- for (i = 0; i < plen; i++) {
- snprintf(tmp, sizeof(tmp), ",%u", pp[i]);
-
- if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
- sizeof(portmsgbuf)) {
- continue;
- }
- }
+ break;
- result = Curl_ftpsendf(conn, "%s %s", mode[fcmd], portmsgbuf);
- if(result)
- return result;
- }
+ case FTP_AUTH:
+ /* we have gotten the response to a previous AUTH command */
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ /* RFC2228 (page 5) says:
+ *
+ * If the server is willing to accept the named security mechanism,
+ * and does not require any security data, it must respond with
+ * reply code 234/334.
+ */
- if (ftpcode != 200) {
- if (EPRT == fcmd) {
- infof(data, "disabling EPRT usage\n");
- conn->bits.ftp_use_eprt = FALSE;
+ if((ftpcode == 234) || (ftpcode == 334)) {
+ /* Curl_SSLConnect is BLOCKING */
+ result = Curl_SSLConnect(conn, FIRSTSOCKET);
+ if(result)
+ return result;
+ conn->protocol |= PROT_FTPS;
+ conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */
}
- else if (LPRT == fcmd) {
- infof(data, "disabling LPRT usage\n");
- conn->bits.ftp_use_lprt = FALSE;
+ else if(ftp->count3 < 1) {
+ ftp->count3++;
+ ftp->count1 += ftp->count2; /* get next attempt */
+ NBFTPSENDF(conn, "AUTH %s", ftpauth[ftp->count1]);
+ /* remain in this same state */
+ }
+ else {
+ result = ftp_state_user(conn);
+ if(result)
+ return result;
}
- continue;
- }
- else
break;
- }
-
- if (fcmd == DONE) {
- sclose(portsock);
- failf(data, "PORT command attempts failed");
- return CURLE_FTP_PORT_FAILED;
- }
- /* we set the secondary socket variable to this for now, it
- is only so that the cleanup function will close it in case
- we fail before the true secondary stuff is made */
- conn->sock[SECONDARYSOCKET] = portsock;
-
-#else
- /******************************************************************
- *
- * Here's a piece of IPv4-specific code coming up
- *
- */
- struct sockaddr_in sa;
- unsigned short porttouse;
- char myhost[256] = "";
- bool sa_filled_in = FALSE;
- Curl_addrinfo *addr = NULL;
- unsigned short ip[4];
- if(data->set.ftpport) {
- in_addr_t in;
+ case FTP_USER:
+ case FTP_PASS:
+ result = ftp_state_user_resp(conn, ftpcode, ftp->state);
+ break;
- /* First check if the given name is an IP address */
- in=inet_addr(data->set.ftpport);
+ case FTP_ACCT:
+ result = ftp_state_acct_resp(conn, ftpcode);
+ break;
- if(in != CURL_INADDR_NONE)
- /* this is an IPv4 address */
- addr = Curl_ip2addr(in, data->set.ftpport, 0);
- else {
- if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
- /* The interface to IP conversion provided a dotted address */
- in=inet_addr(myhost);
- addr = Curl_ip2addr(in, myhost, 0);
- }
- else if(strlen(data->set.ftpport)> 1) {
- /* might be a host name! */
- struct Curl_dns_entry *h=NULL;
- int rc = Curl_resolv(conn, myhost, 0, &h);
- if(rc == CURLRESOLV_PENDING)
- rc = Curl_wait_for_resolv(conn, &h);
- if(h) {
- addr = h->addr;
- /* when we return from this function, we can forget about this entry
- to we can unlock it now already */
- Curl_resolv_unlock(data, h);
- } /* (h) */
- } /* strlen */
- } /* CURL_INADDR_NONE */
- } /* data->set.ftpport */
+ case FTP_PBSZ:
+ /* FIX: check response code */
- if(!addr) {
- /* pick a suitable default here */
+ /* For TLS, the data connection can have one of two security levels.
- socklen_t sslen;
+ 1) Clear (requested by 'PROT C')
- sslen = sizeof(sa);
- if (getsockname(conn->sock[FIRSTSOCKET],
- (struct sockaddr *)&sa, &sslen) < 0) {
- failf(data, "getsockname() failed");
- return CURLE_FTP_PORT_FAILED;
- }
+ 2)Private (requested by 'PROT P')
+ */
+ if(!conn->ssl[SECONDARYSOCKET].use) {
+ NBFTPSENDF(conn, "PROT %c", 'P');
+ state(conn, FTP_PROT);
+ }
+ else {
+ result = ftp_state_pwd(conn);
+ if(result)
+ return result;
+ }
- sa_filled_in = TRUE; /* the sa struct is filled in */
- }
+ break;
- if (addr || sa_filled_in) {
- portsock = socket(AF_INET, SOCK_STREAM, 0);
- if(CURL_SOCKET_BAD != portsock) {
- socklen_t size;
+ case FTP_PROT:
+ if(ftpcode/100 == 2)
+ /* We have enabled SSL for the data connection! */
+ conn->ssl[SECONDARYSOCKET].use = TRUE;
+ /* FTP servers typically responds with 500 if they decide to reject
+ our 'P' request */
+ else if(data->set.ftp_ssl> CURLFTPSSL_CONTROL)
+ /* we failed and bails out */
+ return CURLE_FTP_SSL_FAILED;
- /* we set the secondary socket variable to this for now, it
- is only so that the cleanup function will close it in case
- we fail before the true secondary stuff is made */
- conn->sock[SECONDARYSOCKET] = portsock;
+ result = ftp_state_pwd(conn);
+ if(result)
+ return result;
+ break;
- if(!sa_filled_in) {
- memcpy(&sa, addr->ai_addr, sizeof(sa));
- sa.sin_addr.s_addr = INADDR_ANY;
- }
+ case FTP_PWD:
+ if(ftpcode == 257) {
+ char *dir = (char *)malloc(nread+1);
+ char *store=dir;
+ char *ptr=&data->state.buffer[4]; /* start on the first letter */
- sa.sin_port = 0;
- size = sizeof(sa);
+ if(!dir)
+ return CURLE_OUT_OF_MEMORY;
- if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
- /* we succeeded to bind */
- struct sockaddr_in add;
- socklen_t socksize = sizeof(add);
+ /* Reply format is like
+ 257<space>"<directory-name>"<space><commentary> and the RFC959
+ says
- if(getsockname(portsock, (struct sockaddr *) &add,
- &socksize)<0) {
- failf(data, "getsockname() failed");
- return CURLE_FTP_PORT_FAILED;
+ The directory name can contain any character; embedded
+ double-quotes should be escaped by double-quotes (the
+ "quote-doubling" convention).
+ */
+ if('\"' == *ptr) {
+ /* it started good */
+ ptr++;
+ while(ptr && *ptr) {
+ if('\"' == *ptr) {
+ if('\"' == ptr[1]) {
+ /* "quote-doubling" */
+ *store = ptr[1];
+ ptr++;
+ }
+ else {
+ /* end of path */
+ *store = '\0'; /* zero terminate */
+ break; /* get out of this loop */
+ }
+ }
+ else
+ *store = *ptr;
+ store++;
+ ptr++;
+ }
+ ftp->entrypath =dir; /* remember this */
+ infof(data, "Entry path is '%s'\n", ftp->entrypath);
}
- porttouse = ntohs(add.sin_port);
-
- if ( listen(portsock, 1) < 0 ) {
- failf(data, "listen(2) failed on socket");
- return CURLE_FTP_PORT_FAILED;
+ else {
+ /* couldn't get the path */
+ free(dir);
+ infof(data, "Failed to figure out path\n");
}
}
- else {
- failf(data, "bind(2) failed on socket");
- return CURLE_FTP_PORT_FAILED;
+ state(conn, FTP_STOP); /* we are done with the CONNECT phase! */
+ infof(data, "protocol connect phase DONE\n");
+ break;
+
+ case FTP_QUOTE:
+ case FTP_POSTQUOTE:
+ case FTP_RETR_PREQUOTE:
+ case FTP_STOR_PREQUOTE:
+ if(ftpcode >= 400) {
+ failf(conn->data, "QUOT command failed with %03d", ftpcode);
+ return CURLE_FTP_QUOTE_ERROR;
}
- }
- else {
- failf(data, "socket(2) failed (%s)");
- return CURLE_FTP_PORT_FAILED;
- }
- }
- else {
- failf(data, "could't find IP address to use");
- return CURLE_FTP_PORT_FAILED;
- }
+ result = ftp_state_quote(conn, FALSE, ftp->state);
+ if(result)
+ return result;
- if(sa_filled_in)
- Curl_inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr,
- myhost, sizeof(myhost));
- else
- Curl_printable_address(addr, myhost, sizeof(myhost));
+ break;
- if(4 == sscanf(myhost, "%hu.%hu.%hu.%hu",
- &ip[0], &ip[1], &ip[2], &ip[3])) {
+ case FTP_CWD:
+ if(ftpcode/100 != 2) {
+ /* failure to CWD there */
+ if(conn->data->set.ftp_create_missing_dirs &&
+ ftp->count1 && !ftp->count2) {
+ /* try making it */
+ ftp->count2++; /* counter to prevent CWD-MKD loops */
+ NBFTPSENDF(conn, "MKD %s", ftp->dirs[ftp->count1 - 1]);
+ state(conn, FTP_MKD);
+ }
+ else
+ /* return failure */
+ return CURLE_FTP_ACCESS_DENIED;
+ }
+ else {
+ /* success */
+ ftp->count2=0;
+ if(++ftp->count1 <= ftp->dirdepth) {
+ /* send next CWD */
+ NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->count1 - 1]);
+ }
+ else {
+ result = ftp_state_post_cwd(conn);
+ if(result)
+ return result;
+ }
+ }
+ break;
- infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n",
- ip[0], ip[1], ip[2], ip[3], porttouse);
+ case FTP_MKD:
+ if(ftpcode/100 != 2) {
+ /* failure to MKD the dir */
+ failf(data, "Failed to MKD dir: %03d", ftpcode);
+ return CURLE_FTP_ACCESS_DENIED;
+ }
+ state(conn, FTP_CWD);
+ /* send CWD */
+ NBFTPSENDF(conn, "CWD %s", ftp->dirs[ftp->count1 - 1]);
+ break;
- result=Curl_ftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d",
- ip[0], ip[1], ip[2], ip[3],
- porttouse >> 8,
- porttouse & 255);
- if(result)
- return result;
+ case FTP_MDTM:
+ result = ftp_state_mdtm_resp(conn, ftpcode);
+ break;
- }
- else
- return CURLE_FTP_PORT_FAILED;
+ case FTP_TYPE:
+ case FTP_LIST_TYPE:
+ case FTP_RETR_TYPE:
+ case FTP_STOR_TYPE:
+ result = ftp_state_type_resp(conn, ftpcode, ftp->state);
+ break;
- Curl_freeaddrinfo(addr);
+ case FTP_SIZE:
+ case FTP_RETR_SIZE:
+ case FTP_STOR_SIZE:
+ result = ftp_state_size_resp(conn, ftpcode, ftp->state);
+ break;
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ case FTP_REST:
+ case FTP_RETR_REST:
+ result = ftp_state_rest_resp(conn, ftpcode, ftp->state);
+ break;
- if(ftpcode != 200) {
- failf(data, "Server does not grok PORT, try without it!");
- return CURLE_FTP_PORT_FAILED;
- }
-#endif /* end of ipv4-specific code */
+ case FTP_PASV:
+ result = ftp_state_pasv_resp(conn, ftpcode);
+ break;
- return CURLE_OK;
-}
+ case FTP_PORT:
+ result = ftp_state_port_resp(conn, ftpcode);
+ break;
-/***********************************************************************
- *
- * ftp_use_pasv()
- *
- * Send the PASV command. PASV is the ftp client's way of asking the server to
- * open a second port that we can connect to (for the data transfer). This is
- * the opposite of PORT.
- */
+ case FTP_LIST:
+ case FTP_RETR:
+ result = ftp_state_get_resp(conn, ftpcode, ftp->state);
+ break;
-static
-CURLcode ftp_use_pasv(struct connectdata *conn,
- bool *connected)
-{
- struct SessionHandle *data = conn->data;
- ssize_t nread;
- char *buf = data->state.buffer; /* this is our buffer */
- int ftpcode; /* receive FTP response codes in this */
- CURLcode result;
- struct Curl_dns_entry *addr=NULL;
- Curl_addrinfo *conninfo;
- int rc;
+ case FTP_STOR:
+ result = ftp_state_stor_resp(conn, ftpcode);
+ break;
- /*
- Here's the excecutive summary on what to do:
+ case FTP_QUIT:
+ /* fallthrough, just stop! */
+ default:
+ /* internal error */
+ state(conn, FTP_STOP);
+ break;
+ }
+ } /* if(ftpcode) */
- PASV is RFC959, expect:
- 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
+ return result;
+}
- LPSV is RFC1639, expect:
- 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2)
+/* Returns timeout in ms. 0 or negative number means the timeout has already
+ triggered */
+static long ftp_state_timeout(struct connectdata *conn)
+{
+ struct SessionHandle *data=conn->data;
+ struct FTP *ftp = conn->proto.ftp;
+ long timeout_ms=360000; /* in milliseconds */
+
+ if(data->set.ftp_response_timeout )
+ /* if CURLOPT_FTP_RESPONSE_TIMEOUT is set, use that to determine remaining
+ time. Also, use ftp->response because FTP_RESPONSE_TIMEOUT is supposed
+ to govern the response for any given ftp response, not for the time
+ from connect to the given ftp response. */
+ timeout_ms = data->set.ftp_response_timeout*1000 - /* timeout time */
+ Curl_tvdiff(Curl_tvnow(), ftp->response); /* spent time */
+ else if(data->set.timeout)
+ /* if timeout is requested, find out how much remaining time we have */
+ timeout_ms = data->set.timeout*1000 - /* timeout time */
+ Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
+ else
+ /* Without a requested timeout, we only wait 'response_time' seconds for
+ the full response to arrive before we bail out */
+ timeout_ms = ftp->response_time*1000 -
+ Curl_tvdiff(Curl_tvnow(), ftp->response); /* spent time */
- EPSV is RFC2428, expect:
- 229 Entering Extended Passive Mode (|||port|)
+ return timeout_ms;
+}
- */
- const char *mode[] = { "EPSV", "PASV", NULL };
- int results[] = { 229, 227, 0 };
- int modeoff;
- unsigned short connectport; /* the local port connect() should use! */
- unsigned short newport=0; /* remote port, not necessary the local one */
+/* called repeatedly until done from multi.c */
+CURLcode Curl_ftp_multi_statemach(struct connectdata *conn,
+ bool *done)
+{
+ curl_socket_t sock = conn->sock[FIRSTSOCKET];
+ int rc;
+ struct SessionHandle *data=conn->data;
+ struct FTP *ftp = conn->proto.ftp;
+ CURLcode result = CURLE_OK;
+ long timeout_ms = ftp_state_timeout(conn);
- /* newhost must be able to hold a full IP-style address in ASCII, which
- in the IPv6 case means 5*8-1 = 39 letters */
-#define NEWHOST_BUFSIZE 48
- char newhost[NEWHOST_BUFSIZE];
+ *done = FALSE; /* default to not done yet */
-#ifdef PF_INET6
- if(!conn->bits.ftp_use_epsv && conn->bits.ipv6)
- /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the
- request and enable EPSV again! */
- conn->bits.ftp_use_epsv = TRUE;
-#endif
+ if(timeout_ms <= 0) {
+ failf(data, "FTP response timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
- for (modeoff = (conn->bits.ftp_use_epsv?0:1);
- mode[modeoff]; modeoff++) {
- result = Curl_ftpsendf(conn, "%s", mode[modeoff]);
- if(result)
- return result;
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
- if (ftpcode == results[modeoff])
- break;
+ rc = Curl_select(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+ ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
+ 0);
- if(modeoff == 0) {
- /* EPSV is not supported, disable it for next transfer */
- conn->bits.ftp_use_epsv = FALSE;
- infof(data, "disabling EPSV usage\n");
- }
+ if(rc == -1) {
+ failf(data, "select error");
+ return CURLE_OUT_OF_MEMORY;
}
-
- if (!mode[modeoff]) {
- failf(data, "Odd return code after PASV");
- return CURLE_FTP_WEIRD_PASV_REPLY;
+ else if(rc != 0) {
+ result = ftp_statemach_act(conn);
+ *done = (ftp->state == FTP_STOP);
}
- else if (227 == results[modeoff]) {
- int ip[4];
- int port[2];
- char *str=buf;
+ /* if rc == 0, then select() timed out */
- /*
- * New 227-parser June 3rd 1999.
- * It now scans for a sequence of six comma-separated numbers and
- * will take them as IP+port indicators.
- *
- * Found reply-strings include:
- * "227 Entering Passive Mode (127,0,0,1,4,51)"
- * "227 Data transfer will passively listen to 127,0,0,1,4,51"
- * "227 Entering passive mode. 127,0,0,1,4,51"
- */
+ return result;
+}
- while(*str) {
- if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
- &ip[0], &ip[1], &ip[2], &ip[3],
- &port[0], &port[1]))
- break;
- str++;
- }
+static CURLcode ftp_easy_statemach(struct connectdata *conn)
+{
+ curl_socket_t sock = conn->sock[FIRSTSOCKET];
+ int rc;
+ struct SessionHandle *data=conn->data;
+ struct FTP *ftp = conn->proto.ftp;
+ CURLcode result = CURLE_OK;
- if(!*str) {
- failf(data, "Couldn't interpret the 227-reply");
- return CURLE_FTP_WEIRD_227_FORMAT;
- }
+ while(ftp->state != FTP_STOP) {
+ long timeout_ms = ftp_state_timeout(conn);
- snprintf(newhost, sizeof(newhost),
- "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
- newport = (port[0]<<8) + port[1];
- }
- else if (229 == results[modeoff]) {
- char *ptr = strchr(buf, '(');
- if(ptr) {
- unsigned int num;
- char separator[4];
- ptr++;
- if(5 == sscanf(ptr, "%c%c%c%u%c",
- &separator[0],
- &separator[1],
- &separator[2],
- &num,
- &separator[3])) {
- char sep1 = separator[0];
- int i;
+ if(timeout_ms <=0 ) {
+ failf(data, "FTP response timeout");
+ return CURLE_OPERATION_TIMEDOUT; /* already too little time */
+ }
- /* The four separators should be identical, or else this is an oddly
- formatted reply and we bail out immediately. */
- for(i=1; i<4; i++) {
- if(separator[i] != sep1) {
- ptr=NULL; /* set to NULL to signal error */
- break;
- }
- }
- if(ptr) {
- newport = num;
+ rc = Curl_select(ftp->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+ ftp->sendleft?sock:CURL_SOCKET_BAD, /* writing */
+ timeout_ms);
- /* We must use the same IP we are already connected to */
- snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str);
- }
- }
- else
- ptr=NULL;
+ if(rc == -1) {
+ failf(data, "select error");
+ return CURLE_OUT_OF_MEMORY;
}
- if(!ptr) {
- failf(data, "Weirdly formatted EPSV reply");
- return CURLE_FTP_WEIRD_PASV_REPLY;
+ else if(rc == 0) {
+ result = CURLE_OPERATION_TIMEDOUT;
+ break;
+ }
+ else {
+ result = ftp_statemach_act(conn);
+ if(result)
+ break;
}
}
- else
- return CURLE_FTP_CANT_RECONNECT;
- if(data->change.proxy && *data->change.proxy) {
- /*
- * This is a tunnel through a http proxy and we need to connect to the
- * proxy again here.
- *
- * We don't want to rely on a former host lookup that might've expired
- * now, instead we remake the lookup here and now!
- */
- rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr);
- if(rc == CURLRESOLV_PENDING)
- rc = Curl_wait_for_resolv(conn, &addr);
+ return result;
+}
- connectport =
- (unsigned short)conn->port; /* we connect to the proxy's port */
+/*
+ * Curl_ftp_connect() should do everything that is to be considered a part of
+ * the connection phase.
+ *
+ * The variable 'done' points to will be TRUE if the protocol-layer connect
+ * phase is done when this function returns, or FALSE is not. When called as
+ * a part of the easy interface, it will always be TRUE.
+ */
+CURLcode Curl_ftp_connect(struct connectdata *conn,
+ bool *done) /* see description above */
+{
+ struct FTP *ftp;
+ CURLcode result;
- }
- else {
- /* normal, direct, ftp connection */
- rc = Curl_resolv(conn, newhost, newport, &addr);
- if(rc == CURLRESOLV_PENDING)
- rc = Curl_wait_for_resolv(conn, &addr);
+ *done = FALSE; /* default to not done yet */
- if(!addr) {
- failf(data, "Can't resolve new host %s:%d", newhost, newport);
- return CURLE_FTP_CANT_GET_HOST;
- }
- connectport = newport; /* we connect to the remote port */
- }
+ ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
+ if(!ftp)
+ return CURLE_OUT_OF_MEMORY;
- result = Curl_connecthost(conn,
- addr,
- &conn->sock[SECONDARYSOCKET],
- &conninfo,
- connected);
+ conn->proto.ftp = ftp;
- Curl_resolv_unlock(data, addr); /* we're done using this address */
+ /* We always support persistant connections on ftp */
+ conn->bits.close = FALSE;
- if(result)
- return result;
+ /* get some initial data into the ftp struct */
+ ftp->bytecountp = &conn->bytecount;
- /*
- * When this is used from the multi interface, this might've returned with
- * the 'connected' set to FALSE and thus we are now awaiting a non-blocking
- * connect to connect and we should not be "hanging" here waiting.
- */
+ /* no need to duplicate them, this connectdata struct won't change */
+ ftp->user = conn->user;
+ ftp->passwd = conn->passwd;
+ if (isBadFtpString(ftp->user) || isBadFtpString(ftp->passwd))
+ return CURLE_URL_MALFORMAT;
- if(data->set.verbose)
- /* this just dumps information about this second connection */
- ftp_pasv_verbose(conn, conninfo, newhost, connectport);
+ ftp->response_time = 3600; /* set default response time-out */
#ifndef CURL_DISABLE_HTTP
- if(conn->bits.tunnel_proxy) {
+ if (conn->bits.tunnel_proxy) {
+ /* BLOCKING */
/* We want "seamless" FTP operations through HTTP proxy tunnel */
- result = Curl_ConnectHTTPProxyTunnel(conn, SECONDARYSOCKET,
- newhost, newport);
+ result = Curl_ConnectHTTPProxyTunnel(conn, FIRSTSOCKET,
+ conn->host.name, conn->remote_port);
if(CURLE_OK != result)
return result;
}
#endif /* CURL_DISABLE_HTTP */
- return CURLE_OK;
+ if(conn->protocol & PROT_FTPS) {
+ /* BLOCKING */
+ /* FTPS is simply ftp with SSL for the control channel */
+ /* now, perform the SSL initialization for this socket */
+ result = Curl_SSLConnect(conn, FIRSTSOCKET);
+ if(result)
+ return result;
+ }
+
+ /* When we connect, we start in the state where we await the 220
+ response */
+ state(conn, FTP_WAIT220);
+ ftp->response = Curl_tvnow(); /* start response time-out now! */
+
+ if(conn->data->state.used_interface == Curl_if_multi)
+ result = Curl_ftp_multi_statemach(conn, done);
+ else {
+ result = ftp_easy_statemach(conn);
+ if(!result)
+ *done = TRUE;
+ }
+
+ return result;
}
-/*
- * Curl_ftp_nextconnect()
+/***********************************************************************
+ *
+ * Curl_ftp_done()
+ *
+ * The DONE function. This does what needs to be done after a single DO has
+ * performed.
*
- * This function shall be called when the second FTP connection has been
- * established and is confirmed connected.
+ * Input argument is already checked for validity.
*/
-
-CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
+CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status)
{
- struct SessionHandle *data=conn->data;
- char *buf = data->state.buffer; /* this is our buffer */
- CURLcode result;
- ssize_t nread;
- int ftpcode; /* for ftp status */
-
- /* the ftp struct is already inited in Curl_ftp_connect() */
+ struct SessionHandle *data = conn->data;
struct FTP *ftp = conn->proto.ftp;
- curl_off_t *bytecountp = ftp->bytecountp;
-
- if(data->set.upload) {
-
- /* Set type to binary (unless specified ASCII) */
- result = ftp_transfertype(conn, data->set.ftp_ascii);
- if(result)
- return result;
-
- /* Send any PREQUOTE strings after transfer type is set? (Wesley Laxton)*/
- if(data->set.prequote) {
- if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
- return result;
- }
+ ssize_t nread;
+ int ftpcode;
+ CURLcode result=CURLE_OK;
+ bool was_ctl_valid = ftp->ctl_valid;
+ size_t flen;
+ size_t dlen;
+ char *path;
- if(conn->resume_from) {
- /* we're about to continue the uploading of a file */
- /* 1. get already existing file's size. We use the SIZE
- command for this which may not exist in the server!
- The SIZE command is not in RFC959. */
+ /* now store a copy of the directory we are in */
+ if(ftp->prevpath)
+ free(ftp->prevpath);
- /* 2. This used to set REST. But since we can do append, we
- don't another ftp command. We just skip the source file
- offset and then we APPEND the rest on the file instead */
+ path = curl_unescape(conn->path, 0); /* get the "raw" path */
+ if(!path)
+ return CURLE_OUT_OF_MEMORY;
- /* 3. pass file-size number of bytes in the source file */
- /* 4. lower the infilesize counter */
- /* => transfer as usual */
+ flen = ftp->file?strlen(ftp->file):0; /* file is "raw" already */
+ dlen = strlen(path)-flen;
+ if(dlen) {
+ ftp->prevpath = path;
+ if(flen)
+ /* if 'path' is not the whole string */
+ ftp->prevpath[dlen]=0; /* terminate */
+ infof(data, "Remembering we are in dir %s\n", ftp->prevpath);
+ }
+ else {
+ ftp->prevpath = NULL; /* no path */
+ free(path);
+ }
+ /* free the dir tree and file parts */
+ freedirs(ftp);
- if(conn->resume_from < 0 ) {
- /* we could've got a specified offset from the command line,
- but now we know we didn't */
- curl_off_t gottensize;
+ ftp->ctl_valid = FALSE;
- if(CURLE_OK != ftp_getsize(conn, ftp->file, &gottensize)) {
- failf(data, "Couldn't get remote file size");
- return CURLE_FTP_COULDNT_GET_SIZE;
- }
- conn->resume_from = gottensize;
- }
+ if(data->set.upload) {
+ if((-1 != data->set.infilesize) &&
+ (data->set.infilesize != *ftp->bytecountp) &&
+ !data->set.crlf) {
+ failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T
+ " out of %" FORMAT_OFF_T " bytes)",
+ *ftp->bytecountp, data->set.infilesize);
+ conn->bits.close = TRUE; /* close this connection since we don't
+ know what state this error leaves us in */
+ return CURLE_PARTIAL_FILE;
+ }
+ }
+ else {
+ if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
+ (conn->maxdownload != *ftp->bytecountp)) {
+ failf(data, "Received only partial file: %" FORMAT_OFF_T " bytes",
+ *ftp->bytecountp);
+ conn->bits.close = TRUE; /* close this connection since we don't
+ know what state this error leaves us in */
+ return CURLE_PARTIAL_FILE;
+ }
+ else if(!ftp->dont_check &&
+ !*ftp->bytecountp &&
+ (conn->size>0)) {
+ /* We consider this an error, but there's no true FTP error received
+ why we need to continue to "read out" the server response too.
+ We don't want to leave a "waiting" server reply if we'll get told
+ to make a second request on this same connection! */
+ failf(data, "No data was received!");
+ result = CURLE_FTP_COULDNT_RETR_FILE;
+ }
+ }
- if(conn->resume_from) {
- /* do we still game? */
- curl_off_t passed=0;
- /* enable append instead */
- data->set.ftp_append = 1;
-
- /* Now, let's read off the proper amount of bytes from the
- input. If we knew it was a proper file we could've just
- fseek()ed but we only have a stream here */
- do {
- curl_off_t readthisamountnow = (conn->resume_from - passed);
- curl_off_t actuallyread;
-
- if(readthisamountnow > BUFSIZE)
- readthisamountnow = BUFSIZE;
-
- actuallyread = (curl_off_t)
- conn->fread(data->state.buffer, 1, (size_t)readthisamountnow,
- conn->fread_in);
-
- passed += actuallyread;
- if(actuallyread != readthisamountnow) {
- failf(data, "Could only read %" FORMAT_OFF_T
- " bytes from the input", passed);
- return CURLE_FTP_COULDNT_USE_REST;
- }
- }
- while(passed != conn->resume_from);
+ switch(status) {
+ case CURLE_BAD_DOWNLOAD_RESUME:
+ case CURLE_FTP_WEIRD_PASV_REPLY:
+ case CURLE_FTP_PORT_FAILED:
+ case CURLE_FTP_COULDNT_SET_BINARY:
+ case CURLE_FTP_COULDNT_RETR_FILE:
+ case CURLE_FTP_ACCESS_DENIED:
+ /* the connection stays alive fine even though this happened */
+ /* fall-through */
+ case CURLE_OK: /* doesn't affect the control connection's status */
+ ftp->ctl_valid = was_ctl_valid;
+ break;
+ default: /* by default, an error means the control connection is
+ wedged and should not be used anymore */
+ ftp->ctl_valid = FALSE;
+ break;
+ }
- /* now, decrease the size of the read */
- if(data->set.infilesize>0) {
- data->set.infilesize -= conn->resume_from;
+#ifdef HAVE_KRB4
+ Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]);
+#endif
- if(data->set.infilesize <= 0) {
- infof(data, "File already completely uploaded\n");
+ /* shut down the socket to inform the server we're done */
- /* no data to transfer */
- result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+#ifdef _WIN32_WCE
+ shutdown(conn->sock[SECONDARYSOCKET],2); /* SD_BOTH */
+#endif
- /* Set no_transfer so that we won't get any error in
- * Curl_ftp_done() because we didn't transfer anything! */
- ftp->no_transfer = TRUE;
+ sclose(conn->sock[SECONDARYSOCKET]);
- return CURLE_OK;
- }
- }
- /* we've passed, proceed as normal */
- }
- }
+ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
- /* Send everything on data->state.in to the socket */
- if(data->set.ftp_append) {
- /* we append onto the file instead of rewriting it */
- FTPSENDF(conn, "APPE %s", ftp->file);
- }
- else {
- FTPSENDF(conn, "STOR %s", ftp->file);
- }
+ if(!ftp->no_transfer && !status) {
+ /* Let's see what the server says about the transfer we just performed,
+ * but lower the timeout as sometimes this connection has died while the
+ * data has been transfered. This happens when doing through NATs etc that
+ * abandon old silent connections.
+ */
+ ftp->response_time = 60; /* give it only a minute for now */
result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
- if(ftpcode>=400) {
- failf(data, "Failed FTP upload: %03d", ftpcode);
- /* oops, we never close the sockets! */
- return CURLE_FTP_COULDNT_STOR_FILE;
- }
-
- if(data->set.ftp_use_port) {
- /* PORT means we are now awaiting the server to connect to us. */
- result = AllowServerConnect(conn);
- if( result )
- return result;
- }
+ ftp->response_time = 3600; /* set this back to one hour waits */
- if(conn->ssl[SECONDARYSOCKET].use) {
- /* since we only have a plaintext TCP connection here, we must now
- do the TLS stuff */
- infof(data, "Doing the SSL/TLS handshake on the data stream\n");
- result = Curl_SSLConnect(conn, SECONDARYSOCKET);
- if(result)
- return result;
+ if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) {
+ failf(data, "control connection looks dead");
+ return result;
}
- *bytecountp=0;
-
- /* When we know we're uploading a specified file, we can get the file
- size prior to the actual upload. */
-
- Curl_pgrsSetUploadSize(data, data->set.infilesize);
-
- result = Curl_Transfer(conn, -1, -1, FALSE, NULL, /* no download */
- SECONDARYSOCKET, bytecountp);
if(result)
return result;
- }
- else if(!conn->bits.no_body) {
- /* Retrieve file or directory */
- bool dirlist=FALSE;
- curl_off_t downloadsize=-1;
-
- if(conn->bits.use_range && conn->range) {
- curl_off_t from, to;
- curl_off_t totalsize=-1;
- char *ptr;
- char *ptr2;
-
- from=curlx_strtoofft(conn->range, &ptr, 0);
- while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
- ptr++;
- to=curlx_strtoofft(ptr, &ptr2, 0);
- if(ptr == ptr2) {
- /* we didn't get any digit */
- to=-1;
- }
- if((-1 == to) && (from>=0)) {
- /* X - */
- conn->resume_from = from;
- infof(data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", from);
- }
- else if(from < 0) {
- /* -Y */
- totalsize = -from;
- conn->maxdownload = -from;
- conn->resume_from = from;
- infof(data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", totalsize);
- }
- else {
- /* X-Y */
- totalsize = to-from;
- conn->maxdownload = totalsize+1; /* include the last mentioned byte */
- conn->resume_from = from;
- infof(data, "FTP RANGE from %" FORMAT_OFF_T
- " getting %" FORMAT_OFF_T " bytes\n", from, conn->maxdownload);
+ if(!ftp->dont_check) {
+ /* 226 Transfer complete, 250 Requested file action okay, completed. */
+ if((ftpcode != 226) && (ftpcode != 250)) {
+ failf(data, "server did not report OK, got %d", ftpcode);
+ return CURLE_FTP_WRITE_ERROR;
}
- infof(data, "range-download from %" FORMAT_OFF_T
- " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
- from, to, conn->maxdownload);
- ftp->dont_check = TRUE; /* dont check for successful transfer */
- }
-
- if((data->set.ftp_list_only) || !ftp->file) {
- /* The specified path ends with a slash, and therefore we think this
- is a directory that is requested, use LIST. But before that we
- need to set ASCII transfer mode. */
- dirlist = TRUE;
-
- /* Set type to ASCII */
- result = ftp_transfertype(conn, TRUE /* ASCII enforced */);
- if(result)
- return result;
-
- /* if this output is to be machine-parsed, the NLST command will be
- better used since the LIST command output is not specified or
- standard in any way */
-
- FTPSENDF(conn, "%s",
- data->set.customrequest?data->set.customrequest:
- (data->set.ftp_list_only?"NLST":"LIST"));
}
- else {
- curl_off_t foundsize;
-
- /* Set type to binary (unless specified ASCII) */
- result = ftp_transfertype(conn, data->set.ftp_ascii);
- if(result)
- return result;
-
- /* Send any PREQUOTE strings after transfer type is set? */
- if(data->set.prequote) {
- if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
- return result;
- }
+ }
- /* Attempt to get the size, it'll be useful in some cases: for resumed
- downloads and when talking to servers that don't give away the size
- in the RETR response line. */
- result = ftp_getsize(conn, ftp->file, &foundsize);
- if(CURLE_OK == result) {
- if (data->set.max_filesize && foundsize > data->set.max_filesize) {
- failf(data, "Maximum file size exceeded");
- return CURLE_FILESIZE_EXCEEDED;
- }
- downloadsize = foundsize;
- }
+ /* clear these for next connection */
+ ftp->no_transfer = FALSE;
+ ftp->dont_check = FALSE;
- if(conn->resume_from) {
+ if (!result && conn->sec_conn) { /* 3rd party transfer */
+ /* "done" with the secondary connection */
+ result = Curl_ftp_done(conn->sec_conn, status);
+ }
- /* Daniel: (August 4, 1999)
- *
- * We start with trying to use the SIZE command to figure out the size
- * of the file we're gonna get. If we can get the size, this is by far
- * the best way to know if we're trying to resume beyond the EOF.
- *
- * Daniel, November 28, 2001. We *always* get the size on downloads
- * now, so it is done before this even when not doing resumes. I saved
- * the comment above for nostalgical reasons! ;-)
- */
- if(CURLE_OK != result) {
- infof(data, "ftp server doesn't support SIZE\n");
- /* We couldn't get the size and therefore we can't know if there
- really is a part of the file left to get, although the server
- will just close the connection when we start the connection so it
- won't cause us any harm, just not make us exit as nicely. */
- }
- else {
- /* We got a file size report, so we check that there actually is a
- part of the file left to get, or else we go home. */
- if(conn->resume_from< 0) {
- /* We're supposed to download the last abs(from) bytes */
- if(foundsize < -conn->resume_from) {
- failf(data, "Offset (%" FORMAT_OFF_T
- ") was beyond file size (%" FORMAT_OFF_T ")",
- conn->resume_from, foundsize);
- return CURLE_BAD_DOWNLOAD_RESUME;
- }
- /* convert to size to download */
- downloadsize = -conn->resume_from;
- /* download from where? */
- conn->resume_from = foundsize - downloadsize;
- }
- else {
- if(foundsize < conn->resume_from) {
- failf(data, "Offset (%" FORMAT_OFF_T
- ") was beyond file size (%" FORMAT_OFF_T ")",
- conn->resume_from, foundsize);
- return CURLE_BAD_DOWNLOAD_RESUME;
- }
- /* Now store the number of bytes we are expected to download */
- downloadsize = foundsize-conn->resume_from;
- }
- }
+ /* Send any post-transfer QUOTE strings? */
+ if(!status && !result && data->set.postquote)
+ result = ftp_sendquote(conn, data->set.postquote);
- if (downloadsize == 0) {
- /* no data to transfer */
- result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
- infof(data, "File already completely downloaded\n");
+ return result;
+}
- /* Set no_transfer so that we won't get any error in Curl_ftp_done()
- * because we didn't transfer the any file */
- ftp->no_transfer = TRUE;
- return CURLE_OK;
- }
+/***********************************************************************
+ *
+ * ftp_sendquote()
+ *
+ * Where a 'quote' means a list of custom commands to send to the server.
+ * The quote list is passed as an argument.
+ */
- /* Set resume file transfer offset */
- infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
- "\n",
- conn->resume_from);
+static
+CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote)
+{
+ struct curl_slist *item;
+ ssize_t nread;
+ int ftpcode;
+ CURLcode result;
- FTPSENDF(conn, "REST %" FORMAT_OFF_T, conn->resume_from);
+ item = quote;
+ while (item) {
+ if (item->data) {
+ FTPSENDF(conn, "%s", item->data);
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+ if (result)
+ return result;
- if(ftpcode != 350) {
- failf(data, "Couldn't use REST: %03d", ftpcode);
- return CURLE_FTP_COULDNT_USE_REST;
- }
+ if (ftpcode >= 400) {
+ failf(conn->data, "QUOT string not accepted: %s", item->data);
+ return CURLE_FTP_QUOTE_ERROR;
}
-
- FTPSENDF(conn, "RETR %s", ftp->file);
}
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(result)
- return result;
+ item = item->next;
+ }
- if((ftpcode == 150) || (ftpcode == 125)) {
+ return CURLE_OK;
+}
- /*
- A;
- 150 Opening BINARY mode data connection for /etc/passwd (2241
- bytes). (ok, the file is being transfered)
+/***********************************************************************
+ *
+ * ftp_transfertype()
+ *
+ * Set transfer type. We only deal with ASCII or BINARY so this function
+ * sets one of them.
+ */
+static CURLcode ftp_transfertype(struct connectdata *conn,
+ bool ascii)
+{
+ struct SessionHandle *data = conn->data;
+ int ftpcode;
+ ssize_t nread;
+ CURLcode result;
- B:
- 150 Opening ASCII mode data connection for /bin/ls
+ FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
- C:
- 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
+ result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+ if(result)
+ return result;
- D:
- 150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
+ if(ftpcode != 200) {
+ failf(data, "Couldn't set %s mode",
+ ascii?"ASCII":"binary");
+ return ascii? CURLE_FTP_COULDNT_SET_ASCII:CURLE_FTP_COULDNT_SET_BINARY;
+ }
- E:
- 125 Data connection already open; Transfer starting. */
+ return CURLE_OK;
+}
- curl_off_t size=-1; /* default unknown size */
+/***************************************************************************
+ *
+ * ftp_pasv_verbose()
+ *
+ * This function only outputs some informationals about this second connection
+ * when we've issued a PASV command before and thus we have connected to a
+ * possibly new IP address.
+ *
+ */
+static void
+ftp_pasv_verbose(struct connectdata *conn,
+ Curl_addrinfo *ai,
+ char *newhost, /* ascii version */
+ int port)
+{
+ char buf[256];
+ Curl_printable_address(ai, buf, sizeof(buf));
+ infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port);
+}
+/*
+ Check if this is a range download, and if so, set the internal variables
+ properly.
+ */
- /*
- * It appears that there are FTP-servers that return size 0 for files
- * when SIZE is used on the file while being in BINARY mode. To work
- * around that (stupid) behavior, we attempt to parse the RETR response
- * even if the SIZE returned size zero.
- *
- * Debugging help from Salvatore Sorrentino on February 26, 2003.
- */
+static CURLcode ftp_range(struct connectdata *conn)
+{
+ curl_off_t from, to;
+ curl_off_t totalsize=-1;
+ char *ptr;
+ char *ptr2;
+ struct SessionHandle *data = conn->data;
+ struct FTP *ftp = conn->proto.ftp;
- if(!dirlist &&
- !data->set.ftp_ascii &&
- (downloadsize < 1)) {
- /*
- * It seems directory listings either don't show the size or very
- * often uses size 0 anyway. ASCII transfers may very well turn out
- * that the transfered amount of data is not the same as this line
- * tells, why using this number in those cases only confuses us.
- *
- * Example D above makes this parsing a little tricky */
- char *bytes;
- bytes=strstr(buf, " bytes");
- if(bytes--) {
- long in=bytes-buf;
- /* this is a hint there is size information in there! ;-) */
- while(--in) {
- /* scan for the parenthesis and break there */
- if('(' == *bytes)
- break;
- /* if only skip digits, or else we're in deep trouble */
- if(!isdigit((int)*bytes)) {
- bytes=NULL;
- break;
- }
- /* one more estep backwards */
- bytes--;
- }
- /* only if we have nothing but digits: */
- if(bytes++) {
- /* get the number! */
- size = curlx_strtoofft(bytes, NULL, 0);
- }
+ if(conn->bits.use_range && conn->range) {
+ from=curlx_strtoofft(conn->range, &ptr, 0);
+ while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
+ ptr++;
+ to=curlx_strtoofft(ptr, &ptr2, 0);
+ if(ptr == ptr2) {
+ /* we didn't get any digit */
+ to=-1;
+ }
+ if((-1 == to) && (from>=0)) {
+ /* X - */
+ conn->resume_from = from;
+ infof(data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", from);
+ }
+ else if(from < 0) {
+ /* -Y */
+ totalsize = -from;
+ conn->maxdownload = -from;
+ conn->resume_from = from;
+ infof(data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", totalsize);
+ }
+ else {
+ /* X-Y */
+ totalsize = to-from;
+ conn->maxdownload = totalsize+1; /* include the last mentioned byte */
+ conn->resume_from = from;
+ infof(data, "FTP RANGE from %" FORMAT_OFF_T
+ " getting %" FORMAT_OFF_T " bytes\n", from, conn->maxdownload);
+ }
+ infof(data, "range-download from %" FORMAT_OFF_T
+ " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
+ from, to, conn->maxdownload);
+ ftp->dont_check = TRUE; /* dont check for successful transfer */
+ }
+ return CURLE_OK;
+}
- }
- }
- else if(downloadsize > -1)
- size = downloadsize;
- if(data->set.ftp_use_port) {
- result = AllowServerConnect(conn);
- if( result )
- return result;
- }
+/*
+ * Curl_ftp_nextconnect()
+ *
+ * This function shall be called when the second FTP (data) connection is
+ * connected.
+ */
- if(conn->ssl[SECONDARYSOCKET].use) {
- /* since we only have a plaintext TCP connection here, we must now
- do the TLS stuff */
- infof(data, "Doing the SSL/TLS handshake on the data stream\n");
- result = Curl_SSLConnect(conn, SECONDARYSOCKET);
- if(result)
- return result;
- }
+CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
+{
+ struct SessionHandle *data=conn->data;
+ CURLcode result = CURLE_OK;
+
+ /* the ftp struct is inited in Curl_ftp_connect() */
+ struct FTP *ftp = conn->proto.ftp;
- if(size > conn->maxdownload && conn->maxdownload > 0)
- size = conn->size = conn->maxdownload;
+ infof(data, "DO-MORE phase starts\n");
- infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
+ if(!ftp->no_transfer && !conn->bits.no_body) {
+ /* a transfer is about to take place */
- /* FTP download: */
- result=Curl_Transfer(conn, SECONDARYSOCKET, size, FALSE,
- bytecountp,
- -1, NULL); /* no upload here */
- if(result)
- return result;
+ if(data->set.upload) {
+ NBFTPSENDF(conn, "TYPE %c", data->set.ftp_ascii?'A':'I');
+ state(conn, FTP_STOR_TYPE);
}
else {
- if(dirlist && (ftpcode == 450)) {
- /* simply no matching files */
- ftp->no_transfer = TRUE; /* don't think we should download anything */
+ /* download */
+ ftp->downloadsize = -1; /* unknown as of yet */
+
+ result = ftp_range(conn);
+ if(result)
+ ;
+ else if((data->set.ftp_list_only) || !ftp->file) {
+ /* The specified path ends with a slash, and therefore we think this
+ is a directory that is requested, use LIST. But before that we
+ need to set ASCII transfer mode. */
+ NBFTPSENDF(conn, "TYPE A", NULL);
+ state(conn, FTP_LIST_TYPE);
}
else {
- failf(data, "RETR failed: %03d", ftpcode);
- return CURLE_FTP_COULDNT_RETR_FILE;
+ NBFTPSENDF(conn, "TYPE %c", data->set.ftp_ascii?'A':'I');
+ state(conn, FTP_RETR_TYPE);
}
}
-
+ result = ftp_easy_statemach(conn);
}
+
+ if(ftp->no_transfer)
+ /* no data to transfer. FIX: it feels like a kludge to have this here
+ too! */
+ result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+
/* end of transfer */
+ infof(data, "DO-MORE phase ends\n");
- return CURLE_OK;
+ return result;
}
+
+
/***********************************************************************
*
* ftp_perform()
static
CURLcode ftp_perform(struct connectdata *conn,
- bool *connected) /* for the TCP connect status after
- PASV / PORT */
+ bool *connected, /* connect status after PASV / PORT */
+ bool *dophase_done)
{
/* this is FTP and no proxy */
CURLcode result=CURLE_OK;
struct SessionHandle *data=conn->data;
- char *buf = data->state.buffer; /* this is our buffer */
- /* the ftp struct is already inited in Curl_ftp_connect() */
- struct FTP *ftp = conn->proto.ftp;
+ infof(data, "DO phase starts\n");
- /* Send any QUOTE strings? */
- if(data->set.quote) {
- if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
- return result;
- }
+ *dophase_done = FALSE; /* not done yet */
- result = ftp_cwd_and_create_path(conn);
- if (result)
+ /* start the first command in the DO phase */
+ result = ftp_state_quote(conn, TRUE, FTP_QUOTE);
+ if(result)
return result;
- /* Requested time of file or time-depended transfer? */
- if((data->set.get_filetime || data->set.timecondition) &&
- ftp->file) {
- result = ftp_getfiletime(conn, ftp->file);
- switch( result )
- {
- case CURLE_FTP_COULDNT_RETR_FILE:
- case CURLE_OK:
- if(data->set.timecondition) {
- if((data->info.filetime > 0) && (data->set.timevalue > 0)) {
- switch(data->set.timecondition) {
- case CURL_TIMECOND_IFMODSINCE:
- default:
- if(data->info.filetime < data->set.timevalue) {
- infof(data, "The requested document is not new enough\n");
- ftp->no_transfer = TRUE; /* mark this to not transfer data */
- return CURLE_OK;
- }
- break;
- case CURL_TIMECOND_IFUNMODSINCE:
- if(data->info.filetime > data->set.timevalue) {
- infof(data, "The requested document is not old enough\n");
- ftp->no_transfer = TRUE; /* mark this to not transfer data */
- return CURLE_OK;
- }
- break;
- } /* switch */
- }
- else {
- infof(data, "Skipping time comparison\n");
- }
- }
- break;
- default:
- return result;
- } /* switch */
- }
-
- /* If we have selected NOBODY and HEADER, it means that we only want file
- information. Which in FTP can't be much more than the file size and
- date. */
- if(conn->bits.no_body && data->set.include_header && ftp->file) {
- /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
- may not support it! It is however the only way we have to get a file's
- size! */
- curl_off_t filesize;
- ssize_t nread;
- int ftpcode;
-
- ftp->no_transfer = TRUE; /* this means no actual transfer is made */
-
- /* Some servers return different sizes for different modes, and thus we
- must set the proper type before we check the size */
- result = ftp_transfertype(conn, data->set.ftp_ascii);
- if(result)
- return result;
-
- /* failing to get size is not a serious error */
- result = ftp_getsize(conn, ftp->file, &filesize);
-
- if(CURLE_OK == result) {
- snprintf(buf, sizeof(data->state.buffer),
- "Content-Length: %" FORMAT_OFF_T "\r\n", filesize);
- result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
- if(result)
- return result;
- }
-
- /* Determine if server can respond to REST command and therefore
- whether it can do a range */
- FTPSENDF(conn, "REST 0", NULL);
- result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
-
- if ((CURLE_OK == result) && (ftpcode == 350)) {
- result = Curl_client_write(data, CLIENTWRITE_BOTH,
- (char *)"Accept-ranges: bytes\r\n", 0);
- if(result)
- return result;
- }
-
- /* If we asked for a time of the file and we actually got one as
- well, we "emulate" a HTTP-style header in our output. */
-
-#ifdef HAVE_STRFTIME
- if(data->set.get_filetime && (data->info.filetime>=0) ) {
- struct tm *tm;
- time_t clock = (time_t)data->info.filetime;
-#ifdef HAVE_GMTIME_R
- struct tm buffer;
- tm = (struct tm *)gmtime_r(&clock, &buffer);
-#else
- tm = gmtime(&clock);
-#endif
- /* format: "Tue, 15 Nov 1994 12:45:26" */
- strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n",
- tm);
- result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
- if(result)
- return result;
- }
-#endif
-
- return CURLE_OK;
- }
-
- if(conn->bits.no_body)
- /* doesn't really transfer any data */
- ftp->no_transfer = TRUE;
- /* Get us a second connection up and connected */
- else if(data->set.ftp_use_port) {
- /* We have chosen to use the PORT command */
- result = ftp_use_port(conn);
- if(CURLE_OK == result) {
- /* we have the data connection ready */
- infof(data, "Ordered connect of the data stream with PORT!\n");
- *connected = TRUE; /* mark us "still connected" */
- }
- }
+ /* run the state-machine */
+ if(conn->data->state.used_interface == Curl_if_multi)
+ result = Curl_ftp_multi_statemach(conn, dophase_done);
else {
- /* We have chosen (this is default) to use the PASV command */
- result = ftp_use_pasv(conn, connected);
- if(CURLE_OK == result && *connected)
- infof(data, "Connected the data stream with PASV!\n");
+ result = ftp_easy_statemach(conn);
+ *dophase_done = TRUE; /* with the easy interface we are done here */
}
+ *connected = conn->bits.tcpconnect;
+
+ if(*dophase_done)
+ infof(data, "DO phase is comlete\n");
return result;
}
*
* The input argument is already checked for validity.
*/
-CURLcode Curl_ftp(struct connectdata *conn)
+CURLcode Curl_ftp(struct connectdata *conn, bool *done)
{
CURLcode retcode = CURLE_OK;
+ *done = FALSE; /* default to false */
+
retcode = ftp_parse_url_path(conn);
if (retcode)
return retcode;
- if (conn->sec_conn) /* 3rd party transfer */
+ if (conn->sec_conn) {
+ /* 3rd party transfer */
+ *done = TRUE; /* BLOCKING */
retcode = ftp_3rdparty(conn);
+ }
else
- retcode = ftp_regular_transfer(conn);
+ retcode = ftp_regular_transfer(conn, done);
return retcode;
}
/***********************************************************************
*
- * Curl_ftpsendf()
+ * Curl_(nb)ftpsendf()
*
* Sends the formated string as a ftp command to a ftp server
*
* NOTE: we build the command in a fixed-length buffer, which sets length
* restrictions on the command!
+ *
+ * The "nb" version is made to Never Block.
*/
+CURLcode Curl_nbftpsendf(struct connectdata *conn,
+ const char *fmt, ...)
+{
+ ssize_t bytes_written;
+ char s[256];
+ size_t write_len;
+ char *sptr=s;
+ CURLcode res = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+ struct SessionHandle *data = conn->data;
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(s, 250, fmt, ap);
+ va_end(ap);
+
+ strcat(s, "\r\n"); /* append a trailing CRLF */
+
+ bytes_written=0;
+ write_len = strlen(s);
+
+ res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len,
+ &bytes_written);
+
+ if(CURLE_OK != res)
+ return res;
+
+ if(conn->data->set.verbose)
+ Curl_debug(conn->data, CURLINFO_HEADER_OUT, sptr, bytes_written,
+ conn);
+
+ if(bytes_written != (ssize_t)write_len) {
+ /* the whole chunk was not sent, store the rest of the data */
+ write_len -= bytes_written;
+ sptr += bytes_written;
+ ftp->sendthis = malloc(write_len);
+ if(ftp->sendthis) {
+ memcpy(ftp->sendthis, sptr, write_len);
+ ftp->sendsize=ftp->sendleft=write_len;
+ }
+ else {
+ failf(data, "out of memory");
+ res = CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else
+ ftp->response = Curl_tvnow();
+
+ return res;
+}
+
CURLcode Curl_ftpsendf(struct connectdata *conn,
const char *fmt, ...)
{
*/
static CURLcode ftp_quit(struct connectdata *conn)
{
- ssize_t nread;
- int ftpcode;
- CURLcode ret = CURLE_OK;
+ CURLcode result = CURLE_OK;
if(conn->proto.ftp->ctl_valid) {
- ret = Curl_ftpsendf(conn, "%s", "QUIT");
- if(CURLE_OK == ret)
- ret = Curl_GetFTPResponse(&nread, conn, &ftpcode);
+ NBFTPSENDF(conn, "QUIT", NULL);
+ state(conn, FTP_QUIT);
+
+ result = ftp_easy_statemach(conn);
}
- return ret;
+ return result;
}
/***********************************************************************
* Curl_ftp_disconnect()
*
* Disconnect from an FTP server. Cleanup protocol-specific per-connection
- * resources
+ * resources. BLOCKING.
*/
CURLcode Curl_ftp_disconnect(struct connectdata *conn)
{
return result;
}
+/* call this when the DO phase has completed */
+static CURLcode ftp_dophase_done(struct connectdata *conn,
+ bool connected)
+{
+ CURLcode result = CURLE_OK;
+ struct FTP *ftp = conn->proto.ftp;
+
+ if(connected)
+ result = Curl_ftp_nextconnect(conn);
+
+ if(result && (conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD)) {
+ /* Failure detected, close the second socket if it was created already */
+ sclose(conn->sock[SECONDARYSOCKET]);
+ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
+ }
+
+ if(ftp->no_transfer)
+ /* no data to transfer */
+ result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+ else if(!connected)
+ /* since we didn't connect now, we want do_more to get called */
+ conn->bits.do_more = TRUE;
+
+ ftp->ctl_valid = TRUE; /* seems good */
+
+ return result;
+}
+
+/* called from multi.c while DOing */
+CURLcode Curl_ftp_doing(struct connectdata *conn,
+ bool *dophase_done)
+{
+ CURLcode result;
+ result = Curl_ftp_multi_statemach(conn, dophase_done);
+
+ if(*dophase_done) {
+ result = ftp_dophase_done(conn, FALSE /* not connected */);
+
+ infof(conn->data, "DO phase is comlete\n");
+ }
+ return result;
+}
/***********************************************************************
*
* ftp_regular_transfer()
*
* The input argument is already checked for validity.
- * Performs a regular transfer between local and remote hosts.
+ *
+ * Performs all commands done before a regular transfer between a local and a
+ * remote host.
*
* ftp->ctl_valid starts out as FALSE, and gets set to TRUE if we reach the
* Curl_ftp_done() function without finding any major problem.
*/
static
-CURLcode ftp_regular_transfer(struct connectdata *conn)
+CURLcode ftp_regular_transfer(struct connectdata *conn,
+ bool *dophase_done)
{
- CURLcode retcode=CURLE_OK;
+ CURLcode result=CURLE_OK;
bool connected=0;
struct SessionHandle *data = conn->data;
struct FTP *ftp;
Curl_pgrsSetUploadSize(data, 0);
Curl_pgrsSetDownloadSize(data, 0);
- retcode = ftp_perform(conn, &connected);
+ ftp->ctl_valid = TRUE; /* starts good */
- if(CURLE_OK == retcode) {
- if(connected)
- retcode = Curl_ftp_nextconnect(conn);
+ result = ftp_perform(conn,
+ &connected, /* have we connected after PASV/PORT */
+ dophase_done); /* all commands in the DO-phase done? */
- if(retcode && (conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD)) {
- /* Failure detected, close the second socket if it was created already */
- sclose(conn->sock[SECONDARYSOCKET]);
- conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
- }
+ if(CURLE_OK == result) {
- if(ftp->no_transfer)
- /* no data to transfer */
- retcode=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
- else if(!connected)
- /* since we didn't connect now, we want do_more to get called */
- conn->bits.do_more = TRUE;
+ if(!*dophase_done)
+ /* the DO phase has not completed yet */
+ return CURLE_OK;
+
+ result = ftp_dophase_done(conn, connected);
+ if(result)
+ return result;
}
else
freedirs(ftp);
- ftp->ctl_valid = TRUE; /* seems good */
-
- return retcode;
+ return result;
}
*/
static CURLcode ftp_3rdparty(struct connectdata *conn)
{
- CURLcode retcode = CURLE_OK;
+ CURLcode result = CURLE_OK;
conn->proto.ftp->ctl_valid = conn->sec_conn->proto.ftp->ctl_valid = TRUE;
conn->size = conn->sec_conn->size = -1;
- retcode = ftp_3rdparty_pretransfer(conn);
- if (!retcode)
- retcode = ftp_3rdparty_transfer(conn);
+ result = ftp_3rdparty_pretransfer(conn);
+ if (!result)
+ result = ftp_3rdparty_transfer(conn);
- return retcode;
+ return result;
}
#endif /* CURL_DISABLE_FTP */