Newer
Older
ftp->downloadsize = filesize-data->reqdata.resume_from;
}
}
if(ftp->downloadsize == 0) {
/* no data to transfer */
result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
infof(data, "File already completely downloaded\n");
/* 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;
}
/* Set resume file transfer offset */
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 {
/* no resume */
NBFTPSENDF(conn, "RETR %s", ftp->file);
state(conn, FTP_RETR);
}
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);
/* 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 */
Daniel Stenberg
committed
if(!ISDIGIT(*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;
}
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
}
#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;
Daniel Stenberg
committed
if(data->set.ftp_use_ccc) {
/* CCC - Clear Command Channel
*/
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) {
/* First shut down the SSL layer (note: this call will block) */
result = Curl_ssl_shutdown(conn, FIRSTSOCKET);
if(result) {
failf(conn->data, "Failed to clear the command channel (CCC)");
return result;
}
Daniel Stenberg
committed
/* Then continue as normal */
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
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
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. */
Daniel Stenberg
committed
timeout_ms = data->set.ftp_response_timeout - /* 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 */
Daniel Stenberg
committed
timeout_ms = data->set.timeout - /* 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 */
Daniel Stenberg
committed
timeout_ms = ftpc->response_time -
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
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
/*
* 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;
Daniel Stenberg
committed
ftpc->response_time = 3600000; /* 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.
Daniel Stenberg
committed
CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status, bool premature)
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;