Newer
Older
}
Daniel Stenberg
committed
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;
/* get the size from the ascii string: */
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);
Daniel Stenberg
committed
result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
if(result)
Daniel Stenberg
committed
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;
Daniel Stenberg
committed
result = ftp_state_ul_setup(conn, TRUE);
return result;
static CURLcode ftp_state_rest_resp(struct connectdata *conn,
int ftpcode,
ftpstate instate)
Daniel Stenberg
committed
{
CURLcode result = CURLE_OK;
struct FTP *ftp = conn->data->reqdata.proto.ftp;
Daniel Stenberg
committed
switch(instate) {
case FTP_REST:
default:
if (ftpcode == 350) {
Daniel Stenberg
committed
result = Curl_client_write(conn, CLIENTWRITE_BOTH,
(char *)"Accept-ranges: bytes\r\n", 0);
if(result)
return result;
}
Daniel Stenberg
committed
result = ftp_state_post_rest(conn);
break;
Daniel Stenberg
committed
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);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
}
return result;
Daniel Stenberg
committed
}
static CURLcode ftp_state_stor_resp(struct connectdata *conn,
int ftpcode)
Daniel Stenberg
committed
{
CURLcode result = CURLE_OK;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
Daniel Stenberg
committed
if(ftpcode>=400) {
failf(data, "Failed FTP upload: %0d", ftpcode);
/* oops, we never close the sockets! */
return CURLE_FTP_COULDNT_STOR_FILE;
}
Daniel Stenberg
committed
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;
}
Daniel Stenberg
committed
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_ssl_connect(conn, SECONDARYSOCKET);
if(result)
return result;
}
*(ftp->bytecountp)=0;
Daniel Stenberg
committed
/* 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_setup_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)
Daniel Stenberg
committed
{
CURLcode result = CURLE_OK;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
struct FTP *ftp = data->reqdata.proto.ftp;
char *buf = data->state.buffer;
Daniel Stenberg
committed
if((ftpcode == 150) || (ftpcode == 125)) {
Daniel Stenberg
committed
/*
A;
150 Opening BINARY mode data connection for /etc/passwd (2241
bytes). (ok, the file is being transfered)
Daniel Stenberg
committed
B:
150 Opening ASCII mode data connection for /bin/ls
Daniel Stenberg
committed
C:
150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
D:
150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
Daniel Stenberg
committed
E:
125 Data connection already open; Transfer starting. */
curl_off_t size=-1; /* default unknown size */
Daniel Stenberg
committed
/*
* 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.
*/
Daniel Stenberg
committed
if((instate != FTP_LIST) &&
Daniel Stenberg
committed
!data->set.prefer_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=(long)(bytes-buf);
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
/* 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;
Daniel Stenberg
committed
if(data->set.ftp_use_port) {
/* BLOCKING */
result = AllowServerConnect(conn);
if( result )
return result;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
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_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);
/* FTP download: */
result=Curl_setup_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 */
}
else {
failf(data, "RETR response: %03d", ftpcode);
return CURLE_FTP_COULDNT_RETR_FILE;
}
return result;
}
/* after USER, PASS and ACCT */
static CURLcode ftp_state_loggedin(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
#ifdef HAVE_KRB4
/* We are logged in, asked to use Kerberos. Set the requested
* protection level
*/
if(conn->sec_complete)
/* BLOCKING */
Curl_sec_set_protection_level(conn);
Daniel Stenberg
committed
/* 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;
}
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
}
#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 = data->reqdata.proto.ftp;
struct ftp_conn *ftpc = &conn->proto.ftpc;
(void)instate; /* no use for this yet */
if((ftpcode == 331) && (ftpc->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 but none available");
result = CURLE_LOGIN_DENIED;
Daniel Stenberg
committed
}
else {
/* All other response codes, like:
Daniel Stenberg
committed
530 User ... access denied
(the server denies to log the specified user) */
Daniel Stenberg
committed
if (conn->data->set.ftp_alternative_to_user &&
!conn->data->state.ftp_trying_alternative) {
/* Ok, USER failed. Let's try the supplied command. */
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;
}
Daniel Stenberg
committed
}
return result;
}
/* 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 */
Daniel Stenberg
committed
}
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"
};
if(ftpc->sendleft) {
/* we have a piece of a command still left to send */
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) {
/* only a fraction was sent */
ftpc->sendleft -= written;
Daniel Stenberg
committed
}
else {
free(ftpc->sendthis);
ftpc->sendthis=NULL;
ftpc->sendleft = ftpc->sendsize = 0;
ftpc->response = Curl_tvnow();
}
return CURLE_OK;
}
/* we read a piece of response */
result = ftp_readresp(sock, conn, &ftpcode, &nread);
if(result)
return result;
Sterling Hughes
committed
if(ftpcode) {
/* we have now received a full FTP server response */
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;
Daniel Stenberg
committed
}
Sterling Hughes
committed
/* 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. */
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);
Sterling Hughes
committed
if(Curl_sec_login(conn) != 0)
infof(data, "Logging in with password in cleartext!\n");
Daniel Stenberg
committed
else
infof(data, "Authentication successful\n");
}
#endif
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 */
ftpc->count3=0;
switch(data->set.ftpsslauth) {
case CURLFTPAUTH_DEFAULT:
case CURLFTPAUTH_SSL:
ftpc->count2 = 1; /* add one to get next */
ftpc->count1 = 0;
break;
case CURLFTPAUTH_TLS:
ftpc->count2 = -1; /* subtract one to get next */
ftpc->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 */
Sterling Hughes
committed
}
NBFTPSENDF(conn, "AUTH %s", ftpauth[ftpc->count1]);
state(conn, FTP_AUTH);
Daniel Stenberg
committed
}
else {
result = ftp_state_user(conn);
if(result)
return result;
Daniel Stenberg
committed
}
Sterling Hughes
committed
break;
case FTP_AUTH:
/* we have gotten the response to a previous AUTH command */
/* 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 == 234) || (ftpcode == 334)) {
Daniel Stenberg
committed
/* Curl_ssl_connect is BLOCKING */
result = Curl_ssl_connect(conn, FIRSTSOCKET);
Daniel Stenberg
committed
if(CURLE_OK == result) {
conn->protocol |= PROT_FTPS;
conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */
result = ftp_state_user(conn);
}
else if(ftpc->count3 < 1) {
ftpc->count3++;
ftpc->count1 += ftpc->count2; /* get next attempt */
result = Curl_nbftpsendf(conn, "AUTH %s", ftpauth[ftpc->count1]);
/* remain in this same state */
}
else {
if(data->set.ftp_ssl > CURLFTPSSL_TRY)
/* we failed and CURLFTPSSL_CONTROL or CURLFTPSSL_ALL is set */
result = CURLE_FTP_SSL_FAILED;
else
/* ignore the failure and continue */
result = ftp_state_user(conn);
}
Daniel Stenberg
committed
if(result)
return result;
Daniel Stenberg
committed
break;
case FTP_USER:
case FTP_PASS:
result = ftp_state_user_resp(conn, ftpcode, ftpc->state);
break;
Daniel Stenberg
committed
case FTP_ACCT:
result = ftp_state_acct_resp(conn, ftpcode);
break;
Daniel Stenberg
committed
case FTP_PBSZ:
/* FIX: check response code */
Daniel Stenberg
committed
/* For TLS, the data connection can have one of two security levels.
Daniel Stenberg
committed
1) Clear (requested by 'PROT C')
2)Private (requested by 'PROT P')
*/
if(!conn->ssl[SECONDARYSOCKET].use) {
Daniel Stenberg
committed
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;
}
Daniel Stenberg
committed
break;
Daniel Stenberg
committed
case FTP_PROT:
if(ftpcode/100 == 2)
/* We have enabled SSL for the data connection! */
Daniel Stenberg
committed
conn->ssl[SECONDARYSOCKET].use =
/* 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;
result = ftp_state_pwd(conn);
if(result)
return result;
break;
Daniel Stenberg
committed
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 */
Daniel Stenberg
committed
if(!dir)
return CURLE_OUT_OF_MEMORY;
/* Reply format is like
257<space>"<directory-name>"<space><commentary> and the RFC959
says
Daniel Stenberg
committed
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
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++;
}
ftpc->entrypath =dir; /* remember this */
infof(data, "Entry path is '%s'\n", ftpc->entrypath);
/* also save it where getinfo can access it: */
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
else {
/* couldn't get the path */
free(dir);
infof(data, "Failed to figure out path\n");
state(conn, FTP_STOP); /* we are done with the CONNECT phase! */
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;
Daniel Stenberg
committed
break;
Daniel Stenberg
committed
case FTP_CWD:
if(ftpcode/100 != 2) {
/* failure to CWD there */
if(conn->data->set.ftp_create_missing_dirs &&
ftpc->count1 && !ftpc->count2) {
/* try making it */
ftpc->count2++; /* counter to prevent CWD-MKD loops */
NBFTPSENDF(conn, "MKD %s", ftpc->dirs[ftpc->count1 - 1]);
state(conn, FTP_MKD);
}
/* return failure */
failf(data, "Server denied you to change to the given directory");
ftpc->cwdfail = TRUE; /* don't remember this path as we failed
to enter it */
return CURLE_FTP_ACCESS_DENIED;
}
else {
/* success */
ftpc->count2=0;
if(++ftpc->count1 <= ftpc->dirdepth) {
/* send next CWD */
NBFTPSENDF(conn, "CWD %s", ftpc->dirs[ftpc->count1 - 1]);
}
else {
result = ftp_state_post_cwd(conn);
if(result)
return result;
}
}
break;
Daniel Stenberg
committed
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", ftpc->dirs[ftpc->count1 - 1]);
break;
case FTP_MDTM:
result = ftp_state_mdtm_resp(conn, ftpcode);
break;
Daniel Stenberg
committed
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;
Daniel Stenberg
committed
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;
Daniel Stenberg
committed
case FTP_PORT:
result = ftp_state_port_resp(conn, ftpcode);
break;
Daniel Stenberg
committed
case FTP_LIST:
case FTP_RETR:
result = ftp_state_get_resp(conn, ftpcode, ftpc->state);
break;
Daniel Stenberg
committed
case FTP_STOR:
result = ftp_state_stor_resp(conn, ftpcode);
break;
case FTP_QUIT:
/* fallthrough, just stop! */
default:
/* internal error */
state(conn, FTP_STOP);
break;
}
} /* if(ftpcode) */
return result;
}
/* 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_conn *ftpc = &conn->proto.ftpc;
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(), ftpc->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 = ftpc->response_time*1000 -
Curl_tvdiff(Curl_tvnow(), ftpc->response); /* spent time */
return timeout_ms;
}
Daniel Stenberg
committed
/* 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_conn *ftpc = &conn->proto.ftpc;
CURLcode result = CURLE_OK;
long timeout_ms = ftp_state_timeout(conn);
*done = FALSE; /* default to not done yet */
if(timeout_ms <= 0) {
failf(data, "FTP response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
rc = Curl_select(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
0);
if(rc == -1) {
failf(data, "select error");
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
}
else if(rc != 0) {
result = ftp_statemach_act(conn);
Daniel Stenberg
committed
}
/* if rc == 0, then select() timed out */
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;
Sterling Hughes
committed
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; /* already too little time */
}
rc = Curl_select(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
Daniel Stenberg
committed
if(rc == -1) {
failf(data, "select error");
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
}
else if(rc == 0) {
result = CURLE_OPERATION_TIMEDOUT;
break;
}
else {
result = ftp_statemach_act(conn);
if(result)
break;
Daniel Stenberg
committed
}
return result;
}
Daniel Stenberg
committed
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
/*
* Allocate and initialize the struct FTP for the current SessionHandle. If
* need be.
*/
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;
/* get some initial data into the ftp struct */
ftp->bytecountp = &data->reqdata.keep.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;
return CURLE_OK;
}
/*
* 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 */
{
CURLcode result;
#ifndef CURL_DISABLE_HTTP
/* for FTP over HTTP proxy */
struct HTTP http_proxy;
struct FTP *ftp_save;
#endif /* CURL_DISABLE_HTTP */
struct ftp_conn *ftpc = &conn->proto.ftpc;
struct SessionHandle *data=conn->data;
*done = FALSE; /* default to not done yet */
Daniel Stenberg
committed
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;
/* We always support persistant connections on ftp */
conn->bits.close = FALSE;
ftpc->response_time = 3600; /* set default response time-out */
#ifndef CURL_DISABLE_HTTP
Daniel Stenberg
committed
if (conn->bits.tunnel_proxy && conn->bits.httpproxy) {
/* BLOCKING */
/* We want "seamless" FTP operations through HTTP proxy tunnel */
/* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
* conn->proto.http; we want FTP through HTTP and we have to change the
* member temporarily for connecting to the HTTP proxy. After
* Curl_proxyCONNECT we have to set back the member to the original struct
* FTP pointer
*/
ftp_save = data->reqdata.proto.ftp;
memset(&http_proxy, 0, sizeof(http_proxy));
data->reqdata.proto.http = &http_proxy;
Daniel Stenberg
committed
result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
conn->host.name, conn->remote_port);
data->reqdata.proto.ftp = ftp_save;
Daniel Stenberg
committed
}
#endif /* CURL_DISABLE_HTTP */
Daniel Stenberg
committed
if(conn->protocol & PROT_FTPS) {
/* BLOCKING */
/* FTPS is simply ftp with SSL for the control channel */
/* now, perform the SSL initialization for this socket */
Daniel Stenberg
committed
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(result)
return result;
}
/* When we connect, we start in the state where we await the 220
response */
Daniel Stenberg
committed
ftp_respinit(conn); /* init the response reader stuff */
state(conn, FTP_WAIT220);
ftpc->response = Curl_tvnow(); /* start response time-out now! */
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;
Daniel Stenberg
committed
}
/***********************************************************************
*
* 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)
Daniel Stenberg
committed
{
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;
size_t flen;
size_t dlen;
char *path;
char *path_to_use = data->reqdata.path;
struct Curl_transfer_keeper *k = &data->reqdata.keep;
Daniel Stenberg
committed
if(!ftp)
/* When the easy handle is removed from the multi while libcurl is still
* trying to resolve the host name, it seems that the ftp struct is not
* yet initialized, but the removal action calls Curl_done() which calls
* this function. So we simply return success if no ftp pointer is set.
*/
return CURLE_OK;
/* now store a copy of the directory we are in */
if(ftpc->prevpath)
free(ftpc->prevpath);
/* get the "raw" path */
path = curl_easy_unescape(data, path_to_use, 0, NULL);
if(!path)
return CURLE_OUT_OF_MEMORY;
flen = ftp->file?strlen(ftp->file):0; /* file is "raw" already */
dlen = strlen(path)-flen;
if(dlen && !ftpc->cwdfail) {
ftpc->prevpath = path;
if(flen)
/* if 'path' is not the whole string */
ftpc->prevpath[dlen]=0; /* terminate */
infof(data, "Remembering we are in dir %s\n", ftpc->prevpath);
}
else {
ftpc->prevpath = NULL; /* no path */
free(path);
}
/* free the dir tree and file parts */
freedirs(conn);
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:
/* the connection stays alive fine even though this happened */
/* fall-through */
case CURLE_OK: /* doesn't affect the control connection's status */
ftpc->ctl_valid = was_ctl_valid;
break;
default: /* by default, an error means the control connection is
wedged and should not be used anymore */
ftpc->ctl_valid = FALSE;