Newer
Older
close the connection when we start the connection so it won't cause
us any harm, just not make us exit as nicely. */
}
else {
/* We got a file size report, so we check that there actually is a
part of the file left to get, or else we go home. */
if(data->reqdata.resume_from< 0) {
/* We're supposed to download the last abs(from) bytes */
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;
}
/* convert to size to download */
ftp->downloadsize = -data->reqdata.resume_from;
/* download from where? */
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;
}
/* Now store the number of bytes we are expected to download */
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 ->transfer so that we won't get any error in Curl_ftp_done()
* because we didn't transfer the any file */
ftp->transfer = FTPTRANSFER_NONE;
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) {
#ifdef CURL_FTP_HTTPSTYLE_HEAD
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;
Curl_pgrsSetDownloadSize(data, filesize);
result = ftp_state_post_size(conn);
}
else if(instate == FTP_RETR_SIZE) {
Curl_pgrsSetDownloadSize(data, filesize);
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:
#ifdef CURL_FTP_HTTPSTYLE_HEAD
if (ftpcode == 350) {
char buffer[24]= { "Accept-ranges: bytes\r\n" };
result = Curl_client_write(conn, CLIENTWRITE_BOTH, buffer, 0);
if(result)
return result;
}
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! */
Daniel Stenberg
committed
return CURLE_UPLOAD_FAILED;
}
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->transfer = FTPTRANSFER_NONE; /* 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
if(conn->data->set.krb) {
/* 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;
}
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
}
#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 */
/* some need password anyway, and others just return 2xx ignored */
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) {
Daniel Stenberg
committed
if(data->set.str[STRING_FTP_ACCOUNT]) {
NBFTPSENDF(conn, "ACCT %s", data->set.str[STRING_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
Daniel Stenberg
committed
if (conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] &&
Daniel Stenberg
committed
!conn->data->state.ftp_trying_alternative) {
/* Ok, USER failed. Let's try the supplied command. */
Daniel Stenberg
committed
NBFTPSENDF(conn, "%s",
conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]);
Daniel Stenberg
committed
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. */
#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
if(data->set.krb) {
/* 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.str[STRING_KRB_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 > CURLUSESSL_TRY)
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
result = CURLE_USE_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 == CURLUSESSL_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 =
(bool)(data->set.ftp_ssl != CURLUSESSL_CONTROL);
/* FTP servers typically responds with 500 if they decide to reject
our 'P' request */
else if(data->set.ftp_ssl > CURLUSESSL_CONTROL)
/* we failed and bails out */
return CURLE_USE_SSL_FAILED;
Linus Nielsen
committed
if(data->set.ftp_ccc) {
Daniel Stenberg
committed
/* 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:
/* 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
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
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_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_REMOTE_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_REMOTE_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_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
0);
if(rc == -1) {
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_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
Daniel Stenberg
committed
if(rc == -1) {
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
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
/*
* 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);