#include "setup.h"
#ifndef CURL_DISABLE_FTP
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef WIN32
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#endif
#endif
#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
#undef in_addr_t
#define in_addr_t unsigned long
#endif
#include <curl/curl.h>
#include "urldata.h"
#include "sendf.h"
#include "easyif.h"
#include "if2ip.h"
#include "hostip.h"
#include "progress.h"
#include "transfer.h"
#include "escape.h"
#include "http.h"
#include "socks.h"
#include "ftp.h"
#ifdef HAVE_KRB4
#include "krb4.h"
#endif
#include "strtoofft.h"
#include "strequal.h"
#include "sslgen.h"
#include "connect.h"
#include "strerror.h"
#include "memory.h"
#include "inet_ntop.h"
#include "select.h"
#include "parsedate.h"
#include "sockaddr.h"
#include "multiif.h"
#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL)
#include "inet_ntoa_r.h"
#endif
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#ifdef CURLDEBUG
#include "memdebug.h"
#endif
#ifdef HAVE_NI_WITHSCOPEID
#define NIFLAGS NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID
#else
#define NIFLAGS NI_NUMERICHOST | NI_NUMERICSERV
#endif
#ifdef CURL_DISABLE_VERBOSE_STRINGS
#define ftp_pasv_verbose(a,b,c,d) do { } while (0)
#endif
static CURLcode ftp_sendquote(struct connectdata *conn,
struct curl_slist *quote);
static CURLcode ftp_quit(struct connectdata *conn);
static CURLcode ftp_parse_url_path(struct connectdata *conn);
static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done);
#ifndef CURL_DISABLE_VERBOSE_STRINGS
static void ftp_pasv_verbose(struct connectdata *conn,
Curl_addrinfo *ai,
char *newhost,
int port);
#endif
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);
static CURLcode ftp_nb_type(struct connectdata *conn,
bool ascii, ftpstate state);
static int ftp_need_type(struct connectdata *conn,
bool ascii);
#define FTPSENDF(x,y,z) if ((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \
return result
#define NBFTPSENDF(x,y,z) if ((result = Curl_nbftpsendf(x,y,z)) != CURLE_OK) \
return result
static void freedirs(struct connectdata *conn)
{
struct ftp_conn *ftpc = &conn->proto.ftpc;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
int i;
if(ftpc->dirs) {
for (i=0; i < ftpc->dirdepth; i++){
if(ftpc->dirs[i]) {
free(ftpc->dirs[i]);
ftpc->dirs[i]=NULL;
}
}
free(ftpc->dirs);
ftpc->dirs = NULL;
}
if(ftp->file) {
free(ftp->file);
ftp->file = NULL;
}
}
static bool isBadFtpString(const char *string)
{
return (bool)((NULL != strchr(string, '\r')) || (NULL != strchr(string, '\n')));
}
static CURLcode AllowServerConnect(struct connectdata *conn)
{
long timeout_ms;
struct SessionHandle *data = conn->data;
curl_socket_t sock = conn->sock[SECONDARYSOCKET];
int timeout_set = 0;
if (data->set.timeout > 0)
timeout_set += 1;
if (data->set.connecttimeout > 0)
timeout_set += 2;
switch (timeout_set) {
case 1:
timeout_ms = data->set.timeout;
break;
case 2:
timeout_ms = data->set.connecttimeout;
break;
case 3:
if (data->set.timeout < data->set.connecttimeout)
timeout_ms = data->set.timeout;
else
timeout_ms = data->set.connecttimeout;
break;
default:
timeout_ms = 60000;
break;
}
if (timeout_set > 0) {
timeout_ms -= Curl_tvdiff(Curl_tvnow(), conn->now);
if(timeout_ms < 0) {
failf(data, "Timed out before server could connect to us");
return CURLE_OPERATION_TIMEDOUT;
}
}
switch (Curl_socket_ready(sock, CURL_SOCKET_BAD, (int)timeout_ms)) {
case -1:
failf(data, "Error while waiting for server connect");
return CURLE_FTP_PORT_FAILED;
case 0:
failf(data, "Timeout while waiting for server connect");
return CURLE_FTP_PORT_FAILED;
default:
{
curl_socket_t s = CURL_SOCKET_BAD;
#ifdef ENABLE_IPV6
struct Curl_sockaddr_storage add;
#else
struct sockaddr_in add;
#endif
socklen_t size = (socklen_t) sizeof(add);
if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) {
size = sizeof(add);
s=accept(sock, (struct sockaddr *) &add, &size);
}
sclose(sock);
if (CURL_SOCKET_BAD == s) {
failf(data, "Error accept()ing server connect");
return CURLE_FTP_PORT_FAILED;
}
infof(data, "Connection accepted from server\n");
conn->sock[SECONDARYSOCKET] = s;
Curl_nonblock(s, TRUE);
}
break;
}
return CURLE_OK;
}
static void ftp_respinit(struct connectdata *conn)
{
struct ftp_conn *ftpc = &conn->proto.ftpc;
ftpc->nread_resp = 0;
ftpc->linestart_resp = conn->data->state.buffer;
}
#define lastline(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \
ISDIGIT(line[2]) && (' ' == line[3]))
static CURLcode ftp_readresp(curl_socket_t sockfd,
struct connectdata *conn,
int *ftpcode,
size_t *size)
{
int perline;
bool keepon=TRUE;
ssize_t gotbytes;
char *ptr;
struct SessionHandle *data = conn->data;
char *buf = data->state.buffer;
CURLcode result = CURLE_OK;
struct ftp_conn *ftpc = &conn->proto.ftpc;
int code = 0;
*ftpcode = 0;
ptr=buf + ftpc->nread_resp;
perline= (int)(ptr-ftpc->linestart_resp);
keepon=TRUE;
while((ftpc->nread_resp<BUFSIZE) && (keepon && !result)) {
if(ftpc->cache) {
memcpy(ptr, ftpc->cache, (int)ftpc->cache_size);
gotbytes = (int)ftpc->cache_size;
free(ftpc->cache);
ftpc->cache = NULL;
ftpc->cache_size = 0;
}
else {
int res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftpc->nread_resp,
&gotbytes);
if(res < 0)
return CURLE_OK;
#ifdef CURL_DOES_CONVERSIONS
if((res == CURLE_OK) && (gotbytes > 0)) {
result = res = Curl_convert_from_network(data, ptr, gotbytes);
}
#endif
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 {
int i;
data->reqdata.keep.headerbytecount += gotbytes;
ftpc->nread_resp += gotbytes;
for(i = 0; i < gotbytes; ptr++, i++) {
perline++;
if(*ptr=='\n') {
if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN,
ftpc->linestart_resp, (size_t)perline, conn);
result = Curl_client_write(conn, CLIENTWRITE_HEADER,
ftpc->linestart_resp, perline);
if(result)
return result;
if(perline>3 && lastline(ftpc->linestart_resp)) {
char *meow;
int n;
for(meow=ftpc->linestart_resp, n=0; meow<ptr; meow++, n++)
buf[n] = *meow;
*meow=0;
keepon=FALSE;
ftpc->linestart_resp = ptr+1;
i++;
*size = ftpc->nread_resp;
ftpc->nread_resp = 0;
break;
}
perline=0;
ftpc->linestart_resp = ptr+1;
}
}
if(!keepon && (i != gotbytes)) {
ftpc->cache_size = gotbytes - i;
ftpc->cache = (char *)malloc((int)ftpc->cache_size);
if(ftpc->cache)
memcpy(ftpc->cache, ftpc->linestart_resp, (int)ftpc->cache_size);
else
return CURLE_OUT_OF_MEMORY;
}
}
}
if(!result)
code = atoi(buf);
#ifdef HAVE_KRB4
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:
break;
}
#endif
*ftpcode=code;
conn->data->info.httpcode=code;
return result;
}
CURLcode Curl_GetFTPResponse(ssize_t *nreadp,
struct connectdata *conn,
int *ftpcode)
{
curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
int perline;
bool keepon=TRUE;
ssize_t gotbytes;
char *ptr;
long timeout;
long interval_ms;
struct SessionHandle *data = conn->data;
char *line_start;
int code=0;
char *buf = data->state.buffer;
CURLcode result = CURLE_OK;
struct ftp_conn *ftpc = &conn->proto.ftpc;
struct timeval now = Curl_tvnow();
if (ftpcode)
*ftpcode = 0;
ptr=buf;
line_start = buf;
*nreadp=0;
perline=0;
keepon=TRUE;
while((*nreadp<BUFSIZE) && (keepon && !result)) {
if(data->set.ftp_response_timeout )
timeout = data->set.ftp_response_timeout -
Curl_tvdiff(Curl_tvnow(), now);
else if(data->set.timeout)
timeout = data->set.timeout -
Curl_tvdiff(Curl_tvnow(), conn->now);
else
timeout = ftpc->response_time -
Curl_tvdiff(Curl_tvnow(), now);
if(timeout <=0 ) {
failf(data, "FTP response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
if(!ftpc->cache) {
interval_ms = 1 * 1000;
if(timeout < interval_ms)
interval_ms = timeout;
switch (Curl_socket_ready(sockfd, CURL_SOCKET_BAD, (int)interval_ms)) {
case -1:
result = CURLE_RECV_ERROR;
failf(data, "FTP response aborted due to select/poll error: %d",
SOCKERRNO);
break;
case 0:
if(Curl_pgrsUpdate(conn))
return CURLE_ABORTED_BY_CALLBACK;
continue;
default:
break;
}
}
if(CURLE_OK == result) {
if(ftpc->cache) {
memcpy(ptr, ftpc->cache, (int)ftpc->cache_size);
gotbytes = (int)ftpc->cache_size;
free(ftpc->cache);
ftpc->cache = NULL;
ftpc->cache_size = 0;
}
else {
int res = Curl_read(conn, sockfd, ptr, BUFSIZE-*nreadp, &gotbytes);
if(res < 0)
continue;
#ifdef CURL_DOES_CONVERSIONS
if((res == CURLE_OK) && (gotbytes > 0)) {
result = res = Curl_convert_from_network(data, ptr, gotbytes);
}
#endif
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 {
int i;
data->reqdata.keep.headerbytecount += gotbytes;
*nreadp += gotbytes;
for(i = 0; i < gotbytes; ptr++, i++) {
perline++;
if(*ptr=='\n') {
if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN,
line_start, (size_t)perline, conn);
result = Curl_client_write(conn, CLIENTWRITE_HEADER,
line_start, perline);
if(result)
return result;
if(perline>3 && lastline(line_start)) {
char *meow;
int n;
for(meow=line_start, n=0; meow<ptr; meow++, n++)
buf[n] = *meow;
*meow=0;
keepon=FALSE;
line_start = ptr+1;
i++;
break;
}
perline=0;
line_start = ptr+1;
}
}
if(!keepon && (i != gotbytes)) {
ftpc->cache_size = gotbytes - i;
ftpc->cache = (char *)malloc((int)ftpc->cache_size);
if(ftpc->cache)
memcpy(ftpc->cache, line_start, (int)ftpc->cache_size);
else
return CURLE_OUT_OF_MEMORY;
}
}
}
}
if(!result)
code = atoi(buf);
#ifdef HAVE_KRB4
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:
break;
}
#endif
if(ftpcode)
*ftpcode=code;
conn->data->info.httpcode=code;
return result;
}
static void state(struct connectdata *conn,
ftpstate state)
{
#if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
const char *names[]={
"STOP",
"WAIT220",
"AUTH",
"USER",
"PASS",
"ACCT",
"PBSZ",
"PROT",
"CCC",
"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"
};
#endif
struct ftp_conn *ftpc = &conn->proto.ftpc;
#if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
if(ftpc->state != state)
infof(conn->data, "FTP %p state change from %s to %s\n",
ftpc, names[ftpc->state], names[state]);
#endif
ftpc->state = state;
}
static CURLcode ftp_state_user(struct connectdata *conn)
{
CURLcode result;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
NBFTPSENDF(conn, "USER %s", ftp->user?ftp->user:"");
state(conn, FTP_USER);
conn->data->state.ftp_trying_alternative = FALSE;
return CURLE_OK;
}
static CURLcode ftp_state_pwd(struct connectdata *conn)
{
CURLcode result;
NBFTPSENDF(conn, "PWD", NULL);
state(conn, FTP_PWD);
return CURLE_OK;
}
int Curl_ftp_getsock(struct connectdata *conn,
curl_socket_t *socks,
int numsocks)
{
struct ftp_conn *ftpc = &conn->proto.ftpc;
if(!numsocks)
return GETSOCK_BLANK;
socks[0] = conn->sock[FIRSTSOCKET];
if(ftpc->sendleft) {
return GETSOCK_WRITESOCK(0);
}
return GETSOCK_READSOCK(0);
}
static CURLcode ftp_state_cwd(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct ftp_conn *ftpc = &conn->proto.ftpc;
if(ftpc->cwddone)
result = ftp_state_post_cwd(conn);
else {
ftpc->count2 = 0;
if (conn->bits.reuse && ftpc->entrypath) {
ftpc->count1 = 0;
NBFTPSENDF(conn, "CWD %s", ftpc->entrypath);
state(conn, FTP_CWD);
}
else {
if(ftpc->dirdepth) {
ftpc->count1 = 1;
NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 -1]);
state(conn, FTP_CWD);
}
else {
result = ftp_state_post_cwd(conn);
}
}
}
return result;
}
typedef enum {
EPRT,
PORT,
DONE
} ftpport;
static CURLcode ftp_state_use_port(struct connectdata *conn,
ftpport fcmd)
{
CURLcode result = CURLE_OK;
struct ftp_conn *ftpc = &conn->proto.ftpc;
struct SessionHandle *data=conn->data;
curl_socket_t portsock= CURL_SOCKET_BAD;
char myhost[256] = "";
#ifdef ENABLE_IPV6
struct Curl_sockaddr_storage ss;
struct addrinfo *res, *ai;
socklen_t sslen;
char hbuf[NI_MAXHOST];
struct sockaddr *sa=(struct sockaddr *)&ss;
char tmp[1024];
const char *mode[] = { "EPRT", "PORT", NULL };
int rc;
int error;
char *host=NULL;
struct Curl_dns_entry *h=NULL;
unsigned short port = 0;
if(data->set.ftpport && (strlen(data->set.ftpport) > 1)) {
if(!Curl_if2ip(data->set.ftpport, hbuf, sizeof(hbuf)))
host = data->set.ftpport;
else
host = hbuf;
}
if(!host) {
sslen = sizeof(ss);
if (getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen)) {
failf(data, "getsockname() failed: %s",
Curl_strerror(conn, SOCKERRNO) );
return CURLE_FTP_PORT_FAILED;
}
if (sslen > (socklen_t)sizeof(ss))
sslen = sizeof(ss);
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;
}
rc = Curl_resolv(conn, host, 0, &h);
if(rc == CURLRESOLV_PENDING)
rc = Curl_wait_for_resolv(conn, &h);
if(h) {
res = h->addr;
Curl_resolv_unlock(data, h);
}
else
res = NULL;
portsock = CURL_SOCKET_BAD;
error = 0;
for (ai = res; ai; ai = ai->ai_next) {
if (ai->ai_socktype == 0)
ai->ai_socktype = conn->socktype;
portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (portsock == CURL_SOCKET_BAD) {
error = SOCKERRNO;
continue;
}
break;
}
if(!ai) {
failf(data, "socket failure: %s", Curl_strerror(conn, error));
return CURLE_FTP_PORT_FAILED;
}
if (bind(portsock, ai->ai_addr, ai->ai_addrlen)) {
sslen = sizeof(ss);
if (getsockname(conn->sock[FIRSTSOCKET],
(struct sockaddr *)sa, &sslen)) {
failf(data, "getsockname() failed: %s",
Curl_strerror(conn, SOCKERRNO) );
sclose(portsock);
return CURLE_FTP_PORT_FAILED;
}
if(((struct sockaddr *)sa)->sa_family == AF_INET)
((struct sockaddr_in *)sa)->sin_port=0;
else
((struct sockaddr_in6 *)sa)->sin6_port =0;
if (sslen > (socklen_t)sizeof(ss))
sslen = sizeof(ss);
if(bind(portsock, (struct sockaddr *)sa, sslen)) {
failf(data, "bind failed: %s", Curl_strerror(conn, SOCKERRNO));
sclose(portsock);
return CURLE_FTP_PORT_FAILED;
}
}
sslen = sizeof(ss);
if(getsockname(portsock, (struct sockaddr *)sa, &sslen)) {
failf(data, "getsockname() failed: %s",
Curl_strerror(conn, SOCKERRNO) );
sclose(portsock);
return CURLE_FTP_PORT_FAILED;
}
if (listen(portsock, 1)) {
failf(data, "socket failure: %s", Curl_strerror(conn, SOCKERRNO));
sclose(portsock);
return CURLE_FTP_PORT_FAILED;
}
Curl_printable_address(ai, myhost, sizeof(myhost));
#ifdef PF_INET6
if(!conn->bits.ftp_use_eprt && conn->bits.ipv6)
conn->bits.ftp_use_eprt = TRUE;
#endif
for (; fcmd != DONE; fcmd++) {
if(!conn->bits.ftp_use_eprt && (EPRT == fcmd))
continue;
switch (sa->sa_family) {
case AF_INET:
port = ntohs(((struct sockaddr_in *)sa)->sin_port);
break;
case AF_INET6:
port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
break;
default:
break;
}
if (EPRT == fcmd) {
result = Curl_nbftpsendf(conn, "%s |%d|%s|%d|", mode[fcmd],
ai->ai_family == AF_INET?1:2,
myhost, port);
if(result)
return result;
break;
}
else if (PORT == fcmd) {
char *source = myhost;
char *dest = tmp;
if ((PORT == fcmd) && ai->ai_family != AF_INET)
continue;
while(source && *source) {
if(*source == '.')
*dest=',';
else
*dest = *source;
dest++;
source++;
}
*dest = 0;
snprintf(dest, 20, ",%d,%d", port>>8, port&0xff);
result = Curl_nbftpsendf(conn, "%s %s", mode[fcmd], tmp);
if(result)
return result;
break;
}
}
ftpc->count1 = fcmd;
if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET])
sclose(conn->sock[SECONDARYSOCKET]);
conn->sock[SECONDARYSOCKET] = portsock;
#else
struct sockaddr_in sa;
unsigned short porttouse;
bool sa_filled_in = FALSE;
Curl_addrinfo *addr = NULL;
unsigned short ip[4];
bool freeaddr = TRUE;
socklen_t sslen = sizeof(sa);
(void)fcmd;
if(data->set.ftpport) {
in_addr_t in;
in=inet_addr(data->set.ftpport);
if(in != CURL_INADDR_NONE)
addr = Curl_ip2addr(in, data->set.ftpport, 0);
else {
if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
in=inet_addr(myhost);
addr = Curl_ip2addr(in, myhost, 0);
}
else if(strlen(data->set.ftpport)> 1) {
struct Curl_dns_entry *h=NULL;
int rc = Curl_resolv(conn, data->set.ftpport, 0, &h);
if(rc == CURLRESOLV_PENDING)
rc = Curl_wait_for_resolv(conn, &h);
if(h) {
addr = h->addr;
Curl_resolv_unlock(data, h);
freeaddr = FALSE;
}
else {
infof(data, "Failed to resolve host name %s\n", data->set.ftpport);
}
}
}
}
if(!addr) {
if (getsockname(conn->sock[FIRSTSOCKET],
(struct sockaddr *)&sa, &sslen)) {
failf(data, "getsockname() failed: %s",
Curl_strerror(conn, SOCKERRNO) );
return CURLE_FTP_PORT_FAILED;
}
if (sslen > (socklen_t)sizeof(sa))
sslen = sizeof(sa);
sa_filled_in = TRUE;
}
if (addr || sa_filled_in) {
portsock = socket(AF_INET, SOCK_STREAM, 0);
if(CURL_SOCKET_BAD != portsock) {
if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET])
sclose(conn->sock[SECONDARYSOCKET]);
conn->sock[SECONDARYSOCKET] = portsock;
if(!sa_filled_in) {
memcpy(&sa, addr->ai_addr, sslen);
sa.sin_addr.s_addr = INADDR_ANY;
}
sa.sin_port = 0;
sslen = sizeof(sa);
if(bind(portsock, (struct sockaddr *)&sa, sslen) == 0) {
struct sockaddr_in add;
socklen_t socksize = sizeof(add);
if(getsockname(portsock, (struct sockaddr *) &add,
&socksize)) {
failf(data, "getsockname() failed: %s",
Curl_strerror(conn, SOCKERRNO) );
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, "couldn'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;
if(freeaddr)
Curl_freeaddrinfo(addr);
ftpc->count1 = PORT;
#endif
conn->bits.tcpconnect = TRUE;
state(conn, FTP_PORT);
return result;
}
static CURLcode ftp_state_use_pasv(struct connectdata *conn)
{
struct ftp_conn *ftpc = &conn->proto.ftpc;
CURLcode result = CURLE_OK;
const char *mode[] = { "EPSV", "PASV", NULL };
int modeoff;
#ifdef PF_INET6
if(!conn->bits.ftp_use_epsv && conn->bits.ipv6)
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;
ftpc->count1 = modeoff;
state(conn, FTP_PASV);
infof(conn->data, "Connect data stream passively\n");
return result;
}
static CURLcode ftp_state_post_rest(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
struct SessionHandle *data = conn->data;
if(ftp->no_transfer) {
state(conn, FTP_RETR_PREQUOTE);
result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE);
}
else if(data->set.ftp_use_port) {
result = ftp_state_use_port(conn, EPRT);
}
else {
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->data->reqdata.proto.ftp;
if(ftp->no_transfer) {
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->data->reqdata.proto.ftp;
if(ftp->no_transfer) {
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;
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;
result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE);
return result;
}
static CURLcode ftp_state_post_stortype(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE);
return result;
}
static CURLcode ftp_state_post_mdtm(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
struct SessionHandle *data = conn->data;
if(conn->bits.no_body && data->set.include_header && ftp->file &&
ftp_need_type(conn, data->set.prefer_ascii)) {
ftp->no_transfer = TRUE;
result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_TYPE);
if (result)
return result;
}
else
result = ftp_state_post_type(conn);
return result;
}
static CURLcode ftp_state_post_cwd(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
struct SessionHandle *data = conn->data;
if((data->set.get_filetime || data->set.timecondition) && ftp->file) {
NBFTPSENDF(conn, "MDTM %s", ftp->file);
state(conn, FTP_MDTM);
}
else
result = ftp_state_post_mdtm(conn);
return result;
}
static CURLcode ftp_state_ul_setup(struct connectdata *conn,
bool sizechecked)
{
CURLcode result = CURLE_OK;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
struct SessionHandle *data = conn->data;
curl_off_t passed=0;
if((data->reqdata.resume_from && !sizechecked) ||
((data->reqdata.resume_from > 0) && sizechecked)) {
if(data->reqdata.resume_from < 0 ) {
NBFTPSENDF(conn, "SIZE %s", ftp->file);
state(conn, FTP_STOR_SIZE);
return result;
}
data->set.ftp_append = TRUE;
do {
curl_off_t readthisamountnow = (data->reqdata.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 != data->reqdata.resume_from);
if(data->set.infilesize>0) {
data->set.infilesize -= data->reqdata.resume_from;
if(data->set.infilesize <= 0) {
infof(data, "File already completely uploaded\n");
result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
ftp->no_transfer = TRUE;
state(conn, FTP_STOP);
return CURLE_OK;
}
}
}
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 SessionHandle *data = conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
struct ftp_conn *ftpc = &conn->proto.ftpc;
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;
}
if(init)
ftpc->count1 = 0;
else
ftpc->count1++;
if(item) {
int i = 0;
while((i< ftpc->count1) && item) {
item = item->next;
i++;
}
if(item) {
NBFTPSENDF(conn, "%s", item->data);
state(conn, instate);
quote = TRUE;
}
}
if(!quote) {
switch(instate) {
case FTP_QUOTE:
default:
result = ftp_state_cwd(conn);
break;
case FTP_RETR_PREQUOTE:
if (ftp->no_transfer)
state(conn, FTP_STOP);
else {
NBFTPSENDF(conn, "SIZE %s", ftp->file);
state(conn, FTP_RETR_SIZE);
}
break;
case FTP_STOR_PREQUOTE:
result = ftp_state_ul_setup(conn, FALSE);
break;
case FTP_POSTQUOTE:
break;
}
}
return result;
}
static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
int ftpcode)
{
struct ftp_conn *ftpc = &conn->proto.ftpc;
CURLcode result;
struct SessionHandle *data=conn->data;
Curl_addrinfo *conninfo;
struct Curl_dns_entry *addr=NULL;
int rc;
unsigned short connectport;
unsigned short newport=0;
bool connected;
#define NEWHOST_BUFSIZE 48
char newhost[NEWHOST_BUFSIZE];
char *str=&data->state.buffer[4];
if((ftpc->count1 == 0) &&
(ftpcode == 229)) {
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])) {
const char sep1 = separator[0];
int i;
for(i=1; i<4; i++) {
if(separator[i] != sep1) {
ptr=NULL;
break;
}
}
if(ptr) {
newport = (unsigned short)(num & 0xffff);
if (conn->bits.tunnel_proxy)
snprintf(newhost, sizeof(newhost), "%s", conn->host.name);
else
snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str);
}
}
else
ptr=NULL;
}
if(!ptr) {
failf(data, "Weirdly formatted EPSV reply");
return CURLE_FTP_WEIRD_PASV_REPLY;
}
}
else if((ftpc->count1 == 1) &&
(ftpcode == 227)) {
int ip[4];
int port[2];
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;
}
if(data->set.ftp_skip_ip) {
infof(data, "Skips %d.%d.%d.%d for data connection, uses %s instead\n",
ip[0], ip[1], ip[2], ip[3],
conn->ip_addr_str);
if (conn->bits.tunnel_proxy)
snprintf(newhost, sizeof(newhost), "%s", conn->host.name);
else
snprintf(newhost, sizeof(newhost), "%s", conn->ip_addr_str);
}
else
snprintf(newhost, sizeof(newhost),
"%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
newport = (unsigned short)(((port[0]<<8) + port[1]) & 0xffff);
}
else if(ftpc->count1 == 0) {
conn->bits.ftp_use_epsv = FALSE;
infof(data, "disabling EPSV usage\n");
NBFTPSENDF(conn, "PASV", NULL);
ftpc->count1++;
return result;
}
else {
failf(data, "Bad PASV/EPSV response: %03d", ftpcode);
return CURLE_FTP_WEIRD_PASV_REPLY;
}
if(data->set.proxy && *data->set.proxy) {
rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr);
if(rc == CURLRESOLV_PENDING)
rc = Curl_wait_for_resolv(conn, &addr);
connectport =
(unsigned short)conn->port;
}
else {
rc = Curl_resolv(conn, newhost, newport, &addr);
if(rc == CURLRESOLV_PENDING)
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;
}
result = Curl_connecthost(conn,
addr,
&conn->sock[SECONDARYSOCKET],
&conninfo,
&connected);
Curl_resolv_unlock(data, addr);
if (result && ftpc->count1 == 0 && ftpcode == 229) {
infof(data, "got positive EPSV response, but can't connect. "
"Disabling EPSV\n");
conn->bits.ftp_use_epsv = FALSE;
data->state.errorbuf = FALSE;
NBFTPSENDF(conn, "PASV", NULL);
ftpc->count1++;
return result;
}
if(result)
return result;
conn->bits.tcpconnect = connected;
if(data->set.verbose)
ftp_pasv_verbose(conn, conninfo, newhost, connectport);
switch(data->set.proxytype) {
case CURLPROXY_SOCKS5:
result = Curl_SOCKS5(conn->proxyuser, conn->proxypasswd, newhost, newport,
SECONDARYSOCKET, conn);
break;
case CURLPROXY_HTTP:
break;
case CURLPROXY_SOCKS4:
result = Curl_SOCKS4(conn->proxyuser, newhost, newport,
SECONDARYSOCKET, conn);
break;
default:
failf(data, "unknown proxytype option given");
result = CURLE_COULDNT_CONNECT;
break;
}
#ifndef CURL_DISABLE_HTTP
if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
struct HTTP http_proxy;
struct FTP *ftp_save = data->reqdata.proto.ftp;
memset(&http_proxy, 0, sizeof(http_proxy));
data->reqdata.proto.http = &http_proxy;
result = Curl_proxyCONNECT(conn, SECONDARYSOCKET, newhost, newport);
data->reqdata.proto.ftp = ftp_save;
if(CURLE_OK != result)
return result;
}
#endif
state(conn, FTP_STOP);
return result;
}
static CURLcode ftp_state_port_resp(struct connectdata *conn,
int ftpcode)
{
struct SessionHandle *data = conn->data;
struct ftp_conn *ftpc = &conn->proto.ftpc;
ftpport fcmd = (ftpport)ftpc->count1;
CURLcode result = CURLE_OK;
if(ftpcode != 200) {
if (EPRT == fcmd) {
infof(data, "disabling EPRT usage\n");
conn->bits.ftp_use_eprt = FALSE;
}
fcmd++;
if(fcmd == DONE) {
failf(data, "Failed to do PORT");
result = CURLE_FTP_PORT_FAILED;
}
else
result = ftp_state_use_port(conn, fcmd);
}
else {
infof(data, "Connect data stream actively\n");
state(conn, FTP_STOP);
}
return result;
}
static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
int ftpcode)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data=conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
switch(ftpcode) {
case 213:
{
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)) {
time_t secs=time(NULL);
snprintf(buf, sizeof(conn->data->state.buffer),
"%04d%02d%02d %02d:%02d:%02d GMT",
year, month, day, hour, minute, second);
data->info.filetime = (long)curl_getdate(buf, &secs);
}
if(conn->bits.no_body &&
data->set.include_header &&
ftp->file &&
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
snprintf(buf, BUFSIZE-1,
"Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
tm->tm_mday,
Curl_month[tm->tm_mon],
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
}
}
break;
default:
infof(data, "unsupported MDTM reply format\n");
break;
case 550:
failf(data, "Given file does not exist");
result = CURLE_FTP_COULDNT_RETR_FILE;
break;
}
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;
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;
state(conn, FTP_STOP);
return CURLE_OK;
}
break;
}
}
else {
infof(data, "Skipping time comparison\n");
}
}
if(!result)
result = ftp_state_post_mdtm(conn);
return result;
}
static CURLcode ftp_state_type_resp(struct connectdata *conn,
int ftpcode,
ftpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data=conn->data;
if(ftpcode/100 != 2) {
failf(data, "Couldn't set desired mode");
return CURLE_FTP_COULDNT_SET_BINARY;
}
if(ftpcode != 200)
infof(data, "Got a %03d response code instead of the assumed 200\n",
ftpcode);
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);
return result;
}
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 = data->reqdata.proto.ftp;
if (data->set.max_filesize && (filesize > data->set.max_filesize)) {
failf(data, "Maximum file size exceeded");
return CURLE_FILESIZE_EXCEEDED;
}
ftp->downloadsize = filesize;
if(data->reqdata.resume_from) {
if(filesize == -1) {
infof(data, "ftp server doesn't support SIZE\n");
}
else {
if(data->reqdata.resume_from< 0) {
if(filesize < -data->reqdata.resume_from) {
failf(data, "Offset (%" FORMAT_OFF_T
") was beyond file size (%" FORMAT_OFF_T ")",
data->reqdata.resume_from, filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
ftp->downloadsize = -data->reqdata.resume_from;
data->reqdata.resume_from = filesize - ftp->downloadsize;
}
else {
if(filesize < data->reqdata.resume_from) {
failf(data, "Offset (%" FORMAT_OFF_T
") was beyond file size (%" FORMAT_OFF_T ")",
data->reqdata.resume_from, filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
ftp->downloadsize = filesize-data->reqdata.resume_from;
}
}
if(ftp->downloadsize == 0) {
result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
infof(data, "File already completely downloaded\n");
ftp->no_transfer = TRUE;
state(conn, FTP_STOP);
return CURLE_OK;
}
infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T
"\n", data->reqdata.resume_from);
NBFTPSENDF(conn, "REST %" FORMAT_OFF_T, data->reqdata.resume_from);
state(conn, FTP_RETR_REST);
}
else {
NBFTPSENDF(conn, "RETR %s", ftp->file);
state(conn, FTP_RETR);
}
return result;
}
static CURLcode ftp_state_size_resp(struct connectdata *conn,
int ftpcode,
ftpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data=conn->data;
curl_off_t filesize;
char *buf = data->state.buffer;
filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1;
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(conn, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
}
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) {
data->reqdata.resume_from = filesize;
result = ftp_state_ul_setup(conn, TRUE);
}
return result;
}
static CURLcode ftp_state_rest_resp(struct connectdata *conn,
int ftpcode,
ftpstate instate)
{
CURLcode result = CURLE_OK;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
switch(instate) {
case FTP_REST:
default:
if (ftpcode == 350) {
result = Curl_client_write(conn, CLIENTWRITE_BOTH,
(char *)"Accept-ranges: bytes\r\n", 0);
if(result)
return result;
}
result = ftp_state_post_rest(conn);
break;
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;
}
return result;
}
static CURLcode ftp_state_stor_resp(struct connectdata *conn,
int ftpcode)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
if(ftpcode>=400) {
failf(data, "Failed FTP upload: %0d", ftpcode);
return CURLE_FTP_COULDNT_STOR_FILE;
}
if(data->set.ftp_use_port) {
result = AllowServerConnect(conn);
if( result )
return result;
}
if(conn->ssl[SECONDARYSOCKET].use) {
infof(data, "Doing the SSL/TLS handshake on the data stream\n");
result = Curl_ssl_connect(conn, SECONDARYSOCKET);
if(result)
return result;
}
*(ftp->bytecountp)=0;
Curl_pgrsSetUploadSize(data, data->set.infilesize);
result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL,
SECONDARYSOCKET, ftp->bytecountp);
state(conn, FTP_STOP);
return result;
}
static CURLcode ftp_state_get_resp(struct connectdata *conn,
int ftpcode,
ftpstate instate)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
char *buf = data->state.buffer;
if((ftpcode == 150) || (ftpcode == 125)) {
curl_off_t size=-1;
if((instate != FTP_LIST) &&
!data->set.prefer_ascii &&
(ftp->downloadsize < 1)) {
char *bytes;
bytes=strstr(buf, " bytes");
if(bytes--) {
long in=(long)(bytes-buf);
while(--in) {
if('(' == *bytes)
break;
if(!ISDIGIT(*bytes)) {
bytes=NULL;
break;
}
bytes--;
}
if(bytes++) {
size = curlx_strtoofft(bytes, NULL, 0);
}
}
}
else if(ftp->downloadsize > -1)
size = ftp->downloadsize;
if(data->set.ftp_use_port) {
result = AllowServerConnect(conn);
if( result )
return result;
}
if(conn->ssl[SECONDARYSOCKET].use) {
infof(data, "Doing the SSL/TLS handshake on the data stream\n");
result = Curl_ssl_connect(conn, SECONDARYSOCKET);
if(result)
return result;
}
if(size > data->reqdata.maxdownload && data->reqdata.maxdownload > 0)
size = data->reqdata.size = data->reqdata.maxdownload;
infof(data, "Maxdownload = %" FORMAT_OFF_T "\n", data->reqdata.maxdownload);
if(instate != FTP_LIST)
infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
result=Curl_setup_transfer(conn, SECONDARYSOCKET, size, FALSE,
ftp->bytecountp,
-1, NULL);
if(result)
return result;
state(conn, FTP_STOP);
}
else {
if((instate == FTP_LIST) && (ftpcode == 450)) {
ftp->no_transfer = TRUE;
state(conn, FTP_STOP);
}
else {
failf(data, "RETR response: %03d", ftpcode);
return CURLE_FTP_COULDNT_RETR_FILE;
}
}
return result;
}
static CURLcode ftp_state_loggedin(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
#ifdef HAVE_KRB4
if(conn->data->set.krb4) {
if(conn->sec_complete)
Curl_sec_set_protection_level(conn);
if(conn->passwd && *conn->passwd) {
result = Curl_krb_kauth(conn);
if(result)
return result;
}
}
#endif
if(conn->ssl[FIRSTSOCKET].use) {
NBFTPSENDF(conn, "PBSZ %d", 0);
state(conn, FTP_PBSZ);
}
else {
result = ftp_state_pwd(conn);
}
return result;
}
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 = data->reqdata.proto.ftp;
struct ftp_conn *ftpc = &conn->proto.ftpc;
(void)instate;
if((ftpcode == 331) && (ftpc->state == FTP_USER)) {
NBFTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:"");
state(conn, FTP_PASS);
}
else if(ftpcode/100 == 2) {
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 but none available");
result = CURLE_LOGIN_DENIED;
}
}
else {
if (conn->data->set.ftp_alternative_to_user &&
!conn->data->state.ftp_trying_alternative) {
NBFTPSENDF(conn, "%s", conn->data->set.ftp_alternative_to_user);
conn->data->state.ftp_trying_alternative = TRUE;
state(conn, FTP_USER);
result = CURLE_OK;
}
else {
failf(data, "Access denied: %03d", ftpcode);
result = CURLE_LOGIN_DENIED;
}
}
return result;
}
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;
}
else
result = ftp_state_loggedin(conn);
return result;
}
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_conn *ftpc = &conn->proto.ftpc;
static const char * const ftpauth[] = {
"SSL", "TLS"
};
size_t nread = 0;
if(ftpc->sendleft) {
ssize_t written;
result = Curl_write(conn, sock, ftpc->sendthis + ftpc->sendsize -
ftpc->sendleft, ftpc->sendleft, &written);
if(result)
return result;
if(written != (ssize_t)ftpc->sendleft) {
ftpc->sendleft -= written;
}
else {
free(ftpc->sendthis);
ftpc->sendthis=NULL;
ftpc->sendleft = ftpc->sendsize = 0;
ftpc->response = Curl_tvnow();
}
return CURLE_OK;
}
result = ftp_readresp(sock, conn, &ftpcode, &nread);
if(result)
return result;
if(ftpcode) {
switch(ftpc->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;
}
#ifdef HAVE_KRB4
if(data->set.krb4) {
Curl_sec_request_prot(conn, "private");
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
if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
ftpc->count3=0;
switch(data->set.ftpsslauth) {
case CURLFTPAUTH_DEFAULT:
case CURLFTPAUTH_SSL:
ftpc->count2 = 1;
ftpc->count1 = 0;
break;
case CURLFTPAUTH_TLS:
ftpc->count2 = -1;
ftpc->count1 = 1;
break;
default:
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d\n",
data->set.ftpsslauth);
return CURLE_FAILED_INIT;
}
NBFTPSENDF(conn, "AUTH %s", ftpauth[ftpc->count1]);
state(conn, FTP_AUTH);
}
else {
result = ftp_state_user(conn);
if(result)
return result;
}
break;
case FTP_AUTH:
if((ftpcode == 234) || (ftpcode == 334)) {
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(CURLE_OK == result) {
conn->protocol |= PROT_FTPS;
conn->ssl[SECONDARYSOCKET].use = FALSE;
result = ftp_state_user(conn);
}
}
else if(ftpc->count3 < 1) {
ftpc->count3++;
ftpc->count1 += ftpc->count2;
result = Curl_nbftpsendf(conn, "AUTH %s", ftpauth[ftpc->count1]);
}
else {
if(data->set.ftp_ssl > CURLFTPSSL_TRY)
result = CURLE_FTP_SSL_FAILED;
else
result = ftp_state_user(conn);
}
if(result)
return result;
break;
case FTP_USER:
case FTP_PASS:
result = ftp_state_user_resp(conn, ftpcode, ftpc->state);
break;
case FTP_ACCT:
result = ftp_state_acct_resp(conn, ftpcode);
break;
case FTP_PBSZ:
if(!conn->ssl[SECONDARYSOCKET].use) {
NBFTPSENDF(conn, "PROT %c",
data->set.ftp_ssl == CURLFTPSSL_CONTROL ? 'C' : 'P');
state(conn, FTP_PROT);
}
else {
result = ftp_state_pwd(conn);
if(result)
return result;
}
break;
case FTP_PROT:
if(ftpcode/100 == 2)
conn->ssl[SECONDARYSOCKET].use =
(bool)(data->set.ftp_ssl != CURLFTPSSL_CONTROL);
else if(data->set.ftp_ssl> CURLFTPSSL_CONTROL)
return CURLE_FTP_SSL_FAILED;
if(data->set.ftp_ccc) {
NBFTPSENDF(conn, "CCC", NULL);
state(conn, FTP_CCC);
}
else {
result = ftp_state_pwd(conn);
if(result)
return result;
}
break;
case FTP_CCC:
if (ftpcode < 500) {
result = Curl_ssl_shutdown(conn, FIRSTSOCKET);
if(result) {
failf(conn->data, "Failed to clear the command channel (CCC)");
return result;
}
}
result = ftp_state_pwd(conn);
if(result)
return result;
break;
case FTP_PWD:
if(ftpcode == 257) {
char *dir = (char *)malloc(nread+1);
char *store=dir;
char *ptr=&data->state.buffer[4];
if(!dir)
return CURLE_OUT_OF_MEMORY;
if('\"' == *ptr) {
ptr++;
while(ptr && *ptr) {
if('\"' == *ptr) {
if('\"' == ptr[1]) {
*store = ptr[1];
ptr++;
}
else {
*store = '\0';
break;
}
}
else
*store = *ptr;
store++;
ptr++;
}
ftpc->entrypath =dir;
infof(data, "Entry path is '%s'\n", ftpc->entrypath);
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
}
else {
free(dir);
infof(data, "Failed to figure out path\n");
}
}
state(conn, FTP_STOP);
DEBUGF(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;
}
result = ftp_state_quote(conn, FALSE, ftpc->state);
if(result)
return result;
break;
case FTP_CWD:
if(ftpcode/100 != 2) {
if(conn->data->set.ftp_create_missing_dirs &&
ftpc->count1 && !ftpc->count2) {
ftpc->count2++;
NBFTPSENDF(conn, "MKD %s", ftpc->dirs[ftpc->count1 - 1]);
state(conn, FTP_MKD);
}
else {
failf(data, "Server denied you to change to the given directory");
ftpc->cwdfail = TRUE;
return CURLE_FTP_ACCESS_DENIED;
}
}
else {
ftpc->count2=0;
if(++ftpc->count1 <= ftpc->dirdepth) {
NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
}
else {
result = ftp_state_post_cwd(conn);
if(result)
return result;
}
}
break;
case FTP_MKD:
if(ftpcode/100 != 2) {
failf(data, "Failed to MKD dir: %03d", ftpcode);
return CURLE_FTP_ACCESS_DENIED;
}
state(conn, FTP_CWD);
NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
break;
case FTP_MDTM:
result = ftp_state_mdtm_resp(conn, ftpcode);
break;
case FTP_TYPE:
case FTP_LIST_TYPE:
case FTP_RETR_TYPE:
case FTP_STOR_TYPE:
result = ftp_state_type_resp(conn, ftpcode, ftpc->state);
break;
case FTP_SIZE:
case FTP_RETR_SIZE:
case FTP_STOR_SIZE:
result = ftp_state_size_resp(conn, ftpcode, ftpc->state);
break;
case FTP_REST:
case FTP_RETR_REST:
result = ftp_state_rest_resp(conn, ftpcode, ftpc->state);
break;
case FTP_PASV:
result = ftp_state_pasv_resp(conn, ftpcode);
break;
case FTP_PORT:
result = ftp_state_port_resp(conn, ftpcode);
break;
case FTP_LIST:
case FTP_RETR:
result = ftp_state_get_resp(conn, ftpcode, ftpc->state);
break;
case FTP_STOR:
result = ftp_state_stor_resp(conn, ftpcode);
break;
case FTP_QUIT:
default:
state(conn, FTP_STOP);
break;
}
}
return result;
}
static long ftp_state_timeout(struct connectdata *conn)
{
struct SessionHandle *data=conn->data;
struct ftp_conn *ftpc = &conn->proto.ftpc;
long timeout_ms=360000;
if(data->set.ftp_response_timeout )
timeout_ms = data->set.ftp_response_timeout -
Curl_tvdiff(Curl_tvnow(), ftpc->response);
else if(data->set.timeout)
timeout_ms = data->set.timeout -
Curl_tvdiff(Curl_tvnow(), conn->now);
else
timeout_ms = ftpc->response_time -
Curl_tvdiff(Curl_tvnow(), ftpc->response);
return timeout_ms;
}
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_conn *ftpc = &conn->proto.ftpc;
CURLcode result = CURLE_OK;
long timeout_ms = ftp_state_timeout(conn);
*done = FALSE;
if(timeout_ms <= 0) {
failf(data, "FTP response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
rc = Curl_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock,
ftpc->sendleft?sock:CURL_SOCKET_BAD,
0);
if(rc == -1) {
failf(data, "select/poll error");
return CURLE_OUT_OF_MEMORY;
}
else if(rc != 0) {
result = ftp_statemach_act(conn);
*done = (bool)(ftpc->state == FTP_STOP);
}
return result;
}
static CURLcode ftp_easy_statemach(struct connectdata *conn)
{
curl_socket_t sock = conn->sock[FIRSTSOCKET];
int rc;
struct SessionHandle *data=conn->data;
struct ftp_conn *ftpc = &conn->proto.ftpc;
CURLcode result = CURLE_OK;
while(ftpc->state != FTP_STOP) {
long timeout_ms = ftp_state_timeout(conn);
if(timeout_ms <=0 ) {
failf(data, "FTP response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
rc = Curl_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock,
ftpc->sendleft?sock:CURL_SOCKET_BAD,
(int)timeout_ms);
if(rc == -1) {
failf(data, "select/poll error");
return CURLE_OUT_OF_MEMORY;
}
else if(rc == 0) {
result = CURLE_OPERATION_TIMEDOUT;
break;
}
else {
result = ftp_statemach_act(conn);
if(result)
break;
}
}
return result;
}
static CURLcode ftp_init(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct FTP *ftp;
if(data->reqdata.proto.ftp)
return CURLE_OK;
ftp = (struct FTP *)calloc(sizeof(struct FTP), 1);
if(!ftp)
return CURLE_OUT_OF_MEMORY;
data->reqdata.proto.ftp = ftp;
ftp->bytecountp = &data->reqdata.keep.bytecount;
ftp->user = conn->user;
ftp->passwd = conn->passwd;
if (isBadFtpString(ftp->user) || isBadFtpString(ftp->passwd))
return CURLE_URL_MALFORMAT;
return CURLE_OK;
}
CURLcode Curl_ftp_connect(struct connectdata *conn,
bool *done)
{
CURLcode result;
#ifndef CURL_DISABLE_HTTP
struct HTTP http_proxy;
struct FTP *ftp_save;
#endif
struct ftp_conn *ftpc = &conn->proto.ftpc;
struct SessionHandle *data=conn->data;
*done = FALSE;
if (data->reqdata.proto.ftp) {
Curl_ftp_disconnect(conn);
free(data->reqdata.proto.ftp);
data->reqdata.proto.ftp = NULL;
}
result = ftp_init(conn);
if(result)
return result;
conn->bits.close = FALSE;
ftpc->response_time = 3600000;
#ifndef CURL_DISABLE_HTTP
if (conn->bits.tunnel_proxy && conn->bits.httpproxy) {
ftp_save = data->reqdata.proto.ftp;
memset(&http_proxy, 0, sizeof(http_proxy));
data->reqdata.proto.http = &http_proxy;
result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
conn->host.name, conn->remote_port);
data->reqdata.proto.ftp = ftp_save;
if(CURLE_OK != result)
return result;
}
#endif
if(conn->protocol & PROT_FTPS) {
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(result)
return result;
}
ftp_respinit(conn);
state(conn, FTP_WAIT220);
ftpc->response = Curl_tvnow();
if(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;
}
CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status, bool premature)
{
struct SessionHandle *data = conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
struct ftp_conn *ftpc = &conn->proto.ftpc;
ssize_t nread;
int ftpcode;
CURLcode result=CURLE_OK;
bool was_ctl_valid = ftpc->ctl_valid;
char *path;
char *path_to_use = data->reqdata.path;
struct Curl_transfer_keeper *k = &data->reqdata.keep;
if(!ftp)
return CURLE_OK;
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_COULDNT_STOR_FILE:
case CURLE_FTP_ACCESS_DENIED:
case CURLE_FILESIZE_EXCEEDED:
case CURLE_OK:
if (!premature) {
ftpc->ctl_valid = was_ctl_valid;
break;
}
default:
ftpc->ctl_valid = FALSE;
ftpc->cwdfail = TRUE;
conn->bits.close = TRUE;
break;
}
if(ftpc->prevpath)
free(ftpc->prevpath);
path = curl_easy_unescape(data, path_to_use, 0, NULL);
if(!path) {
ftpc->prevpath = NULL;
} else {
size_t flen = ftp->file?strlen(ftp->file):0;
size_t dlen = strlen(path)-flen;
if(dlen && !ftpc->cwdfail) {
ftpc->prevpath = path;
if(flen)
ftpc->prevpath[dlen]=0;
infof(data, "Remembering we are in dir %s\n", ftpc->prevpath);
}
else {
ftpc->prevpath = NULL;
free(path);
}
}
freedirs(conn);
#ifdef HAVE_KRB4
Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]);
#endif
#ifdef _WIN32_WCE
shutdown(conn->sock[SECONDARYSOCKET],2);
#endif
sclose(conn->sock[SECONDARYSOCKET]);
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
if(!ftp->no_transfer && !status && !premature) {
long old_time = ftpc->response_time;
ftpc->response_time = 60000;
result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
ftpc->response_time = old_time;
if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) {
failf(data, "control connection looks dead");
ftpc->ctl_valid = FALSE;
return result;
}
if(result)
return result;
if(!ftpc->dont_check) {
if((ftpcode != 226) && (ftpcode != 250)) {
failf(data, "server did not report OK, got %d", ftpcode);
result = CURLE_PARTIAL_FILE;
}
}
}
if(result || premature)
;
else if(data->set.upload) {
if((-1 != data->set.infilesize) &&
(data->set.infilesize != *ftp->bytecountp) &&
!data->set.crlf &&
!ftp->no_transfer) {
failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T
" out of %" FORMAT_OFF_T " bytes)",
*ftp->bytecountp, data->set.infilesize);
result = CURLE_PARTIAL_FILE;
}
}
else {
if((-1 != k->size) && (k->size != *ftp->bytecountp) &&
#ifdef CURL_DO_LINEEND_CONV
((k->size + data->state.crlf_conversions) != *ftp->bytecountp) &&
#endif
(k->maxdownload != *ftp->bytecountp)) {
failf(data, "Received only partial file: %" FORMAT_OFF_T " bytes",
*ftp->bytecountp);
result = CURLE_PARTIAL_FILE;
}
else if(!ftpc->dont_check &&
!*ftp->bytecountp &&
(k->size>0)) {
failf(data, "No data was received!");
result = CURLE_FTP_COULDNT_RETR_FILE;
}
}
ftp->no_transfer = FALSE;
ftpc->dont_check = FALSE;
if(!status && !result && !premature && data->set.postquote)
result = ftp_sendquote(conn, data->set.postquote);
return result;
}
static
CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote)
{
struct curl_slist *item;
ssize_t nread;
int ftpcode;
CURLcode result;
item = quote;
while (item) {
if (item->data) {
FTPSENDF(conn, "%s", item->data);
result = Curl_GetFTPResponse(&nread, conn, &ftpcode);
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;
}
return CURLE_OK;
}
static int ftp_need_type(struct connectdata *conn,
bool ascii_wanted)
{
return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I');
}
static CURLcode ftp_nb_type(struct connectdata *conn,
bool ascii, ftpstate newstate)
{
struct ftp_conn *ftpc = &conn->proto.ftpc;
CURLcode result;
int want = ascii?'A':'I';
if (ftpc->transfertype == want) {
state(conn, newstate);
return ftp_state_type_resp(conn, 200, newstate);
}
NBFTPSENDF(conn, "TYPE %c", want);
state(conn, newstate);
ftpc->transfertype = (char)want;
return CURLE_OK;
}
#ifndef CURL_DISABLE_VERBOSE_STRINGS
static void
ftp_pasv_verbose(struct connectdata *conn,
Curl_addrinfo *ai,
char *newhost,
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);
}
#endif
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_conn *ftpc = &conn->proto.ftpc;
if(data->reqdata.use_range && data->reqdata.range) {
from=curlx_strtoofft(data->reqdata.range, &ptr, 0);
while(ptr && *ptr && (ISSPACE(*ptr) || (*ptr=='-')))
ptr++;
to=curlx_strtoofft(ptr, &ptr2, 0);
if(ptr == ptr2) {
to=-1;
}
if((-1 == to) && (from>=0)) {
data->reqdata.resume_from = from;
DEBUGF(infof(conn->data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n",
from));
}
else if(from < 0) {
totalsize = -from;
data->reqdata.maxdownload = -from;
data->reqdata.resume_from = from;
DEBUGF(infof(conn->data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n",
totalsize));
}
else {
totalsize = to-from;
data->reqdata.maxdownload = totalsize+1;
data->reqdata.resume_from = from;
DEBUGF(infof(conn->data, "FTP RANGE from %" FORMAT_OFF_T
" getting %" FORMAT_OFF_T " bytes\n",
from, data->reqdata.maxdownload));
}
DEBUGF(infof(conn->data, "range-download from %" FORMAT_OFF_T
" to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
from, to, data->reqdata.maxdownload));
ftpc->dont_check = TRUE;
}
else
data->reqdata.maxdownload = -1;
return CURLE_OK;
}
CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
{
struct SessionHandle *data=conn->data;
CURLcode result = CURLE_OK;
struct FTP *ftp = data->reqdata.proto.ftp;
DEBUGF(infof(data, "DO-MORE phase starts\n"));
if(!ftp->no_transfer) {
if(data->set.upload) {
result = ftp_nb_type(conn, data->set.prefer_ascii,
FTP_STOR_TYPE);
if (result)
return result;
}
else {
ftp->downloadsize = -1;
result = ftp_range(conn);
if(result)
;
else if((data->set.ftp_list_only) || !ftp->file) {
result = ftp_nb_type(conn, 1, FTP_LIST_TYPE);
if (result)
return result;
}
else {
result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_RETR_TYPE);
if (result)
return result;
}
}
result = ftp_easy_statemach(conn);
}
if(ftp->no_transfer)
result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
DEBUGF(infof(data, "DO-MORE phase ends with %d\n", result));
return result;
}
static
CURLcode ftp_perform(struct connectdata *conn,
bool *connected,
bool *dophase_done)
{
CURLcode result=CURLE_OK;
DEBUGF(infof(conn->data, "DO phase starts\n"));
if(conn->bits.no_body) {
struct FTP *ftp = conn->data->reqdata.proto.ftp;
ftp->no_transfer = TRUE;
}
*dophase_done = FALSE;
result = ftp_state_quote(conn, TRUE, FTP_QUOTE);
if(result)
return result;
if(conn->data->state.used_interface == Curl_if_multi)
result = Curl_ftp_multi_statemach(conn, dophase_done);
else {
result = ftp_easy_statemach(conn);
*dophase_done = TRUE;
}
*connected = conn->bits.tcpconnect;
if(*dophase_done)
DEBUGF(infof(conn->data, "DO phase is complete\n"));
return result;
}
CURLcode Curl_ftp(struct connectdata *conn, bool *done)
{
CURLcode retcode = CURLE_OK;
*done = FALSE;
retcode = ftp_init(conn);
if(retcode)
return retcode;
retcode = ftp_parse_url_path(conn);
if (retcode)
return retcode;
retcode = ftp_regular_transfer(conn, done);
return retcode;
}
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 SessionHandle *data = conn->data;
struct ftp_conn *ftpc = &conn->proto.ftpc;
va_list ap;
va_start(ap, fmt);
vsnprintf(s, 250, fmt, ap);
va_end(ap);
strcat(s, "\r\n");
bytes_written=0;
write_len = strlen(s);
ftp_respinit(conn);
#ifdef CURL_DOES_CONVERSIONS
res = Curl_convert_to_network(data, s, write_len);
if(res != CURLE_OK) {
return res;
}
#endif
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, (size_t)bytes_written, conn);
if(bytes_written != (ssize_t)write_len) {
write_len -= bytes_written;
sptr += bytes_written;
ftpc->sendthis = malloc(write_len);
if(ftpc->sendthis) {
memcpy(ftpc->sendthis, sptr, write_len);
ftpc->sendsize = ftpc->sendleft = write_len;
}
else {
failf(data, "out of memory");
res = CURLE_OUT_OF_MEMORY;
}
}
else
ftpc->response = Curl_tvnow();
return res;
}
CURLcode Curl_ftpsendf(struct connectdata *conn,
const char *fmt, ...)
{
ssize_t bytes_written;
char s[256];
size_t write_len;
char *sptr=s;
CURLcode res = CURLE_OK;
va_list ap;
va_start(ap, fmt);
vsnprintf(s, 250, fmt, ap);
va_end(ap);
strcat(s, "\r\n");
bytes_written=0;
write_len = strlen(s);
#ifdef CURL_DOES_CONVERSIONS
res = Curl_convert_to_network(conn->data, s, write_len);
if(res != CURLE_OK) {
return(res);
}
#endif
while(1) {
res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len,
&bytes_written);
if(CURLE_OK != res)
break;
if(conn->data->set.verbose)
Curl_debug(conn->data, CURLINFO_HEADER_OUT,
sptr, (size_t)bytes_written, conn);
if(bytes_written != (ssize_t)write_len) {
write_len -= bytes_written;
sptr += bytes_written;
}
else
break;
}
return res;
}
static CURLcode ftp_quit(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
if(conn->proto.ftpc.ctl_valid) {
NBFTPSENDF(conn, "QUIT", NULL);
state(conn, FTP_QUIT);
result = ftp_easy_statemach(conn);
}
return result;
}
CURLcode Curl_ftp_disconnect(struct connectdata *conn)
{
struct ftp_conn *ftpc= &conn->proto.ftpc;
if(conn->data->reqdata.proto.ftp) {
(void)ftp_quit(conn);
if(ftpc->entrypath) {
struct SessionHandle *data = conn->data;
data->state.most_recent_ftp_entrypath = NULL;
free(ftpc->entrypath);
ftpc->entrypath = NULL;
}
if(ftpc->cache) {
free(ftpc->cache);
ftpc->cache = NULL;
}
freedirs(conn);
if(ftpc->prevpath) {
free(ftpc->prevpath);
ftpc->prevpath = NULL;
}
}
return CURLE_OK;
}
static
CURLcode ftp_parse_url_path(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
struct ftp_conn *ftpc = &conn->proto.ftpc;
size_t dlen;
char *slash_pos;
char *path_to_use = data->reqdata.path;
char *cur_pos;
cur_pos = path_to_use;
ftpc->ctl_valid = FALSE;
ftpc->cwdfail = FALSE;
switch(data->set.ftp_filemethod) {
case FTPFILE_NOCWD:
ftp->file = data->reqdata.path;
break;
case FTPFILE_SINGLECWD:
if(!path_to_use[0]) {
ftpc->dirdepth = 0;
ftp->file = NULL;
break;
}
slash_pos=strrchr(cur_pos, '/');
if(slash_pos || !*cur_pos) {
ftpc->dirs = (char **)calloc(1, sizeof(ftpc->dirs[0]));
if(!ftpc->dirs)
return CURLE_OUT_OF_MEMORY;
ftpc->dirs[0] = curl_easy_unescape(conn->data, slash_pos ? cur_pos : "/",
slash_pos?(int)(slash_pos-cur_pos):1,
NULL);
if(!ftpc->dirs[0]) {
freedirs(conn);
return CURLE_OUT_OF_MEMORY;
}
ftpc->dirdepth = 1;
ftp->file = slash_pos ? slash_pos+1 : cur_pos;
}
else
ftp->file = cur_pos;
break;
default:
case FTPFILE_MULTICWD:
ftpc->dirdepth = 0;
ftpc->diralloc = 5;
ftpc->dirs = (char **)calloc(ftpc->diralloc, sizeof(ftpc->dirs[0]));
if(!ftpc->dirs)
return CURLE_OUT_OF_MEMORY;
if(strequal(path_to_use, "/")) {
cur_pos++;
ftpc->dirs[0] = strdup("/");
ftpc->dirdepth++;
}
else {
while ((slash_pos = strchr(cur_pos, '/')) != NULL) {
bool absolute_dir = (bool)((cur_pos - data->reqdata.path > 0) &&
(ftpc->dirdepth == 0));
if (slash_pos-cur_pos) {
int len = (int)(slash_pos - cur_pos + absolute_dir);
ftpc->dirs[ftpc->dirdepth] =
curl_easy_unescape(conn->data, cur_pos - absolute_dir, len, NULL);
if (!ftpc->dirs[ftpc->dirdepth]) {
failf(data, "no memory");
freedirs(conn);
return CURLE_OUT_OF_MEMORY;
}
if (isBadFtpString(ftpc->dirs[ftpc->dirdepth])) {
free(ftpc->dirs[ftpc->dirdepth]);
freedirs(conn);
return CURLE_URL_MALFORMAT;
}
}
else {
cur_pos = slash_pos + 1;
continue;
}
cur_pos = slash_pos + 1;
if(++ftpc->dirdepth >= ftpc->diralloc) {
char *bigger;
ftpc->diralloc *= 2;
bigger = realloc(ftpc->dirs, ftpc->diralloc * sizeof(ftpc->dirs[0]));
if(!bigger) {
freedirs(conn);
return CURLE_OUT_OF_MEMORY;
}
ftpc->dirs = (char **)bigger;
}
}
}
ftp->file = cur_pos;
}
if(ftp->file && *ftp->file) {
ftp->file = curl_easy_unescape(conn->data, ftp->file, 0, NULL);
if(NULL == ftp->file) {
freedirs(conn);
failf(data, "no memory");
return CURLE_OUT_OF_MEMORY;
}
if (isBadFtpString(ftp->file)) {
freedirs(conn);
return CURLE_URL_MALFORMAT;
}
}
else
ftp->file=NULL;
if(data->set.upload && !ftp->file && !ftp->no_transfer) {
failf(data, "Uploading to a URL without a file name!");
return CURLE_URL_MALFORMAT;
}
ftpc->cwddone = FALSE;
if(ftpc->prevpath) {
char *path = curl_easy_unescape(conn->data, data->reqdata.path, 0, NULL);
if(!path) {
freedirs(conn);
return CURLE_OUT_OF_MEMORY;
}
dlen = strlen(path) - (ftp->file?strlen(ftp->file):0);
if((dlen == strlen(ftpc->prevpath)) &&
curl_strnequal(path, ftpc->prevpath, dlen)) {
infof(data, "Request has same path as previous transfer\n");
ftpc->cwddone = TRUE;
}
free(path);
}
return CURLE_OK;
}
static CURLcode ftp_dophase_done(struct connectdata *conn,
bool connected)
{
CURLcode result = CURLE_OK;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
struct ftp_conn *ftpc = &conn->proto.ftpc;
if(connected)
result = Curl_ftp_nextconnect(conn);
if(result && (conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD)) {
sclose(conn->sock[SECONDARYSOCKET]);
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
return result;
}
if(ftp->no_transfer)
result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
else if(!connected)
conn->bits.do_more = TRUE;
ftpc->ctl_valid = TRUE;
return result;
}
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 );
DEBUGF(infof(conn->data, "DO phase is complete\n"));
}
return result;
}
static
CURLcode ftp_regular_transfer(struct connectdata *conn,
bool *dophase_done)
{
CURLcode result=CURLE_OK;
bool connected=0;
struct SessionHandle *data = conn->data;
struct ftp_conn *ftpc = &conn->proto.ftpc;
data->reqdata.size = -1;
Curl_pgrsSetUploadCounter(data, 0);
Curl_pgrsSetDownloadCounter(data, 0);
Curl_pgrsSetUploadSize(data, 0);
Curl_pgrsSetDownloadSize(data, 0);
ftpc->ctl_valid = TRUE;
result = ftp_perform(conn,
&connected,
dophase_done);
if(CURLE_OK == result) {
if(!*dophase_done)
return CURLE_OK;
result = ftp_dophase_done(conn, connected);
if(result)
return result;
}
else
freedirs(conn);
return result;
}
#endif