mirror of
https://github.com/reactos/reactos.git
synced 2024-12-27 01:24:38 +00:00
86bda6b3d9
svn path=/trunk/; revision=2453
1362 lines
36 KiB
C
1362 lines
36 KiB
C
/* ftp.c
|
|
*
|
|
* Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#define _libncftp_ftp_c_
|
|
#include "syshdrs.h"
|
|
|
|
char gLibNcFTPVersion[64] = kLibraryVersion;
|
|
|
|
#ifdef NO_SIGNALS
|
|
static char gNoSignalsMarker[] = "@(#) LibNcFTP - NO_SIGNALS";
|
|
#else
|
|
|
|
static int gGotSig = 0;
|
|
#ifdef HAVE_SIGSETJMP
|
|
static sigjmp_buf gCancelConnectJmp;
|
|
#else
|
|
static jmp_buf gCancelConnectJmp;
|
|
#endif /* HAVE_SIGSETJMP */
|
|
|
|
#endif /* NO_SIGNALS */
|
|
|
|
|
|
#ifndef lint
|
|
static char gCopyright[] = "@(#) LibNcFTP Copyright 1995-2000, by Mike Gleason. All rights reserved.";
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBSOCKS5
|
|
# define SOCKS 5
|
|
# include <socks.h>
|
|
#else
|
|
# ifdef HAVE_LIBSOCKS
|
|
# define accept Raccept
|
|
# define connect Rconnect
|
|
# define getsockname Rgetsockname
|
|
# define listen Rlisten
|
|
# endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* On entry, you should have 'host' be set to a symbolic name (like
|
|
* cse.unl.edu), or set to a numeric address (like 129.93.3.1).
|
|
* If the function fails, it will return NULL, but if the host was
|
|
* a numeric style address, you'll have the ip_address to fall back on.
|
|
*/
|
|
|
|
static struct hostent *
|
|
GetHostEntry(char *host, struct in_addr *ip_address)
|
|
{
|
|
struct in_addr ip;
|
|
struct hostent *hp;
|
|
|
|
/* See if the host was given in the dotted IP format, like "36.44.0.2."
|
|
* If it was, inet_addr will convert that to a 32-bit binary value;
|
|
* it not, inet_addr will return (-1L).
|
|
*/
|
|
ip.s_addr = inet_addr(host);
|
|
if (ip.s_addr != INADDR_NONE) {
|
|
hp = NULL;
|
|
} else {
|
|
/* No IP address, so it must be a hostname, like ftp.wustl.edu. */
|
|
hp = gethostbyname(host);
|
|
if (hp != NULL)
|
|
(void) memcpy(&ip.s_addr, hp->h_addr_list[0], (size_t) hp->h_length);
|
|
}
|
|
if (ip_address != NULL)
|
|
*ip_address = ip;
|
|
return (hp);
|
|
} /* GetHostEntry */
|
|
|
|
|
|
|
|
|
|
/* Makes every effort to return a fully qualified domain name. */
|
|
int
|
|
GetOurHostName(char *host, size_t siz)
|
|
{
|
|
#ifdef HOSTNAME
|
|
/* You can hardcode in the name if this routine doesn't work
|
|
* the way you want it to.
|
|
*/
|
|
Strncpy(host, HOSTNAME, siz);
|
|
return (1); /* Success */
|
|
#else
|
|
struct hostent *hp;
|
|
int result;
|
|
char **curAlias;
|
|
char domain[64];
|
|
char *cp;
|
|
int rc;
|
|
|
|
host[0] = '\0';
|
|
result = gethostname(host, (int) siz);
|
|
if ((result < 0) || (host[0] == '\0')) {
|
|
return (-1);
|
|
}
|
|
|
|
if (strchr(host, '.') != NULL) {
|
|
/* gethostname returned full name (like "cse.unl.edu"), instead
|
|
* of just the node name (like "cse").
|
|
*/
|
|
return (2); /* Success */
|
|
}
|
|
|
|
hp = gethostbyname(host);
|
|
if (hp != NULL) {
|
|
/* Maybe the host entry has the full name. */
|
|
cp = strchr((char *) hp->h_name, '.');
|
|
if ((cp != NULL) && (cp[1] != '\0')) {
|
|
/* The 'name' field for the host entry had full name. */
|
|
(void) Strncpy(host, (char *) hp->h_name, siz);
|
|
return (3); /* Success */
|
|
}
|
|
|
|
/* Now try the list of aliases, to see if any of those look real. */
|
|
for (curAlias = hp->h_aliases; *curAlias != NULL; curAlias++) {
|
|
cp = strchr(*curAlias, '.');
|
|
if ((cp != NULL) && (cp[1] != '\0')) {
|
|
(void) Strncpy(host, *curAlias, siz);
|
|
return (4); /* Success */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Otherwise, we just have the node name. See if we can get the
|
|
* domain name ourselves.
|
|
*/
|
|
#ifdef DOMAINNAME
|
|
(void) STRNCPY(domain, DOMAINNAME);
|
|
rc = 5;
|
|
#else
|
|
rc = -1;
|
|
domain[0] = '\0';
|
|
# if defined(HAVE_RES_INIT) && defined(HAVE__RES_DEFDNAME)
|
|
if (domain[0] == '\0') {
|
|
(void) res_init();
|
|
if ((_res.defdname != NULL) && (_res.defdname[0] != '\0')) {
|
|
(void) STRNCPY(domain, _res.defdname);
|
|
rc = 6;
|
|
}
|
|
}
|
|
# endif /* HAVE_RES_INIT && HAVE__RES_DEFDNAME */
|
|
|
|
if (domain[0] == '\0') {
|
|
FILE *fp;
|
|
char line[256];
|
|
char *tok;
|
|
|
|
fp = fopen("/etc/resolv.conf", "r");
|
|
if (fp != NULL) {
|
|
(void) memset(line, 0, sizeof(line));
|
|
while (fgets(line, sizeof(line) - 1, fp) != NULL) {
|
|
if (!isalpha((int) line[0]))
|
|
continue; /* Skip comment lines. */
|
|
tok = strtok(line, " \t\n\r");
|
|
if (tok == NULL)
|
|
continue; /* Impossible */
|
|
if (strcmp(tok, "domain") == 0) {
|
|
tok = strtok(NULL, " \t\n\r");
|
|
if (tok == NULL)
|
|
continue; /* syntax error */
|
|
(void) STRNCPY(domain, tok);
|
|
rc = 7;
|
|
break; /* Done. */
|
|
}
|
|
}
|
|
(void) fclose(fp);
|
|
}
|
|
}
|
|
#endif /* DOMAINNAME */
|
|
|
|
if (domain[0] != '\0') {
|
|
/* Supposedly, it's legal for a domain name with
|
|
* a period at the end.
|
|
*/
|
|
cp = domain + strlen(domain) - 1;
|
|
if (*cp == '.')
|
|
*cp = '\0';
|
|
if (domain[0] != '.')
|
|
(void) Strncat(host, ".", siz);
|
|
(void) Strncat(host, domain, siz);
|
|
}
|
|
if (rc < 0)
|
|
host[0] = '\0';
|
|
return(rc); /* Success */
|
|
#endif /* !HOSTNAME */
|
|
} /* GetOurHostName */
|
|
|
|
|
|
|
|
void
|
|
CloseControlConnection(const FTPCIPtr cip)
|
|
{
|
|
/* This will close each file, if it was open. */
|
|
#ifdef NO_SIGNALS
|
|
SClose(cip->ctrlSocketR, 3);
|
|
cip->ctrlSocketR = kClosedFileDescriptor;
|
|
cip->ctrlSocketW = kClosedFileDescriptor;
|
|
DisposeSReadlineInfo(&cip->ctrlSrl);
|
|
#else /* NO_SIGNALS */
|
|
if (cip->ctrlTimeout > 0)
|
|
(void) alarm(cip->ctrlTimeout);
|
|
CloseFile(&cip->cin);
|
|
CloseFile(&cip->cout);
|
|
cip->ctrlSocketR = kClosedFileDescriptor;
|
|
cip->ctrlSocketW = kClosedFileDescriptor;
|
|
if (cip->ctrlTimeout > 0)
|
|
(void) alarm(0);
|
|
#endif /* NO_SIGNALS */
|
|
cip->connected = 0;
|
|
cip->loggedIn = 0;
|
|
} /* CloseControlConnection */
|
|
|
|
|
|
|
|
static int
|
|
GetSocketAddress(const FTPCIPtr cip, int sockfd, struct sockaddr_in *saddr)
|
|
{
|
|
int len = (int) sizeof (struct sockaddr_in);
|
|
int result = 0;
|
|
|
|
if (getsockname(sockfd, (struct sockaddr *)saddr, &len) < 0) {
|
|
Error(cip, kDoPerror, "Could not get socket name.\n");
|
|
cip->errNo = kErrGetSockName;
|
|
result = kErrGetSockName;
|
|
}
|
|
return (result);
|
|
} /* GetSocketAddress */
|
|
|
|
|
|
|
|
|
|
int
|
|
SetKeepAlive(const FTPCIPtr cip, int sockfd)
|
|
{
|
|
#ifndef SO_KEEPALIVE
|
|
cip->errNo = kErrSetKeepAlive;
|
|
return (kErrSetKeepAlive);
|
|
#else
|
|
int opt;
|
|
|
|
opt = 1;
|
|
|
|
if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, (int) sizeof(opt)) < 0) {
|
|
/* Error(cip, kDoPerror, "Could not set keep-alive mode.\n"); */
|
|
cip->errNo = kErrSetKeepAlive;
|
|
return (kErrSetKeepAlive);
|
|
}
|
|
return (kNoErr);
|
|
#endif /* SO_KEEPALIVE */
|
|
} /* SetKeepAlive */
|
|
|
|
|
|
|
|
|
|
int
|
|
SetLinger(const FTPCIPtr cip, int sockfd, int onoff)
|
|
{
|
|
#ifndef SO_LINGER
|
|
cip->errNo = kErrSetLinger;
|
|
return (kErrSetLinger);
|
|
#else
|
|
struct linger li;
|
|
|
|
if (onoff != 0) {
|
|
li.l_onoff = 1;
|
|
li.l_linger = 120; /* 2 minutes, but system ignores field. */
|
|
} else {
|
|
li.l_onoff = 0;
|
|
li.l_linger = 0;
|
|
}
|
|
/* Have the system make an effort to deliver any unsent data,
|
|
* even after we close the connection.
|
|
*/
|
|
if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *) &li, (int) sizeof(li)) < 0) {
|
|
/* Error(cip, kDoPerror, "Could not set linger mode.\n"); */
|
|
cip->errNo = kErrSetLinger;
|
|
return (kErrSetLinger);
|
|
}
|
|
return (kNoErr);
|
|
#endif /* SO_LINGER */
|
|
} /* SetLinger */
|
|
|
|
|
|
|
|
|
|
#ifdef IP_TOS
|
|
int
|
|
SetTypeOfService(const FTPCIPtr cip, int sockfd, int tosType)
|
|
{
|
|
/* Specify to the router what type of connection this is, so it
|
|
* can prioritize packets.
|
|
*/
|
|
if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, (char *) &tosType, (int) sizeof(tosType)) < 0) {
|
|
/* Error(cip, kDoPerror, "Could not set type of service.\n"); */
|
|
cip->errNo = kErrSetTypeOfService;
|
|
return (kErrSetTypeOfService);
|
|
}
|
|
return (kNoErr);
|
|
} /* SetTypeOfService */
|
|
#endif /* IP_TOS */
|
|
|
|
|
|
|
|
|
|
#ifdef SO_OOBINLINE
|
|
int
|
|
SetInlineOutOfBandData(const FTPCIPtr cip, int sockfd)
|
|
{
|
|
int on = 1;
|
|
|
|
if (setsockopt(sockfd, SOL_SOCKET, SO_OOBINLINE, (char *) &on, (int) sizeof(on)) < 0) {
|
|
Error(cip, kDoPerror, "Could not set out of band inline mode.\n");
|
|
cip->errNo = kErrSetOutOfBandInline;
|
|
return (kErrSetOutOfBandInline);
|
|
}
|
|
return (kNoErr);
|
|
} /* SetInlineOutOfBandData */
|
|
#endif /* SO_OOBINLINE */
|
|
|
|
|
|
|
|
|
|
#ifndef NO_SIGNALS
|
|
|
|
static void
|
|
CancelConnect(int signum)
|
|
{
|
|
gGotSig = signum;
|
|
#ifdef HAVE_SIGSETJMP
|
|
siglongjmp(gCancelConnectJmp, 1);
|
|
#else
|
|
longjmp(gCancelConnectJmp, 1);
|
|
#endif /* HAVE_SIGSETJMP */
|
|
} /* CancelConnect */
|
|
|
|
#endif /* NO_SIGNALS */
|
|
|
|
|
|
|
|
int
|
|
OpenControlConnection(const FTPCIPtr cip, char *host, unsigned int port)
|
|
{
|
|
struct in_addr ip_address;
|
|
int err = 0;
|
|
int result;
|
|
int oerrno;
|
|
volatile int sockfd = -1;
|
|
volatile int sock2fd = -1;
|
|
ResponsePtr rp;
|
|
char **volatile curaddr;
|
|
struct hostent *hp;
|
|
char *volatile fhost;
|
|
unsigned int fport;
|
|
#ifndef NO_SIGNALS
|
|
volatile FTPSigProc osigint;
|
|
volatile FTPSigProc osigalrm;
|
|
volatile FTPCIPtr vcip;
|
|
int sj;
|
|
#endif /* NO_SIGNALS */
|
|
const char *firstLine, *secondLine, *srvr;
|
|
|
|
LIBNCFTP_USE_VAR(gLibNcFTPVersion);
|
|
LIBNCFTP_USE_VAR(gCopyright);
|
|
#ifdef NO_SIGNALS
|
|
LIBNCFTP_USE_VAR(gNoSignalsMarker);
|
|
#endif /* NO_SIGNALS */
|
|
|
|
if (cip->firewallType == kFirewallNotInUse) {
|
|
fhost = host;
|
|
fport = port;
|
|
} else {
|
|
fhost = cip->firewallHost;
|
|
fport = cip->firewallPort;
|
|
}
|
|
if (fport == 0)
|
|
fport = cip->lip->defaultPort;
|
|
|
|
/* Since we're the client, we just have to get a socket() and
|
|
* connect() it.
|
|
*/
|
|
(void) ZERO(cip->servCtlAddr);
|
|
cip->cin = NULL;
|
|
cip->cout = NULL;
|
|
|
|
/* Make sure we use network byte-order. */
|
|
fport = (unsigned int) htons((unsigned short) fport);
|
|
|
|
cip->servCtlAddr.sin_port = (unsigned short) fport;
|
|
|
|
hp = GetHostEntry(fhost, &ip_address);
|
|
|
|
if (hp == NULL) {
|
|
/* Okay, no Host entry, but maybe we have a numeric address
|
|
* in ip_address we can try.
|
|
*/
|
|
if (ip_address.s_addr == INADDR_NONE) {
|
|
Error(cip, kDontPerror, "%s: unknown host.\n", fhost);
|
|
cip->errNo = kErrHostUnknown;
|
|
return (kErrHostUnknown);
|
|
}
|
|
cip->servCtlAddr.sin_family = AF_INET;
|
|
cip->servCtlAddr.sin_addr.s_addr = ip_address.s_addr;
|
|
} else {
|
|
cip->servCtlAddr.sin_family = hp->h_addrtype;
|
|
/* We'll fill in the rest of the structure below. */
|
|
}
|
|
|
|
/* After obtaining a socket, try to connect it to a remote
|
|
* address. If we didn't get a host entry, we will only have
|
|
* one thing to try (ip_address); if we do have one, we can try
|
|
* every address in the list from the host entry.
|
|
*/
|
|
|
|
if (hp == NULL) {
|
|
/* Since we're given a single raw address, and not a host entry,
|
|
* we can only try this one address and not any other addresses
|
|
* that could be present for a site with a host entry.
|
|
*/
|
|
|
|
if ((sockfd = socket(cip->servCtlAddr.sin_family, SOCK_STREAM, 0)) < 0) {
|
|
Error(cip, kDoPerror, "Could not get a socket.\n");
|
|
cip->errNo = kErrNewStreamSocket;
|
|
return (kErrNewStreamSocket);
|
|
}
|
|
|
|
/* This doesn't do anything if you left these
|
|
* at their defaults (zero). Otherwise it
|
|
* tries to set the buffer size to the
|
|
* size specified.
|
|
*/
|
|
(void) SetSockBufSize(sockfd, cip->ctrlSocketRBufSize, cip->ctrlSocketSBufSize);
|
|
|
|
#ifdef NO_SIGNALS
|
|
err = SConnect(sockfd, &cip->servCtlAddr, (int) cip->connTimeout);
|
|
|
|
if (err < 0) {
|
|
oerrno = errno;
|
|
(void) SClose(sockfd, 3);
|
|
errno = oerrno;
|
|
sockfd = -1;
|
|
}
|
|
#else /* NO_SIGNALS */
|
|
osigint = (volatile FTPSigProc) signal(SIGINT, CancelConnect);
|
|
if (cip->connTimeout > 0) {
|
|
osigalrm = (volatile FTPSigProc) signal(SIGALRM, CancelConnect);
|
|
(void) alarm(cip->connTimeout);
|
|
}
|
|
|
|
vcip = cip;
|
|
|
|
#ifdef HAVE_SIGSETJMP
|
|
sj = sigsetjmp(gCancelConnectJmp, 1);
|
|
#else
|
|
sj = setjmp(gCancelConnectJmp);
|
|
#endif /* HAVE_SIGSETJMP */
|
|
|
|
if (sj != 0) {
|
|
/* Interrupted by a signal. */
|
|
(void) closesocket(sockfd);
|
|
(void) signal(SIGINT, (FTPSigProc) osigint);
|
|
if (vcip->connTimeout > 0) {
|
|
(void) alarm(0);
|
|
(void) signal(SIGALRM, (FTPSigProc) osigalrm);
|
|
}
|
|
if (gGotSig == SIGINT) {
|
|
result = vcip->errNo = kErrConnectMiscErr;
|
|
Error(vcip, kDontPerror, "Connection attempt canceled.\n");
|
|
(void) kill(getpid(), SIGINT);
|
|
} else if (gGotSig == SIGALRM) {
|
|
result = vcip->errNo = kErrConnectRetryableErr;
|
|
Error(vcip, kDontPerror, "Connection attempt timed-out.\n");
|
|
(void) kill(getpid(), SIGALRM);
|
|
} else {
|
|
result = vcip->errNo = kErrConnectMiscErr;
|
|
Error(vcip, kDontPerror, "Connection attempt failed due to an unexpected signal (%d).\n", gGotSig);
|
|
}
|
|
return (result);
|
|
} else {
|
|
err = connect(sockfd, (struct sockaddr *) &cip->servCtlAddr,
|
|
(int) sizeof (cip->servCtlAddr));
|
|
if (cip->connTimeout > 0) {
|
|
(void) alarm(0);
|
|
(void) signal(SIGALRM, (FTPSigProc) osigalrm);
|
|
}
|
|
(void) signal(SIGINT, (FTPSigProc) osigint);
|
|
}
|
|
|
|
if (err < 0) {
|
|
oerrno = errno;
|
|
(void) closesocket(sockfd);
|
|
errno = oerrno;
|
|
sockfd = -1;
|
|
}
|
|
#endif /* NO_SIGNALS */
|
|
} else {
|
|
/* We can try each address in the list. We'll quit when we
|
|
* run out of addresses to try or get a successful connection.
|
|
*/
|
|
for (curaddr = hp->h_addr_list; *curaddr != NULL; curaddr++) {
|
|
if ((sockfd = socket(cip->servCtlAddr.sin_family, SOCK_STREAM, 0)) < 0) {
|
|
Error(cip, kDoPerror, "Could not get a socket.\n");
|
|
cip->errNo = kErrNewStreamSocket;
|
|
return (kErrNewStreamSocket);
|
|
}
|
|
/* This could overwrite the address field in the structure,
|
|
* but this is okay because the structure has a junk field
|
|
* just for this purpose.
|
|
*/
|
|
(void) memcpy(&cip->servCtlAddr.sin_addr, *curaddr, (size_t) hp->h_length);
|
|
|
|
/* This doesn't do anything if you left these
|
|
* at their defaults (zero). Otherwise it
|
|
* tries to set the buffer size to the
|
|
* size specified.
|
|
*/
|
|
(void) SetSockBufSize(sockfd, cip->ctrlSocketRBufSize, cip->ctrlSocketSBufSize);
|
|
|
|
#ifdef NO_SIGNALS
|
|
err = SConnect(sockfd, &cip->servCtlAddr, (int) cip->connTimeout);
|
|
|
|
if (err == 0)
|
|
break;
|
|
oerrno = errno;
|
|
(void) SClose(sockfd, 3);
|
|
errno = oerrno;
|
|
sockfd = -1;
|
|
#else /* NO_SIGNALS */
|
|
|
|
osigint = (volatile FTPSigProc) signal(SIGINT, CancelConnect);
|
|
if (cip->connTimeout > 0) {
|
|
osigalrm = (volatile FTPSigProc) signal(SIGALRM, CancelConnect);
|
|
(void) alarm(cip->connTimeout);
|
|
}
|
|
|
|
vcip = cip;
|
|
#ifdef HAVE_SIGSETJMP
|
|
sj = sigsetjmp(gCancelConnectJmp, 1);
|
|
#else
|
|
sj = setjmp(gCancelConnectJmp);
|
|
#endif /* HAVE_SIGSETJMP */
|
|
|
|
if (sj != 0) {
|
|
/* Interrupted by a signal. */
|
|
(void) closesocket(sockfd);
|
|
(void) signal(SIGINT, (FTPSigProc) osigint);
|
|
if (vcip->connTimeout > 0) {
|
|
(void) alarm(0);
|
|
(void) signal(SIGALRM, (FTPSigProc) osigalrm);
|
|
}
|
|
if (gGotSig == SIGINT) {
|
|
result = vcip->errNo = kErrConnectMiscErr;
|
|
Error(vcip, kDontPerror, "Connection attempt canceled.\n");
|
|
(void) kill(getpid(), SIGINT);
|
|
} else if (gGotSig == SIGALRM) {
|
|
result = vcip->errNo = kErrConnectRetryableErr;
|
|
Error(vcip, kDontPerror, "Connection attempt timed-out.\n");
|
|
(void) kill(getpid(), SIGALRM);
|
|
} else {
|
|
result = vcip->errNo = kErrConnectMiscErr;
|
|
Error(vcip, kDontPerror, "Connection attempt failed due to an unexpected signal (%d).\n", gGotSig);
|
|
}
|
|
return (result);
|
|
} else {
|
|
err = connect(sockfd, (struct sockaddr *) &cip->servCtlAddr,
|
|
(int) sizeof (cip->servCtlAddr));
|
|
if (cip->connTimeout > 0) {
|
|
(void) alarm(0);
|
|
(void) signal(SIGALRM, (FTPSigProc) osigalrm);
|
|
}
|
|
(void) signal(SIGINT, (FTPSigProc) osigint);
|
|
}
|
|
|
|
if (err == 0)
|
|
break;
|
|
oerrno = errno;
|
|
(void) closesocket(sockfd);
|
|
errno = oerrno;
|
|
sockfd = -1;
|
|
#endif /* NO_SIGNALS */
|
|
}
|
|
}
|
|
|
|
if (err < 0) {
|
|
/* Could not connect. Close up shop and go home. */
|
|
|
|
/* If possible, tell the caller if they should bother
|
|
* calling back later.
|
|
*/
|
|
switch (errno) {
|
|
#ifdef ENETDOWN
|
|
case ENETDOWN:
|
|
#elif defined(WSAENETDOWN)
|
|
case WSAENETDOWN:
|
|
#endif
|
|
#ifdef ENETUNREACH
|
|
case ENETUNREACH:
|
|
#elif defined(WSAENETUNREACH)
|
|
case WSAENETUNREACH:
|
|
#endif
|
|
#ifdef ECONNABORTED
|
|
case ECONNABORTED:
|
|
#elif defined(WSAECONNABORTED)
|
|
case WSAECONNABORTED:
|
|
#endif
|
|
#ifdef ETIMEDOUT
|
|
case ETIMEDOUT:
|
|
#elif defined(WSAETIMEDOUT)
|
|
case WSAETIMEDOUT:
|
|
#endif
|
|
#ifdef EHOSTDOWN
|
|
case EHOSTDOWN:
|
|
#elif defined(WSAEHOSTDOWN)
|
|
case WSAEHOSTDOWN:
|
|
#endif
|
|
#ifdef ECONNRESET
|
|
case ECONNRESET:
|
|
#elif defined(WSAECONNRESET)
|
|
case WSAECONNRESET:
|
|
#endif
|
|
Error(cip, kDoPerror, "Could not connect to %s -- try again later.\n", fhost);
|
|
result = cip->errNo = kErrConnectRetryableErr;
|
|
break;
|
|
#ifdef ECONNREFUSED
|
|
case ECONNREFUSED:
|
|
#elif defined(WSAECONNREFUSED)
|
|
case WSAECONNREFUSED:
|
|
#endif
|
|
Error(cip, kDoPerror, "Could not connect to %s.\n", fhost);
|
|
result = cip->errNo = kErrConnectRefused;
|
|
break;
|
|
default:
|
|
Error(cip, kDoPerror, "Could not connect to %s.\n", fhost);
|
|
result = cip->errNo = kErrConnectMiscErr;
|
|
}
|
|
goto fatal;
|
|
}
|
|
|
|
/* Get our end of the socket address for later use. */
|
|
if ((result = GetSocketAddress(cip, sockfd, &cip->ourCtlAddr)) < 0)
|
|
goto fatal;
|
|
|
|
#ifdef SO_OOBINLINE
|
|
/* We want Out-of-band data to appear in the regular stream,
|
|
* since we can handle TELNET.
|
|
*/
|
|
(void) SetInlineOutOfBandData(cip, sockfd);
|
|
#endif
|
|
(void) SetKeepAlive(cip, sockfd);
|
|
(void) SetLinger(cip, sockfd, 0); /* Don't need it for ctrl. */
|
|
|
|
#if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
|
|
/* Control connection is somewhat interactive, so quick response
|
|
* is desired.
|
|
*/
|
|
(void) SetTypeOfService(cip, sockfd, IPTOS_LOWDELAY);
|
|
#endif
|
|
|
|
#ifdef NO_SIGNALS
|
|
cip->ctrlSocketR = sockfd;
|
|
cip->ctrlSocketW = sockfd;
|
|
cip->cout = NULL;
|
|
cip->cin = NULL;
|
|
sock2fd = kClosedFileDescriptor;
|
|
|
|
if (InitSReadlineInfo(&cip->ctrlSrl, sockfd, cip->srlBuf, sizeof(cip->srlBuf), (int) cip->ctrlTimeout, 1) < 0) {
|
|
result = kErrFdopenW;
|
|
cip->errNo = kErrFdopenW;
|
|
Error(cip, kDoPerror, "Could not fdopen.\n");
|
|
goto fatal;
|
|
}
|
|
#else /* NO_SIGNALS */
|
|
if ((sock2fd = dup(sockfd)) < 0) {
|
|
result = kErrDupSocket;
|
|
cip->errNo = kErrDupSocket;
|
|
Error(cip, kDoPerror, "Could not duplicate a file descriptor.\n");
|
|
goto fatal;
|
|
}
|
|
|
|
/* Now setup the FILE pointers for use with the Std I/O library
|
|
* routines.
|
|
*/
|
|
if ((cip->cin = fdopen(sockfd, "r")) == NULL) {
|
|
result = kErrFdopenR;
|
|
cip->errNo = kErrFdopenR;
|
|
Error(cip, kDoPerror, "Could not fdopen.\n");
|
|
goto fatal;
|
|
}
|
|
|
|
if ((cip->cout = fdopen(sock2fd, "w")) == NULL) {
|
|
result = kErrFdopenW;
|
|
cip->errNo = kErrFdopenW;
|
|
Error(cip, kDoPerror, "Could not fdopen.\n");
|
|
CloseFile(&cip->cin);
|
|
sockfd = kClosedFileDescriptor;
|
|
goto fatal;
|
|
}
|
|
|
|
cip->ctrlSocketR = sockfd;
|
|
cip->ctrlSocketW = sockfd;
|
|
|
|
/* We'll be reading and writing lines, so use line buffering. This
|
|
* is necessary since the stdio library will use full buffering
|
|
* for all streams not associated with the tty.
|
|
*/
|
|
#ifdef HAVE_SETLINEBUF
|
|
setlinebuf(cip->cin);
|
|
setlinebuf(cip->cout);
|
|
#else
|
|
(void) SETVBUF(cip->cin, NULL, _IOLBF, (size_t) BUFSIZ);
|
|
(void) SETVBUF(cip->cout, NULL, _IOLBF, (size_t) BUFSIZ);
|
|
#endif
|
|
#endif /* NO_SIGNALS */
|
|
|
|
#ifdef HAVE_INET_NTOP /* Mostly to workaround bug in IRIX 6.5's inet_ntoa */
|
|
(void) memset(cip->ip, 0, sizeof(cip->ip));
|
|
(void) inet_ntop(AF_INET, &cip->servCtlAddr.sin_addr, cip->ip, sizeof(cip->ip) - 1);
|
|
#else
|
|
(void) STRNCPY(cip->ip, inet_ntoa(cip->servCtlAddr.sin_addr));
|
|
#endif
|
|
if ((hp == NULL) || (hp->h_name == NULL))
|
|
(void) STRNCPY(cip->actualHost, fhost);
|
|
else
|
|
(void) STRNCPY(cip->actualHost, (char *) hp->h_name);
|
|
|
|
/* Read the startup message from the server. */
|
|
rp = InitResponse();
|
|
if (rp == NULL) {
|
|
Error(cip, kDontPerror, "Malloc failed.\n");
|
|
cip->errNo = kErrMallocFailed;
|
|
result = cip->errNo;
|
|
goto fatal;
|
|
}
|
|
|
|
result = GetResponse(cip, rp);
|
|
if ((result < 0) && (rp->msg.first == NULL)) {
|
|
goto fatal;
|
|
}
|
|
if (rp->msg.first != NULL) {
|
|
cip->serverType = kServerTypeUnknown;
|
|
srvr = NULL;
|
|
firstLine = rp->msg.first->line;
|
|
secondLine = NULL;
|
|
if (rp->msg.first->next != NULL)
|
|
secondLine = rp->msg.first->next->line;
|
|
|
|
if (strstr(firstLine, "Version wu-") != NULL) {
|
|
cip->serverType = kServerTypeWuFTPd;
|
|
srvr = "wu-ftpd";
|
|
} else if (strstr(firstLine, "NcFTPd") != NULL) {
|
|
cip->serverType = kServerTypeNcFTPd;
|
|
srvr = "NcFTPd Server";
|
|
} else if (STRNEQ("ProFTPD", firstLine, 7)) {
|
|
cip->serverType = kServerTypeProFTPD;
|
|
srvr = "ProFTPD";
|
|
} else if (strstr(firstLine, "Microsoft FTP Service") != NULL) {
|
|
cip->serverType = kServerTypeMicrosoftFTP;
|
|
srvr = "Microsoft FTP Service";
|
|
} else if (strstr(firstLine, "(NetWare ") != NULL) {
|
|
cip->serverType = kServerTypeNetWareFTP;
|
|
srvr = "NetWare FTP Service";
|
|
} else if (STRNEQ("WFTPD", firstLine, 5)) {
|
|
cip->serverType = kServerTypeWFTPD;
|
|
srvr = "WFTPD";
|
|
} else if (STRNEQ("Serv-U FTP", firstLine, 10)) {
|
|
cip->serverType = kServerTypeServ_U;
|
|
srvr = "Serv-U FTP-Server";
|
|
} else if (strstr(firstLine, "VFTPD") != NULL) {
|
|
cip->serverType = kServerTypeVFTPD;
|
|
srvr = "VFTPD";
|
|
} else if (STRNEQ("FTP-Max", firstLine, 7)) {
|
|
cip->serverType = kServerTypeFTP_Max;
|
|
srvr = "FTP-Max";
|
|
} else if (strstr(firstLine, "Roxen") != NULL) {
|
|
cip->serverType = kServerTypeRoxen;
|
|
srvr = "Roxen";
|
|
} else if (strstr(firstLine, "WS_FTP") != NULL) {
|
|
cip->serverType = kServerTypeWS_FTP;
|
|
srvr = "WS_FTP Server";
|
|
} else if ((secondLine != NULL) && (strstr(secondLine, "WarFTP") != NULL)) {
|
|
cip->serverType = kServerTypeWarFTPd;
|
|
srvr = "WarFTPd";
|
|
}
|
|
|
|
if (srvr != NULL)
|
|
PrintF(cip, "Remote server is running %s.\n", srvr);
|
|
|
|
/* Do the application's connect message callback, if present. */
|
|
if ((cip->onConnectMsgProc != 0) && (rp->codeType < 4))
|
|
(*cip->onConnectMsgProc)(cip, rp);
|
|
}
|
|
|
|
if (rp->codeType >= 4) {
|
|
/* They probably hung up on us right away. That's too bad,
|
|
* but we can tell the caller that they can call back later
|
|
* and try again.
|
|
*/
|
|
DoneWithResponse(cip, rp);
|
|
result = kErrConnectRetryableErr;
|
|
Error(cip, kDontPerror, "Server hungup immediately after connect.\n");
|
|
cip->errNo = kErrConnectRetryableErr;
|
|
goto fatal;
|
|
}
|
|
if (result < 0) /* Some other error occurred during connect message */
|
|
goto fatal;
|
|
cip->connected = 1;
|
|
DoneWithResponse(cip, rp);
|
|
return (kNoErr);
|
|
|
|
fatal:
|
|
if (sockfd > 0)
|
|
(void) closesocket(sockfd);
|
|
if (sock2fd > 0)
|
|
(void) closesocket(sock2fd);
|
|
CloseFile(&cip->cin);
|
|
CloseFile(&cip->cout);
|
|
cip->ctrlSocketR = kClosedFileDescriptor;
|
|
cip->ctrlSocketW = kClosedFileDescriptor;
|
|
return (result);
|
|
} /* OpenControlConnection */
|
|
|
|
|
|
|
|
|
|
void
|
|
CloseDataConnection(const FTPCIPtr cip)
|
|
{
|
|
if (cip->dataSocket != kClosedFileDescriptor) {
|
|
#ifdef NO_SIGNALS
|
|
SClose(cip->dataSocket, 3);
|
|
#else /* NO_SIGNALS */
|
|
if (cip->xferTimeout > 0)
|
|
(void) alarm(cip->xferTimeout);
|
|
(void) closesocket(cip->dataSocket);
|
|
if (cip->xferTimeout > 0)
|
|
(void) alarm(0);
|
|
#endif /* NO_SIGNALS */
|
|
cip->dataSocket = kClosedFileDescriptor;
|
|
}
|
|
memset(&cip->ourDataAddr, 0, sizeof(cip->ourDataAddr));
|
|
memset(&cip->servDataAddr, 0, sizeof(cip->servDataAddr));
|
|
} /* CloseDataConnection */
|
|
|
|
|
|
|
|
|
|
int
|
|
SetStartOffset(const FTPCIPtr cip, longest_int restartPt)
|
|
{
|
|
ResponsePtr rp;
|
|
int result;
|
|
|
|
if (restartPt != (longest_int) 0) {
|
|
rp = InitResponse();
|
|
if (rp == NULL) {
|
|
Error(cip, kDontPerror, "Malloc failed.\n");
|
|
cip->errNo = kErrMallocFailed;
|
|
return (cip->errNo);
|
|
}
|
|
|
|
/* Force reset to offset zero. */
|
|
if (restartPt == (longest_int) -1)
|
|
restartPt = (longest_int) 0;
|
|
#ifdef PRINTF_LONG_LONG
|
|
result = RCmd(cip, rp,
|
|
"REST " PRINTF_LONG_LONG,
|
|
restartPt);
|
|
#else
|
|
result = RCmd(cip, rp, "REST %ld", (long) restartPt);
|
|
#endif
|
|
|
|
if (result < 0) {
|
|
return (result);
|
|
} else if (result == 3) {
|
|
cip->hasREST = kCommandAvailable;
|
|
DoneWithResponse(cip, rp);
|
|
} else if (UNIMPLEMENTED_CMD(rp->code)) {
|
|
cip->hasREST = kCommandNotAvailable;
|
|
DoneWithResponse(cip, rp);
|
|
cip->errNo = kErrSetStartPoint;
|
|
return (kErrSetStartPoint);
|
|
} else {
|
|
DoneWithResponse(cip, rp);
|
|
cip->errNo = kErrSetStartPoint;
|
|
return (kErrSetStartPoint);
|
|
}
|
|
}
|
|
return (0);
|
|
} /* SetStartOffset */
|
|
|
|
|
|
|
|
static int
|
|
SendPort(const FTPCIPtr cip, struct sockaddr_in *saddr)
|
|
{
|
|
char *a, *p;
|
|
int result;
|
|
ResponsePtr rp;
|
|
|
|
rp = InitResponse();
|
|
if (rp == NULL) {
|
|
Error(cip, kDontPerror, "Malloc failed.\n");
|
|
cip->errNo = kErrMallocFailed;
|
|
return (cip->errNo);
|
|
}
|
|
|
|
/* These will point to data in network byte order. */
|
|
a = (char *) &saddr->sin_addr;
|
|
p = (char *) &saddr->sin_port;
|
|
#define UC(x) (int) (((int) x) & 0xff)
|
|
|
|
/* Need to tell the other side which host (the address) and
|
|
* which process (port) on that host to send data to.
|
|
*/
|
|
result = RCmd(cip, rp, "PORT %d,%d,%d,%d,%d,%d",
|
|
UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
|
|
|
|
if (result < 0) {
|
|
return (result);
|
|
} else if (result != 2) {
|
|
/* A 500'ish response code means the PORT command failed. */
|
|
DoneWithResponse(cip, rp);
|
|
cip->errNo = kErrPORTFailed;
|
|
return (cip->errNo);
|
|
}
|
|
DoneWithResponse(cip, rp);
|
|
return (kNoErr);
|
|
} /* SendPort */
|
|
|
|
|
|
|
|
|
|
static int
|
|
Passive(const FTPCIPtr cip, struct sockaddr_in *saddr, int *weird)
|
|
{
|
|
ResponsePtr rp;
|
|
int i[6], j;
|
|
unsigned char n[6];
|
|
char *cp;
|
|
int result;
|
|
|
|
rp = InitResponse();
|
|
if (rp == NULL) {
|
|
Error(cip, kDontPerror, "Malloc failed.\n");
|
|
cip->errNo = kErrMallocFailed;
|
|
return (cip->errNo);
|
|
}
|
|
|
|
result = RCmd(cip, rp, "PASV");
|
|
if (result < 0)
|
|
goto done;
|
|
|
|
if (rp->codeType != 2) {
|
|
/* Didn't understand or didn't want passive port selection. */
|
|
cip->errNo = result = kErrPASVFailed;
|
|
goto done;
|
|
}
|
|
|
|
/* The other side returns a specification in the form of
|
|
* an internet address as the first four integers (each
|
|
* integer stands for 8-bits of the real 32-bit address),
|
|
* and two more integers for the port (16-bit port).
|
|
*
|
|
* It should give us something like:
|
|
* "Entering Passive Mode (129,93,33,1,10,187)", so look for
|
|
* digits with sscanf() starting 24 characters down the string.
|
|
*/
|
|
for (cp = rp->msg.first->line; ; cp++) {
|
|
if (*cp == '\0') {
|
|
Error(cip, kDontPerror, "Cannot parse PASV response: %s\n", rp->msg.first->line);
|
|
goto done;
|
|
}
|
|
if (isdigit((int) *cp))
|
|
break;
|
|
}
|
|
|
|
if (sscanf(cp, "%d,%d,%d,%d,%d,%d",
|
|
&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6) {
|
|
Error(cip, kDontPerror, "Cannot parse PASV response: %s\n", rp->msg.first->line);
|
|
goto done;
|
|
}
|
|
|
|
for (j=0, *weird = 0; j<6; j++) {
|
|
/* Some ftp servers return bogus port octets, such as
|
|
* boombox.micro.umn.edu. Let the caller know if we got a
|
|
* weird looking octet.
|
|
*/
|
|
if ((i[j] < 0) || (i[j] > 255))
|
|
*weird = *weird + 1;
|
|
n[j] = (unsigned char) (i[j] & 0xff);
|
|
}
|
|
|
|
(void) memcpy(&saddr->sin_addr, &n[0], (size_t) 4);
|
|
(void) memcpy(&saddr->sin_port, &n[4], (size_t) 2);
|
|
|
|
result = kNoErr;
|
|
done:
|
|
DoneWithResponse(cip, rp);
|
|
return (result);
|
|
} /* Passive */
|
|
|
|
|
|
|
|
|
|
static int
|
|
BindToEphemeralPortNumber(int sockfd, struct sockaddr_in *addrp, int ephemLo, int ephemHi)
|
|
{
|
|
int i;
|
|
int result;
|
|
int rangesize;
|
|
unsigned short port;
|
|
|
|
addrp->sin_family = AF_INET;
|
|
if (((int) ephemLo == 0) || ((int) ephemLo >= (int) ephemHi)) {
|
|
/* Do it the normal way. System will
|
|
* pick one, typically in the range
|
|
* of 1024-4999.
|
|
*/
|
|
addrp->sin_port = 0; /* Let system pick one. */
|
|
|
|
result = bind(sockfd, (struct sockaddr *) addrp, sizeof(struct sockaddr_in));
|
|
} else {
|
|
rangesize = (int) ((int) ephemHi - (int) ephemLo);
|
|
result = 0;
|
|
for (i=0; i<10; i++) {
|
|
port = (unsigned short) (((int) rand() % rangesize) + (int) ephemLo);
|
|
addrp->sin_port = port;
|
|
|
|
result = bind(sockfd, (struct sockaddr *) addrp, sizeof(struct sockaddr_in));
|
|
if (result == 0)
|
|
break;
|
|
if ((errno != 999)
|
|
/* This next line is just fodder to
|
|
* shut the compiler up about variable
|
|
* not being used.
|
|
*/
|
|
&& (gCopyright[0] != '\0'))
|
|
break;
|
|
}
|
|
}
|
|
return (result);
|
|
} /* BindToEphemeralPortNumber */
|
|
|
|
|
|
|
|
|
|
int
|
|
OpenDataConnection(const FTPCIPtr cip, int mode)
|
|
{
|
|
int dataSocket;
|
|
int weirdPort;
|
|
int result;
|
|
|
|
/* Before we can transfer any data, and before we even ask the
|
|
* remote server to start transferring via RETR/NLST/etc, we have
|
|
* to setup the connection.
|
|
*/
|
|
|
|
tryPort2:
|
|
weirdPort = 0;
|
|
result = 0;
|
|
CloseDataConnection(cip); /* In case we didn't before... */
|
|
|
|
dataSocket = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (dataSocket < 0) {
|
|
Error(cip, kDoPerror, "Could not get a data socket.\n");
|
|
result = kErrNewStreamSocket;
|
|
cip->errNo = kErrNewStreamSocket;
|
|
return result;
|
|
}
|
|
|
|
/* This doesn't do anything if you left these
|
|
* at their defaults (zero). Otherwise it
|
|
* tries to set the buffer size to the
|
|
* size specified.
|
|
*/
|
|
(void) SetSockBufSize(dataSocket, cip->dataSocketRBufSize, cip->dataSocketSBufSize);
|
|
|
|
if ((cip->hasPASV == kCommandNotAvailable) || (mode == kSendPortMode)) {
|
|
tryPort:
|
|
cip->ourDataAddr = cip->ourCtlAddr;
|
|
cip->ourDataAddr.sin_family = AF_INET;
|
|
|
|
#ifdef HAVE_LIBSOCKS
|
|
cip->ourDataAddr.sin_port = 0;
|
|
if (Rbind(dataSocket, (struct sockaddr *) &cip->ourDataAddr,
|
|
(int) sizeof (cip->ourDataAddr),
|
|
cip->servCtlAddr.sin_addr.s_addr) < 0)
|
|
#else
|
|
if (BindToEphemeralPortNumber(dataSocket, &cip->ourDataAddr, (int) cip->ephemLo, (int) cip->ephemHi) < 0)
|
|
#endif
|
|
{
|
|
Error(cip, kDoPerror, "Could not bind the data socket");
|
|
result = kErrBindDataSocket;
|
|
cip->errNo = kErrBindDataSocket;
|
|
goto bad;
|
|
}
|
|
|
|
/* Need to do this so we can figure out which port the system
|
|
* gave to us.
|
|
*/
|
|
if ((result = GetSocketAddress(cip, dataSocket, &cip->ourDataAddr)) < 0)
|
|
goto bad;
|
|
|
|
if (listen(dataSocket, 1) < 0) {
|
|
Error(cip, kDoPerror, "listen failed");
|
|
result = kErrListenDataSocket;
|
|
cip->errNo = kErrListenDataSocket;
|
|
goto bad;
|
|
}
|
|
|
|
if ((result = SendPort(cip, &cip->ourDataAddr)) < 0)
|
|
goto bad;
|
|
|
|
cip->dataPortMode = kSendPortMode;
|
|
} else {
|
|
/* Passive mode. Let the other side decide where to send. */
|
|
|
|
cip->servDataAddr = cip->servCtlAddr;
|
|
cip->servDataAddr.sin_family = AF_INET;
|
|
cip->ourDataAddr = cip->ourCtlAddr;
|
|
cip->ourDataAddr.sin_family = AF_INET;
|
|
|
|
if (Passive(cip, &cip->servDataAddr, &weirdPort) < 0) {
|
|
Error(cip, kDontPerror, "Passive mode refused.\n");
|
|
cip->hasPASV = kCommandNotAvailable;
|
|
|
|
/* We can try using regular PORT commands, which are required
|
|
* by all FTP protocol compliant programs, if you said so.
|
|
*
|
|
* We don't do this automatically, because if your host
|
|
* is running a firewall you (probably) do not want SendPort
|
|
* FTP for security reasons.
|
|
*/
|
|
if (mode == kFallBackToSendPortMode)
|
|
goto tryPort;
|
|
result = kErrPassiveModeFailed;
|
|
cip->errNo = kErrPassiveModeFailed;
|
|
goto bad;
|
|
}
|
|
|
|
#ifdef HAVE_LIBSOCKS
|
|
cip->ourDataAddr.sin_port = 0;
|
|
if (Rbind(dataSocket, (struct sockaddr *) &cip->ourDataAddr,
|
|
(int) sizeof (cip->ourDataAddr),
|
|
cip->servCtlAddr.sin_addr.s_addr) < 0)
|
|
#else
|
|
if (BindToEphemeralPortNumber(dataSocket, &cip->ourDataAddr, (int) cip->ephemLo, (int) cip->ephemHi) < 0)
|
|
#endif
|
|
{
|
|
Error(cip, kDoPerror, "Could not bind the data socket");
|
|
result = kErrBindDataSocket;
|
|
cip->errNo = kErrBindDataSocket;
|
|
goto bad;
|
|
}
|
|
|
|
#ifdef NO_SIGNALS
|
|
result = SConnect(dataSocket, &cip->servDataAddr, (int) cip->connTimeout);
|
|
#else /* NO_SIGNALS */
|
|
if (cip->connTimeout > 0)
|
|
(void) alarm(cip->connTimeout);
|
|
|
|
result = connect(dataSocket, (struct sockaddr *) &cip->servDataAddr, (int) sizeof(cip->servDataAddr));
|
|
if (cip->connTimeout > 0)
|
|
(void) alarm(0);
|
|
#endif /* NO_SIGNALS */
|
|
|
|
#ifdef NO_SIGNALS
|
|
if (result == kTimeoutErr) {
|
|
if (mode == kFallBackToSendPortMode) {
|
|
Error(cip, kDontPerror, "Data connection timed out.\n");
|
|
Error(cip, kDontPerror, "Falling back to PORT instead of PASV mode.\n");
|
|
(void) closesocket(dataSocket);
|
|
cip->hasPASV = kCommandNotAvailable;
|
|
goto tryPort2;
|
|
}
|
|
Error(cip, kDontPerror, "Data connection timed out.\n");
|
|
result = kErrConnectDataSocket;
|
|
cip->errNo = kErrConnectDataSocket;
|
|
} else
|
|
#endif /* NO_SIGNALS */
|
|
|
|
if (result < 0) {
|
|
#ifdef ECONNREFUSED
|
|
if ((weirdPort > 0) && (errno == ECONNREFUSED)) {
|
|
#elif defined(WSAECONNREFUSED)
|
|
if ((weirdPort > 0) && (errno == WSAECONNREFUSED)) {
|
|
#endif
|
|
Error(cip, kDontPerror, "Server sent back a bogus port number.\nI will fall back to PORT instead of PASV mode.\n");
|
|
if (mode == kFallBackToSendPortMode) {
|
|
(void) closesocket(dataSocket);
|
|
cip->hasPASV = kCommandNotAvailable;
|
|
goto tryPort2;
|
|
}
|
|
result = kErrServerSentBogusPortNumber;
|
|
cip->errNo = kErrServerSentBogusPortNumber;
|
|
goto bad;
|
|
}
|
|
if (mode == kFallBackToSendPortMode) {
|
|
Error(cip, kDoPerror, "connect failed.\n");
|
|
Error(cip, kDontPerror, "Falling back to PORT instead of PASV mode.\n");
|
|
(void) closesocket(dataSocket);
|
|
cip->hasPASV = kCommandNotAvailable;
|
|
goto tryPort2;
|
|
}
|
|
Error(cip, kDoPerror, "connect failed.\n");
|
|
result = kErrConnectDataSocket;
|
|
cip->errNo = kErrConnectDataSocket;
|
|
goto bad;
|
|
}
|
|
|
|
/* Need to do this so we can figure out which port the system
|
|
* gave to us.
|
|
*/
|
|
if ((result = GetSocketAddress(cip, dataSocket, &cip->ourDataAddr)) < 0)
|
|
goto bad;
|
|
|
|
cip->dataPortMode = kPassiveMode;
|
|
cip->hasPASV = kCommandAvailable;
|
|
}
|
|
|
|
(void) SetLinger(cip, dataSocket, 1);
|
|
(void) SetKeepAlive(cip, dataSocket);
|
|
|
|
#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
|
|
/* Data connection is a non-interactive data stream, so
|
|
* high throughput is desired, at the expense of low
|
|
* response time.
|
|
*/
|
|
(void) SetTypeOfService(cip, dataSocket, IPTOS_THROUGHPUT);
|
|
#endif
|
|
|
|
cip->dataSocket = dataSocket;
|
|
return (0);
|
|
bad:
|
|
(void) closesocket(dataSocket);
|
|
return (result);
|
|
} /* OpenDataConnection */
|
|
|
|
|
|
|
|
|
|
int
|
|
AcceptDataConnection(const FTPCIPtr cip)
|
|
{
|
|
int newSocket;
|
|
#ifndef NO_SIGNALS
|
|
int len;
|
|
#endif
|
|
unsigned short remoteDataPort;
|
|
unsigned short remoteCtrlPort;
|
|
|
|
/* If we did a PORT, we have some things to finish up.
|
|
* If we did a PASV, we're ready to go.
|
|
*/
|
|
if (cip->dataPortMode == kSendPortMode) {
|
|
/* Accept will give us back the server's data address; at the
|
|
* moment we don't do anything with it though.
|
|
*/
|
|
memset(&cip->servDataAddr, 0, sizeof(cip->servDataAddr));
|
|
|
|
#ifdef NO_SIGNALS
|
|
newSocket = SAccept(cip->dataSocket, &cip->servDataAddr, (int) cip->connTimeout);
|
|
#else /* NO_SIGNALS */
|
|
len = (int) sizeof(cip->servDataAddr);
|
|
if (cip->connTimeout > 0)
|
|
(void) alarm(cip->connTimeout);
|
|
newSocket = accept(cip->dataSocket, (struct sockaddr *) &cip->servDataAddr, &len);
|
|
if (cip->connTimeout > 0)
|
|
(void) alarm(0);
|
|
#endif /* NO_SIGNALS */
|
|
|
|
(void) closesocket(cip->dataSocket);
|
|
if (newSocket < 0) {
|
|
Error(cip, kDoPerror, "Could not accept a data connection.\n");
|
|
cip->dataSocket = kClosedFileDescriptor;
|
|
cip->errNo = kErrAcceptDataSocket;
|
|
return (kErrAcceptDataSocket);
|
|
}
|
|
|
|
if (cip->require20 != 0) {
|
|
remoteDataPort = ntohs(cip->servDataAddr.sin_port);
|
|
remoteCtrlPort = ntohs(cip->servCtlAddr.sin_port);
|
|
if ((int) remoteDataPort != ((int) remoteCtrlPort - 1)) {
|
|
Error(cip, kDontPerror, "Data connection did not originate on correct port!\n");
|
|
(void) closesocket(newSocket);
|
|
cip->dataSocket = kClosedFileDescriptor;
|
|
cip->errNo = kErrAcceptDataSocket;
|
|
return (kErrAcceptDataSocket);
|
|
} else if (memcmp(&cip->servDataAddr.sin_addr.s_addr, &cip->servCtlAddr.sin_addr.s_addr, sizeof(cip->servDataAddr.sin_addr.s_addr)) != 0) {
|
|
Error(cip, kDontPerror, "Data connection did not originate from remote server!\n");
|
|
(void) closesocket(newSocket);
|
|
cip->dataSocket = kClosedFileDescriptor;
|
|
cip->errNo = kErrAcceptDataSocket;
|
|
return (kErrAcceptDataSocket);
|
|
}
|
|
}
|
|
|
|
cip->dataSocket = newSocket;
|
|
}
|
|
|
|
return (0);
|
|
} /* AcceptDataConnection */
|
|
|
|
|
|
|
|
|
|
void
|
|
HangupOnServer(const FTPCIPtr cip)
|
|
{
|
|
/* Since we want to close both sides of the connection for each
|
|
* socket, we can just have them closed with close() instead of
|
|
* using shutdown().
|
|
*/
|
|
CloseControlConnection(cip);
|
|
CloseDataConnection(cip);
|
|
} /* HangupOnServer */
|
|
|
|
|
|
|
|
|
|
void
|
|
SendTelnetInterrupt(const FTPCIPtr cip)
|
|
{
|
|
char msg[4];
|
|
|
|
/* 1. User system inserts the Telnet "Interrupt Process" (IP) signal
|
|
* in the Telnet stream.
|
|
*/
|
|
|
|
if (cip->cout != NULL)
|
|
(void) fflush(cip->cout);
|
|
|
|
msg[0] = (char) (unsigned char) IAC;
|
|
msg[1] = (char) (unsigned char) IP;
|
|
(void) send(cip->ctrlSocketW, msg, 2, 0);
|
|
|
|
/* 2. User system sends the Telnet "Sync" signal. */
|
|
#if 1
|
|
msg[0] = (char) (unsigned char) IAC;
|
|
msg[1] = (char) (unsigned char) DM;
|
|
if (send(cip->ctrlSocketW, msg, 2, MSG_OOB) != 2)
|
|
Error(cip, kDoPerror, "Could not send an urgent message.\n");
|
|
#else
|
|
/* "Send IAC in urgent mode instead of DM because UNIX places oob mark
|
|
* after urgent byte rather than before as now is protocol," says
|
|
* the BSD ftp code.
|
|
*/
|
|
msg[0] = (char) (unsigned char) IAC;
|
|
if (send(cip->ctrlSocketW, msg, 1, MSG_OOB) != 1)
|
|
Error(cip, kDoPerror, "Could not send an urgent message.\n");
|
|
(void) fprintf(cip->cout, "%c", DM);
|
|
(void) fflush(cip->cout);
|
|
#endif
|
|
} /* SendTelnetInterrupt */
|
|
|
|
/* eof FTP.c */
|