#include "setup.h"
#ifndef CURL_DISABLE_FTP
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
#include <winsock.h>
#else
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <sys/utsname.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#endif
#endif
#if defined(WIN32) && defined(__GNUC__) || defined(__MINGW32__)
#include <errno.h>
#endif
#include <curl/curl.h>
#include "urldata.h"
#include "sendf.h"
#include "if2ip.h"
#include "hostip.h"
#include "progress.h"
#include "transfer.h"
#include "escape.h"
#include "http.h"
#include "ftp.h"
#ifdef KRB4
#include "security.h"
#include "krb4.h"
#endif
#include "strequal.h"
#include "ssluse.h"
#include "connect.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 MALLOCDEBUG
#include "memdebug.h"
#endif
static CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote);
static CURLcode ftp_cwd(struct connectdata *conn, char *path);
#define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z))) return result
static CURLcode AllowServerConnect(struct SessionHandle *data,
struct connectdata *conn,
int sock)
{
fd_set rdset;
struct timeval dt;
FD_ZERO(&rdset);
FD_SET(sock, &rdset);
dt.tv_sec = 10;
dt.tv_usec = 0;
switch (select(sock+1, &rdset, NULL, NULL, &dt)) {
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:
{
int s;
size_t size = sizeof(struct sockaddr_in);
struct sockaddr_in add;
getsockname(sock, (struct sockaddr *) &add, (socklen_t *)&size);
s=accept(sock, (struct sockaddr *) &add, (socklen_t *)&size);
sclose(sock);
if (-1 == s) {
failf(data, "Error accept()ing server connect");
return CURLE_FTP_PORT_FAILED;
}
infof(data, "Connection accepted from server\n");
conn->secondarysocket = s;
}
break;
}
return CURLE_OK;
}
int Curl_GetFTPResponse(char *buf,
struct connectdata *conn,
int *ftpcode)
{
int sockfd = conn->firstsocket;
int nread;
int perline;
bool keepon=TRUE;
ssize_t gotbytes;
char *ptr;
int timeout = 3600;
struct timeval interval;
fd_set rkeepfd;
fd_set readfd;
struct SessionHandle *data = conn->data;
char *line_start;
int code=0;
#define SELECT_OK 0
#define SELECT_ERROR 1
#define SELECT_TIMEOUT 2
#define SELECT_MEMORY 3
#define SELECT_CALLBACK 4
int error = SELECT_OK;
struct FTP *ftp = conn->proto.ftp;
if (ftpcode)
*ftpcode = 0;
FD_ZERO (&readfd);
FD_SET (sockfd, &readfd);
rkeepfd = readfd;
ptr=buf;
line_start = buf;
nread=0;
perline=0;
keepon=TRUE;
while((nread<BUFSIZE) && (keepon && !error)) {
if(data->set.timeout) {
timeout = data->set.timeout -
Curl_tvdiff(Curl_tvnow(), conn->now)/1000;
if(timeout <=0 ) {
failf(data, "Transfer aborted due to timeout");
return -SELECT_TIMEOUT;
}
}
if(!ftp->cache) {
readfd = rkeepfd;
interval.tv_sec = timeout;
interval.tv_usec = 0;
switch (select (sockfd+1, &readfd, NULL, NULL, &interval)) {
case -1:
error = SELECT_ERROR;
failf(data, "Transfer aborted due to select() error");
break;
case 0:
error = SELECT_TIMEOUT;
failf(data, "Transfer aborted due to timeout");
break;
default:
error = SELECT_OK;
break;
}
}
if(SELECT_OK == error) {
if(ftp->cache) {
memcpy(ptr, ftp->cache, ftp->cache_size);
gotbytes = ftp->cache_size;
free(ftp->cache);
ftp->cache = NULL;
ftp->cache_size = 0;
}
else {
int res = Curl_read(conn, sockfd, ptr,
BUFSIZE-nread, &gotbytes);
if(res < 0)
continue;
if(CURLE_OK != res)
keepon = FALSE;
}
if(!keepon)
;
else if(gotbytes <= 0) {
keepon = FALSE;
error = SELECT_ERROR;
failf(data, "Connection aborted");
}
else {
int i;
nread += gotbytes;
for(i = 0; i < gotbytes; ptr++, i++) {
perline++;
if(*ptr=='\n') {
CURLcode result;
if(data->set.verbose)
Curl_debug(data, CURLINFO_HEADER_IN, line_start, perline);
result = Curl_client_write(data, CLIENTWRITE_HEADER,
line_start, perline);
if(result)
return -SELECT_CALLBACK;
#define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
isdigit((int)line[2]) && (' ' == line[3]))
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)) {
ftp->cache_size = gotbytes - i;
ftp->cache = (char *)malloc(ftp->cache_size);
if(ftp->cache)
memcpy(ftp->cache, line_start, ftp->cache_size);
else
return -SELECT_MEMORY;
}
}
}
}
if(!error)
code = atoi(buf);
#ifdef 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(error)
return -error;
if(ftpcode)
*ftpcode=code;
return nread;
}
CURLcode Curl_ftp_connect(struct connectdata *conn)
{
int nread;
struct SessionHandle *data=conn->data;
char *buf = data->state.buffer;
struct FTP *ftp;
CURLcode result;
int ftpcode;
ftp = (struct FTP *)malloc(sizeof(struct FTP));
if(!ftp)
return CURLE_OUT_OF_MEMORY;
memset(ftp, 0, sizeof(struct FTP));
conn->proto.ftp = ftp;
conn->bits.close = FALSE;
ftp->bytecountp = &conn->bytecount;
ftp->user = data->state.user;
ftp->passwd = data->state.passwd;
if (data->set.tunnel_thru_httpproxy) {
result = Curl_ConnectHTTPProxyTunnel(conn, conn->firstsocket,
conn->hostname, conn->remote_port);
if(CURLE_OK != result)
return result;
}
if(conn->protocol & PROT_FTPS) {
result = Curl_SSLConnect(conn);
if(result)
return result;
}
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode != 220) {
failf(data, "This doesn't seem like a nice ftp-server response");
return CURLE_FTP_WEIRD_SERVER_REPLY;
}
#ifdef 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
FTPSENDF(conn, "USER %s", ftp->user);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode == 530) {
failf(data, "Access denied: %s", &buf[4]);
return CURLE_FTP_ACCESS_DENIED;
}
else if(ftpcode == 331) {
FTPSENDF(conn, "PASS %s", ftp->passwd);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode == 530) {
failf(data, "the username and/or the password are incorrect");
return CURLE_FTP_USER_PASSWORD_INCORRECT;
}
else if(ftpcode == 230) {
infof(data, "We have successfully logged in\n");
}
else {
failf(data, "Odd return code after PASS");
return CURLE_FTP_WEIRD_PASS_REPLY;
}
}
else if(buf[0] == '2') {
infof(data, "We have successfully logged in\n");
#ifdef KRB4
if(conn->sec_complete)
Curl_sec_set_protection_level(conn);
if(data->state.passwd && *data->state.passwd)
Curl_krb_kauth(conn);
#endif
}
else {
failf(data, "Odd return code after USER");
return CURLE_FTP_WEIRD_USER_REPLY;
}
FTPSENDF(conn, "PWD", NULL);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode == 257) {
char *dir = (char *)malloc(nread+1);
char *store=dir;
char *ptr=&buf[4];
if('\"' == *ptr) {
ptr++;
while(ptr && *ptr) {
if('\"' == *ptr) {
if('\"' == ptr[1]) {
*store = ptr[1];
ptr++;
}
else {
*store = '\0';
break;
}
}
else
*store = *ptr;
store++;
ptr++;
}
ftp->entrypath =dir;
infof(data, "Entry path is '%s'\n", ftp->entrypath);
}
else {
}
}
else {
}
return CURLE_OK;
}
CURLcode Curl_ftp_done(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct FTP *ftp = conn->proto.ftp;
ssize_t nread;
char *buf = data->state.buffer;
int ftpcode;
CURLcode result=CURLE_OK;
if(data->set.upload) {
if((-1 != data->set.infilesize) &&
(data->set.infilesize != *ftp->bytecountp) &&
!data->set.crlf) {
failf(data, "Uploaded unaligned file size (%d out of %d bytes)",
*ftp->bytecountp, data->set.infilesize);
return CURLE_PARTIAL_FILE;
}
}
else {
if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
(conn->maxdownload != *ftp->bytecountp)) {
failf(data, "Received only partial file: %d bytes", *ftp->bytecountp);
return CURLE_PARTIAL_FILE;
}
else if(!ftp->dont_check &&
!*ftp->bytecountp &&
(conn->size>0)) {
failf(data, "No data was received!");
result = CURLE_FTP_COULDNT_RETR_FILE;
}
}
#ifdef KRB4
Curl_sec_fflush_fd(conn, conn->secondarysocket);
#endif
sclose(conn->secondarysocket);
conn->secondarysocket = -1;
if(!ftp->no_transfer) {
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(!ftp->dont_check) {
if((ftpcode != 226) && (ftpcode != 250)) {
failf(data, "server did not report OK, got %d", ftpcode);
return CURLE_FTP_WRITE_ERROR;
}
}
}
ftp->no_transfer = FALSE;
ftp->dont_check = FALSE;
if(!result && 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);
nread = Curl_GetFTPResponse(conn->data->state.buffer, conn, &ftpcode);
if (nread < 0)
return CURLE_OPERATION_TIMEOUTED;
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
CURLcode ftp_cwd(struct connectdata *conn, char *path)
{
ssize_t nread;
int ftpcode;
CURLcode result;
FTPSENDF(conn, "CWD %s", path);
nread = Curl_GetFTPResponse(conn->data->state.buffer, conn, &ftpcode);
if (nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if (ftpcode != 250) {
failf(conn->data, "Couldn't cd to %s", path);
return CURLE_FTP_ACCESS_DENIED;
}
return CURLE_OK;
}
static
CURLcode ftp_getfiletime(struct connectdata *conn, char *file)
{
CURLcode result=CURLE_OK;
int ftpcode;
ssize_t nread;
char *buf = conn->data->state.buffer;
FTPSENDF(conn, "MDTM %s", file);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode == 213) {
int year, month, day, hour, minute, second;
if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d",
&year, &month, &day, &hour, &minute, &second)) {
time_t secs=time(NULL);
sprintf(buf, "%04d%02d%02d %02d:%02d:%02d",
year, month, day, hour, minute, second);
conn->data->info.filetime = curl_getdate(buf, &secs);
}
else {
infof(conn->data, "unsupported MDTM reply format\n");
}
}
return result;
}
static CURLcode ftp_transfertype(struct connectdata *conn,
bool ascii)
{
struct SessionHandle *data = conn->data;
int ftpcode;
ssize_t nread;
char *buf=data->state.buffer;
CURLcode result;
FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode != 200) {
failf(data, "Couldn't set %s mode",
ascii?"ASCII":"binary");
return ascii? CURLE_FTP_COULDNT_SET_ASCII:CURLE_FTP_COULDNT_SET_BINARY;
}
return CURLE_OK;
}
static
CURLcode ftp_getsize(struct connectdata *conn, char *file,
ssize_t *size)
{
struct SessionHandle *data = conn->data;
int ftpcode;
ssize_t nread;
char *buf=data->state.buffer;
CURLcode result;
FTPSENDF(conn, "SIZE %s", file);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode == 213) {
*size = atoi(buf+4);
}
else
return CURLE_FTP_COULDNT_GET_SIZE;
return CURLE_OK;
}
static void
ftp_pasv_verbose(struct connectdata *conn,
Curl_ipconnect *addr,
char *newhost,
int port)
{
#ifndef ENABLE_IPV6
struct in_addr in;
struct hostent * answer;
#ifdef HAVE_INET_NTOA_R
char ntoa_buf[64];
#endif
long bigbuf[9000 / sizeof(long)];
#if defined(HAVE_INET_ADDR)
in_addr_t address;
# if defined(HAVE_GETHOSTBYADDR_R)
int h_errnop;
# endif
char *hostent_buf = (char *)bigbuf;
address = inet_addr(newhost);
# ifdef HAVE_GETHOSTBYADDR_R
# ifdef HAVE_GETHOSTBYADDR_R_5
memset(hostent_buf, 0, sizeof(struct hostent)+sizeof(struct hostent_data));
if(gethostbyaddr_r((char *) &address,
sizeof(address), AF_INET,
(struct hostent *)hostent_buf,
(struct hostent_data *)(hostent_buf + sizeof(*answer))))
answer=NULL;
else
answer=(struct hostent *)hostent_buf;
# endif
# ifdef HAVE_GETHOSTBYADDR_R_7
answer = gethostbyaddr_r((char *) &address, sizeof(address), AF_INET,
(struct hostent *)bigbuf,
hostent_buf + sizeof(*answer),
sizeof(bigbuf) - sizeof(*answer),
&h_errnop);
# endif
# ifdef HAVE_GETHOSTBYADDR_R_8
if(gethostbyaddr_r((char *) &address, sizeof(address), AF_INET,
(struct hostent *)hostent_buf,
hostent_buf + sizeof(*answer),
sizeof(bigbuf) - sizeof(*answer),
&answer,
&h_errnop))
answer=NULL;
# endif
# else
answer = gethostbyaddr((char *) &address, sizeof(address), AF_INET);
# endif
#else
answer = NULL;
#endif
(void) memcpy(&in.s_addr, addr, sizeof (Curl_ipconnect));
infof(conn->data, "Connecting to %s (%s) port %u\n",
answer?answer->h_name:newhost,
#if defined(HAVE_INET_NTOA_R)
inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf)),
#else
inet_ntoa(in),
#endif
port);
#else
char hbuf[NI_MAXHOST];
char nbuf[NI_MAXHOST];
char sbuf[NI_MAXSERV];
#ifdef NI_WITHSCOPEID
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
#else
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
#endif
port = 0;
if (getnameinfo(addr->ai_addr, addr->ai_addrlen,
nbuf, sizeof(nbuf), sbuf, sizeof(sbuf), niflags)) {
snprintf(nbuf, sizeof(nbuf), "?");
snprintf(sbuf, sizeof(sbuf), "?");
}
if (getnameinfo(addr->ai_addr, addr->ai_addrlen,
hbuf, sizeof(hbuf), NULL, 0, 0)) {
infof(conn->data, "Connecting to %s (%s) port %s\n", nbuf, newhost, sbuf);
}
else {
infof(conn->data, "Connecting to %s (%s) port %s\n", hbuf, nbuf, sbuf);
}
#endif
}
static
CURLcode ftp_use_port(struct connectdata *conn)
{
struct SessionHandle *data=conn->data;
int portsock=-1;
ssize_t nread;
char *buf = data->state.buffer;
int ftpcode;
CURLcode result;
#ifdef ENABLE_IPV6
struct addrinfo hints, *res, *ai;
struct sockaddr_storage ss;
socklen_t sslen;
char hbuf[NI_MAXHOST];
struct sockaddr *sa=(struct sockaddr *)&ss;
#ifdef NI_WITHSCOPEID
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
#else
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
#endif
unsigned char *ap;
unsigned char *pp;
char portmsgbuf[4096], tmp[4096];
const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
char **modep;
sslen = sizeof(ss);
if (getsockname(conn->firstsocket, (struct sockaddr *)&ss, &sslen) < 0)
return CURLE_FTP_PORT_FAILED;
if (getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL, 0,
niflags))
return CURLE_FTP_PORT_FAILED;
memset(&hints, 0, sizeof(hints));
hints.ai_family = sa->sa_family;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(hbuf, (char *)"0", &hints, &res))
return CURLE_FTP_PORT_FAILED;
portsock = -1;
for (ai = res; ai; ai = ai->ai_next) {
portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (portsock < 0)
continue;
if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
sclose(portsock);
portsock = -1;
continue;
}
if (listen(portsock, 1) < 0) {
sclose(portsock);
portsock = -1;
continue;
}
break;
}
freeaddrinfo(res);
if (portsock < 0) {
failf(data, "%s", strerror(errno));
return CURLE_FTP_PORT_FAILED;
}
sslen = sizeof(ss);
if (getsockname(portsock, sa, &sslen) < 0) {
failf(data, "%s", strerror(errno));
return CURLE_FTP_PORT_FAILED;
}
for (modep = (char **)mode; modep && *modep; modep++) {
int lprtaf, eprtaf;
int alen=0, plen=0;
switch (sa->sa_family) {
case AF_INET:
ap = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_addr;
alen = sizeof(((struct sockaddr_in *)&ss)->sin_addr);
pp = (unsigned char *)&((struct sockaddr_in *)&ss)->sin_port;
plen = sizeof(((struct sockaddr_in *)&ss)->sin_port);
lprtaf = 4;
eprtaf = 1;
break;
case AF_INET6:
ap = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
alen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_addr);
pp = (unsigned char *)&((struct sockaddr_in6 *)&ss)->sin6_port;
plen = sizeof(((struct sockaddr_in6 *)&ss)->sin6_port);
lprtaf = 6;
eprtaf = 2;
break;
default:
ap = pp = NULL;
lprtaf = eprtaf = -1;
break;
}
if (strcmp(*modep, "EPRT") == 0) {
if (eprtaf < 0)
continue;
if (getnameinfo((struct sockaddr *)&ss, sslen,
portmsgbuf, sizeof(portmsgbuf), tmp, sizeof(tmp), niflags))
continue;
if (sa->sa_family == AF_INET6) {
char *q = strchr(portmsgbuf, '%');
if (q)
*q = '\0';
}
result = Curl_ftpsendf(conn, "%s |%d|%s|%s|", *modep, eprtaf,
portmsgbuf, tmp);
if(result)
return result;
} else if (strcmp(*modep, "LPRT") == 0 ||
strcmp(*modep, "PORT") == 0) {
int i;
if (strcmp(*modep, "LPRT") == 0 && lprtaf < 0)
continue;
if (strcmp(*modep, "PORT") == 0 && sa->sa_family != AF_INET)
continue;
portmsgbuf[0] = '\0';
if (strcmp(*modep, "LPRT") == 0) {
snprintf(tmp, sizeof(tmp), "%d,%d", lprtaf, alen);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
sizeof(portmsgbuf)) {
continue;
}
}
for (i = 0; i < alen; i++) {
if (portmsgbuf[0])
snprintf(tmp, sizeof(tmp), ",%u", ap[i]);
else
snprintf(tmp, sizeof(tmp), "%u", ap[i]);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
sizeof(portmsgbuf)) {
continue;
}
}
if (strcmp(*modep, "LPRT") == 0) {
snprintf(tmp, sizeof(tmp), ",%d", plen);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >= sizeof(portmsgbuf))
continue;
}
for (i = 0; i < plen; i++) {
snprintf(tmp, sizeof(tmp), ",%u", pp[i]);
if (strlcat(portmsgbuf, tmp, sizeof(portmsgbuf)) >=
sizeof(portmsgbuf)) {
continue;
}
}
result = Curl_ftpsendf(conn, "%s %s", *modep, portmsgbuf);
if(result)
return result;
}
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if (ftpcode != 200) {
failf(data, "Server does not grok %s", *modep);
continue;
}
else
break;
}
if (!*modep) {
sclose(portsock);
return CURLE_FTP_PORT_FAILED;
}
conn->secondarysocket = portsock;
#else
struct sockaddr_in sa;
struct Curl_dns_entry *h=NULL;
unsigned short porttouse;
char myhost[256] = "";
bool sa_filled_in = FALSE;
if(data->set.ftpport) {
if(Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) {
h = Curl_resolv(data, myhost, 0);
}
else {
int len = strlen(data->set.ftpport);
if(len>1)
h = Curl_resolv(data, data->set.ftpport, 0);
if(h)
strcpy(myhost, data->set.ftpport);
}
}
if(! *myhost) {
socklen_t sslen;
sslen = sizeof(sa);
if (getsockname(conn->firstsocket, (struct sockaddr *)&sa, &sslen) < 0) {
failf(data, "getsockname() failed");
return CURLE_FTP_PORT_FAILED;
}
sa_filled_in = TRUE;
}
if(h)
Curl_resolv_unlock(h);
if ( h || sa_filled_in) {
if( (portsock = socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) {
int size;
conn->secondarysocket = portsock;
if(!sa_filled_in) {
memset((char *)&sa, 0, sizeof(sa));
memcpy((char *)&sa.sin_addr,
h->addr->h_addr,
h->addr->h_length);
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = INADDR_ANY;
}
sa.sin_port = 0;
size = sizeof(sa);
if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
struct sockaddr_in add;
socklen_t socksize = sizeof(add);
if(getsockname(portsock, (struct sockaddr *) &add,
&socksize)<0) {
failf(data, "getsockname() failed");
return CURLE_FTP_PORT_FAILED;
}
porttouse = ntohs(add.sin_port);
if ( listen(portsock, 1) < 0 ) {
failf(data, "listen(2) failed on socket");
return CURLE_FTP_PORT_FAILED;
}
}
else {
failf(data, "bind(2) failed on socket");
return CURLE_FTP_PORT_FAILED;
}
}
else {
failf(data, "socket(2) failed (%s)");
return CURLE_FTP_PORT_FAILED;
}
}
else {
failf(data, "could't find my own IP address (%s)", myhost);
return CURLE_FTP_PORT_FAILED;
}
{
#ifdef HAVE_INET_NTOA_R
char ntoa_buf[64];
#endif
struct in_addr in;
unsigned short ip[5];
(void) memcpy(&in.s_addr,
h?*h->addr->h_addr_list:(char *)&sa.sin_addr.s_addr,
sizeof (in.s_addr));
#ifdef HAVE_INET_NTOA_R
inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf));
sscanf( ntoa_buf, "%hu.%hu.%hu.%hu",
&ip[0], &ip[1], &ip[2], &ip[3]);
#else
sscanf( inet_ntoa(in), "%hu.%hu.%hu.%hu",
&ip[0], &ip[1], &ip[2], &ip[3]);
#endif
infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n",
ip[0], ip[1], ip[2], ip[3], porttouse);
result=Curl_ftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d",
ip[0], ip[1], ip[2], ip[3],
porttouse >> 8,
porttouse & 255);
if(result)
return result;
}
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode != 200) {
failf(data, "Server does not grok PORT, try without it!");
return CURLE_FTP_PORT_FAILED;
}
#endif
return CURLE_OK;
}
static
CURLcode ftp_use_pasv(struct connectdata *conn,
bool *connected)
{
struct SessionHandle *data = conn->data;
ssize_t nread;
char *buf = data->state.buffer;
int ftpcode;
CURLcode result;
struct Curl_dns_entry *addr=NULL;
Curl_ipconnect *conninfo;
#if 1
const char *mode[] = { "EPSV", "PASV", NULL };
int results[] = { 229, 227, 0 };
#else
#if 0
char *mode[] = { "EPSV", "LPSV", "PASV", NULL };
int results[] = { 229, 228, 227, 0 };
#else
const char *mode[] = { "PASV", NULL };
int results[] = { 227, 0 };
#endif
#endif
int modeoff;
unsigned short connectport;
unsigned short newport=0;
char newhost[48];
char *newhostp=NULL;
for (modeoff = (data->set.ftp_use_epsv?0:1);
mode[modeoff]; modeoff++) {
result = Curl_ftpsendf(conn, "%s", mode[modeoff]);
if(result)
return result;
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if (ftpcode == results[modeoff])
break;
}
if (!mode[modeoff]) {
failf(data, "Odd return code after PASV");
return CURLE_FTP_WEIRD_PASV_REPLY;
}
else if (227 == results[modeoff]) {
int ip[4];
int port[2];
char *str=buf;
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 this 227-reply: %s", buf);
return CURLE_FTP_WEIRD_227_FORMAT;
}
sprintf(newhost, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
newhostp = newhost;
newport = (port[0]<<8) + port[1];
}
#if 1
else if (229 == results[modeoff]) {
char *ptr = strchr(buf, '(');
if(ptr) {
unsigned int num;
char separator[4];
ptr++;
if(5 == sscanf(ptr, "%c%c%c%u%c",
&separator[0],
&separator[1],
&separator[2],
&num,
&separator[3])) {
newport = num;
newhostp = conn->name;
}
else
ptr=NULL;
}
if(!ptr) {
failf(data, "Weirdly formatted EPSV reply");
return CURLE_FTP_WEIRD_PASV_REPLY;
}
}
#endif
else
return CURLE_FTP_CANT_RECONNECT;
if(data->change.proxy) {
addr = Curl_resolv(data, conn->proxyhost, conn->port);
connectport =
(unsigned short)conn->port;
}
else {
addr = Curl_resolv(data, newhostp, newport);
if(!addr) {
failf(data, "Can't resolve new host %s:%d", newhostp, newport);
return CURLE_FTP_CANT_GET_HOST;
}
connectport = newport;
}
result = Curl_connecthost(conn,
addr,
connectport,
&conn->secondarysocket,
&conninfo,
connected);
Curl_resolv_unlock(addr);
if((CURLE_OK == result) &&
data->set.verbose)
ftp_pasv_verbose(conn, conninfo, newhostp, connectport);
if(CURLE_OK != result)
return result;
if (data->set.tunnel_thru_httpproxy) {
result = Curl_ConnectHTTPProxyTunnel(conn, conn->secondarysocket,
newhostp, newport);
if(CURLE_OK != result)
return result;
}
return CURLE_OK;
}
CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
{
struct SessionHandle *data=conn->data;
char *buf = data->state.buffer;
CURLcode result;
ssize_t nread;
int ftpcode;
struct FTP *ftp = conn->proto.ftp;
long *bytecountp = ftp->bytecountp;
if(data->set.upload) {
result = ftp_transfertype(conn, data->set.ftp_ascii);
if(result)
return result;
if(data->set.prequote) {
if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
return result;
}
if(conn->resume_from) {
if(conn->resume_from < 0 ) {
ssize_t gottensize;
if(CURLE_OK != ftp_getsize(conn, ftp->file, &gottensize)) {
failf(data, "Couldn't get remote file size");
return CURLE_FTP_COULDNT_GET_SIZE;
}
conn->resume_from = gottensize;
}
if(conn->resume_from) {
int passed=0;
data->set.ftp_append = 1;
do {
int readthisamountnow = (conn->resume_from - passed);
int actuallyread;
if(readthisamountnow > BUFSIZE)
readthisamountnow = BUFSIZE;
actuallyread =
data->set.fread(data->state.buffer, 1, readthisamountnow,
data->set.in);
passed += actuallyread;
if(actuallyread != readthisamountnow) {
failf(data, "Could only read %d bytes from the input", passed);
return CURLE_FTP_COULDNT_USE_REST;
}
}
while(passed != conn->resume_from);
if(data->set.infilesize>0) {
data->set.infilesize -= conn->resume_from;
if(data->set.infilesize <= 0) {
infof(data, "File already completely uploaded\n");
result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
ftp->no_transfer = TRUE;
return CURLE_OK;
}
}
}
}
if(data->set.ftp_append) {
FTPSENDF(conn, "APPE %s", ftp->file);
}
else {
FTPSENDF(conn, "STOR %s", ftp->file);
}
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode>=400) {
failf(data, "Failed FTP upload:%s", buf+3);
return CURLE_FTP_COULDNT_STOR_FILE;
}
if(data->set.ftp_use_port) {
result = AllowServerConnect(data, conn, conn->secondarysocket);
if( result )
return result;
}
*bytecountp=0;
Curl_pgrsSetUploadSize(data, data->set.infilesize);
result = Curl_Transfer(conn, -1, -1, FALSE, NULL,
conn->secondarysocket, bytecountp);
if(result)
return result;
}
else if(!data->set.no_body) {
bool dirlist=FALSE;
long downloadsize=-1;
if(conn->bits.use_range && conn->range) {
long from, to;
int totalsize=-1;
char *ptr;
char *ptr2;
from=strtol(conn->range, &ptr, 0);
while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
ptr++;
to=strtol(ptr, &ptr2, 0);
if(ptr == ptr2) {
to=-1;
}
if((-1 == to) && (from>=0)) {
conn->resume_from = from;
infof(data, "FTP RANGE %d to end of file\n", from);
}
else if(from < 0) {
totalsize = -from;
conn->maxdownload = -from;
conn->resume_from = from;
infof(data, "FTP RANGE the last %d bytes\n", totalsize);
}
else {
totalsize = to-from;
conn->maxdownload = totalsize+1;
conn->resume_from = from;
infof(data, "FTP RANGE from %d getting %d bytes\n", from,
conn->maxdownload);
}
infof(data, "range-download from %d to %d, totally %d bytes\n",
from, to, totalsize);
ftp->dont_check = TRUE;
}
if((data->set.ftp_list_only) || !ftp->file) {
dirlist = TRUE;
result = ftp_transfertype(conn, TRUE );
if(result)
return result;
FTPSENDF(conn, "%s",
data->set.customrequest?data->set.customrequest:
(data->set.ftp_list_only?"NLST":"LIST"));
}
else {
ssize_t foundsize;
result = ftp_transfertype(conn, data->set.ftp_ascii);
if(result)
return result;
if(data->set.prequote) {
if ((result = ftp_sendquote(conn, data->set.prequote)) != CURLE_OK)
return result;
}
result = ftp_getsize(conn, ftp->file, &foundsize);
if(CURLE_OK == result)
downloadsize = foundsize;
if(conn->resume_from) {
if(CURLE_OK != result) {
infof(data, "ftp server doesn't support SIZE\n");
}
else {
if(conn->resume_from< 0) {
if(foundsize < -conn->resume_from) {
failf(data, "Offset (%d) was beyond file size (%d)",
conn->resume_from, foundsize);
return CURLE_FTP_BAD_DOWNLOAD_RESUME;
}
downloadsize = -conn->resume_from;
conn->resume_from = foundsize - downloadsize;
}
else {
if(foundsize < conn->resume_from) {
failf(data, "Offset (%d) was beyond file size (%d)",
conn->resume_from, foundsize);
return CURLE_FTP_BAD_DOWNLOAD_RESUME;
}
downloadsize = foundsize-conn->resume_from;
}
}
if (downloadsize == 0) {
result=Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
infof(data, "File already completely downloaded\n");
ftp->no_transfer = TRUE;
return CURLE_OK;
}
infof(data, "Instructs server to resume from offset %d\n",
conn->resume_from);
FTPSENDF(conn, "REST %d", conn->resume_from);
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if(ftpcode != 350) {
failf(data, "Couldn't use REST: %s", buf+4);
return CURLE_FTP_COULDNT_USE_REST;
}
}
FTPSENDF(conn, "RETR %s", ftp->file);
}
nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
if(nread < 0)
return CURLE_OPERATION_TIMEOUTED;
if((ftpcode == 150) || (ftpcode == 125)) {
int size=-1;
if(!dirlist &&
!data->set.ftp_ascii &&
(-1 == downloadsize)) {
char *bytes;
bytes=strstr(buf, " bytes");
if(bytes--) {
int index=bytes-buf;
while(--index) {
if('(' == *bytes)
break;
if(!isdigit((int)*bytes)) {
bytes=NULL;
break;
}
bytes--;
}
if(bytes++) {
size = atoi(bytes);
}
}
}
else if(downloadsize > -1)
size = downloadsize;
if(data->set.ftp_use_port) {
result = AllowServerConnect(data, conn, conn->secondarysocket);
if( result )
return result;
}
infof(data, "Getting file with size: %d\n", size);
result=Curl_Transfer(conn, conn->secondarysocket, size, FALSE,
bytecountp,
-1, NULL);
if(result)
return result;
}
else {
failf(data, "%s", buf+4);
return CURLE_FTP_COULDNT_RETR_FILE;
}
}
return CURLE_OK;
}
static
CURLcode ftp_perform(struct connectdata *conn,
bool *connected)
{
CURLcode result=CURLE_OK;
struct SessionHandle *data=conn->data;
char *buf = data->state.buffer;
struct FTP *ftp = conn->proto.ftp;
if(data->set.quote) {
if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
return result;
}
if (conn->bits.reuse && ftp->entrypath) {
if ((result = ftp_cwd(conn, ftp->entrypath)) != CURLE_OK)
return result;
}
if(ftp->dir && ftp->dir[0]) {
if ((result = ftp_cwd(conn, ftp->dir)) != CURLE_OK)
return result;
}
if(data->set.get_filetime && ftp->file) {
result = ftp_getfiletime(conn, ftp->file);
if(result)
return result;
}
if(data->set.no_body && data->set.include_header && ftp->file) {
ssize_t filesize;
ftp->no_transfer = TRUE;
result = ftp_transfertype(conn, data->set.ftp_ascii);
if(result)
return result;
result = ftp_getsize(conn, ftp->file, &filesize);
if(CURLE_OK == result) {
sprintf(buf, "Content-Length: %d\r\n", filesize);
result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
}
#ifdef HAVE_STRFTIME
if(data->set.get_filetime && data->info.filetime) {
struct tm *tm;
#ifdef HAVE_LOCALTIME_R
struct tm buffer;
tm = (struct tm *)localtime_r(&data->info.filetime, &buffer);
#else
tm = localtime((unsigned long *)&data->info.filetime);
#endif
strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S %Z\r\n",
tm);
result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
}
#endif
return CURLE_OK;
}
if(data->set.no_body)
ftp->no_transfer = TRUE;
else if(data->set.ftp_use_port) {
result = ftp_use_port(conn);
if(CURLE_OK == result) {
infof(data, "Ordered connect of the data stream with PORT!\n");
*connected = TRUE;
}
}
else {
result = ftp_use_pasv(conn, connected);
if(connected)
infof(data, "Connected the data stream with PASV!\n");
}
return result;
}
CURLcode Curl_ftp(struct connectdata *conn)
{
CURLcode retcode;
bool connected;
struct SessionHandle *data = conn->data;
struct FTP *ftp;
int dirlength=0;
ftp = conn->proto.ftp;
ftp->file = strrchr(conn->ppath, '/');
if(ftp->file) {
if(ftp->file != conn->ppath)
dirlength=ftp->file-conn->ppath;
ftp->file++;
}
else {
ftp->file = conn->ppath;
}
if(*ftp->file) {
ftp->file = curl_unescape(ftp->file, 0);
if(NULL == ftp->file) {
failf(data, "no memory");
return CURLE_OUT_OF_MEMORY;
}
}
else
ftp->file=NULL;
ftp->urlpath = conn->ppath;
if(dirlength) {
ftp->dir = curl_unescape(ftp->urlpath, dirlength);
if(NULL == ftp->dir) {
if(ftp->file)
free(ftp->file);
failf(data, "no memory");
return CURLE_OUT_OF_MEMORY;
}
}
else
ftp->dir = NULL;
retcode = ftp_perform(conn, &connected);
if(CURLE_OK == retcode) {
if(connected)
retcode = Curl_ftp_nextconnect(conn);
else
conn->bits.do_more = TRUE;
}
return retcode;
}
CURLcode Curl_ftpsendf(struct connectdata *conn,
const char *fmt, ...)
{
ssize_t bytes_written;
char s[256];
ssize_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);
do {
res = Curl_write(conn, conn->firstsocket, sptr, write_len,
&bytes_written);
if(CURLE_OK != res)
break;
if(conn->data->set.verbose)
Curl_debug(conn->data, CURLINFO_HEADER_OUT, sptr, bytes_written);
if(bytes_written != write_len) {
write_len -= bytes_written;
sptr += bytes_written;
}
else
break;
} while(1);
return res;
}
CURLcode Curl_ftp_disconnect(struct connectdata *conn)
{
struct FTP *ftp= conn->proto.ftp;
if(ftp) {
if(ftp->entrypath)
free(ftp->entrypath);
if(ftp->cache)
free(ftp->cache);
if(ftp->file)
free(ftp->file);
if(ftp->dir)
free(ftp->dir);
ftp->file = ftp->dir = NULL;
}
return CURLE_OK;
}
#endif