/* ftp.c * * Copyright (c) 1996-2005 Mike Gleason, NcFTP Software. * All rights reserved. * */ #include "syshdrs.h" #ifdef PRAGMA_HDRSTOP # pragma hdrstop #endif const char gLibNcFTPVersion[] = kLibraryVersion; #ifdef NO_SIGNALS static const 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 const char gCopyright[] = "@(#) LibNcFTP Copyright 1995-2001, by Mike Gleason. All rights reserved."; #endif #ifdef HAVE_LIBSOCKS5 # define SOCKS 5 # include #else # ifdef HAVE_LIBSOCKS # define accept Raccept # define connect Rconnect # define getsockname Rgetsockname # define listen Rlisten # endif #endif #if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__) # define DisposeSocket(a) shutdown(a, 2); closesocket(a) #else # define DisposeSocket(a) close(a) #endif #ifndef HAVE_SOCKADDR_SA_LEN #ifndef SA_LEN #ifdef ENABLE_IPV6 #define SA_LEN(x) (((x)->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) \ : (((x)->sa_family == AF_INET) ? sizeof(struct sockaddr_in) \ : -1)) #else /* ENABLE_IPV6 */ #define SA_LEN(x) (((x)->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : -1) #endif /* ENABLE_IPV6 */ #endif /* SA_LEN */ #endif /* HAVE_SOCKADDR_SA_LEN */ static const char *gPrivateNetworks[] = { "192.168.", "10.", "172.16.", "172.17.", "172.18.", "172.19.", "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.", "172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.", NULL }; void FTPCloseControlConnection(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; } /* FTPCloseControlConnection */ static int GetSocketAddress(const FTPCIPtr cip, int sockfd, struct sockaddr *saddr) { sockaddr_size_t len = (sockaddr_size_t) sizeof (struct sockaddr_storage); int result = 0; if (getsockname(sockfd, (struct sockaddr *)saddr, &len) < 0) { FTPLogError(cip, kDoPerror, "Could not get socket name.\n"); cip->errNo = kErrGetSockName; result = kErrGetSockName; } return (result); } /* GetSocketAddress */ #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) { int err = 0; int result; int oerrno; volatile int sockfd = -1; volatile int sock2fd = -1; ResponsePtr rp = NULL; struct addrinfo hints; struct addrinfo *res, *res0; int error; char buf[NI_MAXHOST]; 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; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; sprintf(buf, "%d\0", fport); error = getaddrinfo(fhost, buf, &hints, &res0); if (error) { FTPLogError(cip, kDontPerror, "%s: %s.\n", fhost, gai_strerror(error)); cip->errNo = kErrHostUnknown; return (kErrHostUnknown); } /* 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. */ /* 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 (res = res0; res; res = res->ai_next) { (void) memcpy(&cip->servCtlAddr, res->ai_addr, (size_t)res->ai_addrlen); if ((sockfd = socket(cip->servCtlAddr.ss_family, SOCK_STREAM, 0)) < 0) { if (res0->ai_next) continue; FTPLogError(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) SetSocketBufSize(sockfd, cip->ctrlSocketRBufSize, cip->ctrlSocketSBufSize); #ifdef NO_SIGNALS err = SConnect(sockfd, (struct sockaddr *)&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) DisposeSocket(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) { if (res0->ai_next) continue; 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, #ifdef HAVE_SOCKADDR_SA_LEN cip->servCtlAddr.ss_len #else SA_LEN((struct sockaddr *)&cip->servCtlAddr) #endif ); if (cip->connTimeout > 0) { (void) alarm(0); (void) signal(SIGALRM, (FTPSigProc) osigalrm); } (void) signal(SIGINT, (FTPSigProc) osigint); } if (err == 0) break; oerrno = errno; (void) DisposeSocket(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 FTPLogError(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 FTPLogError(cip, kDoPerror, "Could not connect to %s.\n", fhost); result = cip->errNo = kErrConnectRefused; break; default: FTPLogError(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, (struct sockaddr *)&cip->ourCtlAddr)) < 0) goto fatal; /* We want Out-of-band data to appear in the regular stream, * since we can handle TELNET. */ (void) SetSocketInlineOutOfBandData(sockfd, 1); (void) SetSocketKeepAlive(sockfd, 1); (void) SetSocketLinger(sockfd, 0, 0); /* Don't need it for ctrl. */ /* Control connection is somewhat interactive, so quick response * is desired. */ (void) SetSocketTypeOfService(sockfd, IPTOS_LOWDELAY); #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; FTPLogError(cip, kDoPerror, "Could not fdopen.\n"); goto fatal; } #else /* NO_SIGNALS */ if ((sock2fd = dup(sockfd)) < 0) { result = kErrDupSocket; cip->errNo = kErrDupSocket; FTPLogError(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; FTPLogError(cip, kDoPerror, "Could not fdopen.\n"); goto fatal; } if ((cip->cout = fdopen(sock2fd, "w")) == NULL) { result = kErrFdopenW; cip->errNo = kErrFdopenW; FTPLogError(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 */ getnameinfo((struct sockaddr*)&cip->servCtlAddr, #ifdef HAVE_SOCKADDR_SA_LEN cip->servCtlAddr.ss_len, #else SA_LEN((struct sockaddr *)&cip->servCtlAddr), #endif buf, sizeof(buf), NULL, 0, NI_NUMERICHOST); (void) STRNCPY(cip->ip, buf); if ((res0 == NULL) || (res0->ai_canonname == NULL)) (void) STRNCPY(cip->actualHost, fhost); else (void) STRNCPY(cip->actualHost, (char *) res0->ai_canonname); freeaddrinfo(res0); res0 = NULL; /* Read the startup message from the server. */ rp = InitResponse(); if (rp == NULL) { FTPLogError(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 (strstr(firstLine, "(DG/UX ") != NULL) { cip->serverType = kServerTypeDguxFTP; srvr = "DG/UX FTP Service"; } else if (strstr(firstLine, "IBM FTP CS ") != NULL) { cip->serverType = kServerTypeIBMFTPCS; srvr = "IBM FTP CS Server"; } else if (strstr(firstLine, "DC/OSx") != NULL) { cip->serverType = kServerTypePyramid; srvr = "Pyramid DC/OSx 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. */ result = cip->errNo = kErrConnectRetryableErr; FTPLogError(cip, kDontPerror, "Server hungup immediately after connect.\n"); goto fatal; } if (result < 0) /* Some other error occurred during connect message */ goto fatal; cip->connected = 1; DoneWithResponse(cip, rp); return (kNoErr); fatal: if (rp != NULL) DoneWithResponse(cip, rp); if (sockfd > 0) (void) DisposeSocket(sockfd); if (sock2fd > 0) (void) DisposeSocket(sock2fd); CloseFile(&cip->cin); CloseFile(&cip->cout); cip->ctrlSocketR = kClosedFileDescriptor; cip->ctrlSocketW = kClosedFileDescriptor; if (res0) { freeaddrinfo(res0); res0 = NULL; } return (result); } /* OpenControlConnection */ void CloseDataConnection(const FTPCIPtr cip) { if (cip->dataSocket != kClosedFileDescriptor) { #ifdef NO_SIGNALS #if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__) if (cip->dataSocketConnected != 0) { /* This could block if we were uploading, but only if * linger mode was set. */ SClose(cip->dataSocket, cip->xferTimeout); } else { DisposeSocket(cip->dataSocket); } #else /* This could block, but only if * linger mode was set. */ DisposeSocket(cip->dataSocket); #endif #else /* NO_SIGNALS */ if (cip->xferTimeout > 0) (void) alarm(cip->xferTimeout); (void) DisposeSocket(cip->dataSocket); if (cip->xferTimeout > 0) (void) alarm(0); #endif /* NO_SIGNALS */ cip->dataSocket = kClosedFileDescriptor; } cip->dataSocketConnected = 0; memset(&cip->ourDataAddr, 0, sizeof(cip->ourDataAddr)); memset(&cip->servDataAddr, 0, sizeof(cip->servDataAddr)); } /* CloseDataConnection */ int FTPSetStartOffset(const FTPCIPtr cip, longest_int restartPt) { ResponsePtr rp; int result; if (restartPt != (longest_int) 0) { rp = InitResponse(); if (rp == NULL) { FTPLogError(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) { DoneWithResponse(cip, rp); return (result); } else if (result == 3) { /* Success */ cip->hasREST = kCommandAvailable; } else if (FTP_UNIMPLEMENTED_CMD(rp->code)) { cip->hasREST = kCommandNotAvailable; DoneWithResponse(cip, rp); cip->errNo = kErrSetStartPoint; return (kErrSetStartPoint); } else { DoneWithResponse(cip, rp); cip->errNo = kErrSetStartPoint; return (kErrSetStartPoint); } DoneWithResponse(cip, rp); } return (0); } /* FTPSetStartOffset */ int FTPSendPort(const FTPCIPtr cip, struct sockaddr *saddr) { char *a, *p; int result; ResponsePtr rp; struct sockaddr_in *saddr4; #ifdef ENABLE_IPV6 struct sockaddr_in6 *saddr6; #endif rp = InitResponse(); if (rp == NULL) { FTPLogError(cip, kDontPerror, "Malloc failed.\n"); cip->errNo = kErrMallocFailed; return (cip->errNo); } #define UC(x) (int) (((int) x) & 0xff) #ifdef ENABLE_IPV6 if (saddr->sa_family == AF_INET6) { char host[NI_MAXHOST]; char port[NI_MAXSERV]; getnameinfo(saddr, sizeof(struct sockaddr_in6), host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); saddr6 = (struct sockaddr_in6 *)saddr; /* These will point to data in network byte order. */ a = (char *) &saddr6->sin6_addr; p = (char *) &saddr6->sin6_port; /* 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, "EPRT |2|%s|%s|", host, port); if (result != 2) { result = RCmd(cip, rp, "LPRT 6,16,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,2,%d,%d", UC(a[ 0]), UC(a[ 1]), UC(a[ 2]), UC(a[ 3]), UC(a[ 4]), UC(a[ 5]), UC(a[ 6]), UC(a[ 7]), UC(a[ 8]), UC(a[ 9]), UC(a[10]), UC(a[11]), UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]), UC(p[0]), UC(p[1])); } } else { #endif saddr4 = (struct sockaddr_in *)saddr; /* These will point to data in network byte order. */ a = (char *) &saddr4->sin_addr; p = (char *) &saddr4->sin_port; /* 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])); #ifdef ENABLE_IPV6 } #endif DoneWithResponse(cip, rp); if (result < 0) { return (result); } else if (result != 2) { /* A 500'ish response code means the PORT command failed. */ cip->errNo = kErrPORTFailed; return (cip->errNo); } return (kNoErr); } /* FTPSendPort */ int FTPSendPassive(const FTPCIPtr cip, struct sockaddr *saddr, int *weird) { ResponsePtr rp; int i[18], j; unsigned char n[18]; char *cp; int result; struct sockaddr_in *saddr4 = (struct sockaddr_in *)saddr; #ifdef ENABLE_IPV6 struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)saddr; #endif unsigned short port; rp = InitResponse(); if (rp == NULL) { FTPLogError(cip, kDontPerror, "Malloc failed.\n"); cip->errNo = kErrMallocFailed; return (cip->errNo); } result = RCmd(cip, rp, "EPSV"); if (result != 2) goto next; if (rp->codeType != 2) goto next; for (cp = rp->msg.first->line; ; cp++) { if (*cp == '\0') { FTPLogError(cip, kDontPerror, "Cannot parse EPSV response: %s\n", rp->msg.first->line); goto next; } if (isdigit((int) *cp)) break; } if (sscanf(cp, "%hd|", &port) != 1) { FTPLogError(cip, kDontPerror, "Cannot parse EPSV response: %s\n", rp->msg.first->line); goto next; } #ifdef ENABLE_IPV6 if (saddr->sa_family == AF_INET6) { saddr6->sin6_addr = ((struct sockaddr_in6 *)&cip->servCtlAddr)->sin6_addr; saddr6->sin6_port = htons(port); } else { #endif saddr4->sin_addr = ((struct sockaddr_in *)&cip->servCtlAddr)->sin_addr; saddr4->sin_port = htons(port); #ifdef ENABLE_IPV6 } #endif goto done; next: rp = InitResponse(); if (rp == NULL) { FTPLogError(cip, kDontPerror, "Malloc failed.\n"); cip->errNo = kErrMallocFailed; return (cip->errNo); } #ifdef ENABLE_IPV6 if (saddr->sa_family == AF_INET6) { int af, hal, pal; result = RCmd(cip, rp, "LPSV"); 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') { FTPLogError(cip, kDontPerror, "Cannot parse LPSV response: %s\n", rp->msg.first->line); goto done; } if (isdigit((int) *cp)) break; } if (sscanf(cp, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", &af, &hal, &i[ 0], &i[ 1], &i[ 2], &i[ 3], &i[ 4], &i[ 5], &i[ 6], &i[ 7], &i[ 8], &i[ 9], &i[10], &i[11], &i[12], &i[13], &i[14], &i[15], &pal, &i[16], &i[17]) != 21) { FTPLogError(cip, kDontPerror, "Cannot parse LPSV response: %s\n", rp->msg.first->line); goto done; } if (af != 6 || hal != 16 || pal != 2) { FTPLogError(cip, kDontPerror, "Cannot parse LPSV response: %s\n", rp->msg.first->line); goto done; } for (j=0, *weird = 0; j<18; 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(&saddr6->sin6_addr, &n[ 0], (size_t) 16); (void) memcpy(&saddr6->sin6_port, &n[16], (size_t) 2); } else { #endif 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') { FTPLogError(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) { FTPLogError(cip, kDontPerror, "Cannot parse PASV response: %s\n", rp->msg.first->line); goto done; } if (weird != (int *) 0) *weird = 0; for (j=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 != (int *) 0)) *weird = *weird + 1; n[j] = (unsigned char) (i[j] & 0xff); } (void) memcpy(&saddr4->sin_addr, &n[0], (size_t) 4); (void) memcpy(&saddr4->sin_port, &n[4], (size_t) 2); #ifdef ENABLE_IPV6 } #endif result = kNoErr; done: DoneWithResponse(cip, rp); return (result); } /* FTPSendPassive */ int FTPFixPrivateAddr(struct sockaddr_in *maybePrivateAddr, struct sockaddr_in *knownNonPrivateAddrToUseIfNeeded) { int i; char maybePrivateAddrStr[64]; char knownNonPrivateAddrToUseIfNeededStr[64]; AddrToAddrStr(maybePrivateAddrStr, sizeof(maybePrivateAddrStr), (struct sockaddr *)maybePrivateAddr, 0, "%h"); AddrToAddrStr(knownNonPrivateAddrToUseIfNeededStr, sizeof(knownNonPrivateAddrToUseIfNeededStr), (struct sockaddr *)knownNonPrivateAddrToUseIfNeeded, 0, "%h"); if (strcmp(maybePrivateAddrStr, knownNonPrivateAddrToUseIfNeededStr) == 0) return (0); /* Assume if we could reach the Ctl, we can reach Data. */ for (i=0; gPrivateNetworks[i] != NULL; i++) { if (strncmp(maybePrivateAddrStr, gPrivateNetworks[i], strlen(gPrivateNetworks[i])) == 0) break; } if (gPrivateNetworks[i] == NULL) return (0); /* It wasn't a private network. */ if (strncmp(knownNonPrivateAddrToUseIfNeededStr, gPrivateNetworks[i], strlen(gPrivateNetworks[i])) == 0) return (0); /* Assume we might be able to reach slightly different net */ memcpy(&maybePrivateAddr->sin_addr, &knownNonPrivateAddrToUseIfNeeded->sin_addr, sizeof(maybePrivateAddr->sin_addr)); return (1); } /* FTPFixPrivateAddr */ void FTPFixServerDataAddr(const FTPCIPtr cip) { struct sockaddr_in oldServDataAddr; char servDataAddrStr[64]; char newDataAddrStr[64]; memcpy(&oldServDataAddr, (struct sockaddr_in *)&cip->servDataAddr, sizeof(oldServDataAddr)); if (FTPFixPrivateAddr((struct sockaddr_in *)&cip->servDataAddr, (struct sockaddr_in *)&cip->servCtlAddr)) { AddrToAddrStr(servDataAddrStr, sizeof(servDataAddrStr), (struct sockaddr *)&oldServDataAddr, 0, NULL); AddrToAddrStr(newDataAddrStr, sizeof(newDataAddrStr), (struct sockaddr *)&cip->servDataAddr, 0, NULL); PrintF(cip, "Fixing bogus PASV data address from %s to %s.\n", servDataAddrStr, newDataAddrStr); } } /* FTPFixServerDataAddr */ void FTPFixClientDataAddr(const FTPCIPtr cip) { struct sockaddr_in oldClientDataAddr, newClientDataAddr; char ourDataAddrStr[64]; char newDataAddrStr[64]; if (cip->clientKnownExternalAddr.sin_family == 0) return; memcpy(&oldClientDataAddr, &cip->ourDataAddr, sizeof(oldClientDataAddr)); if (FTPFixPrivateAddr((struct sockaddr_in *)&cip->ourDataAddr, (struct sockaddr_in *)&cip->clientKnownExternalAddr)) { memcpy(&newClientDataAddr, &cip->clientKnownExternalAddr, sizeof(newClientDataAddr)); newClientDataAddr.sin_port = ((struct sockaddr_in *)&cip->ourDataAddr)->sin_port; AddrToAddrStr(ourDataAddrStr, sizeof(ourDataAddrStr), (struct sockaddr *)&oldClientDataAddr, 0, NULL); AddrToAddrStr(newDataAddrStr, sizeof(newDataAddrStr), (struct sockaddr *)&newClientDataAddr, 0, NULL); PrintF(cip, "Fixing what would have been a bogus PORT data address from %s to %s.\n", ourDataAddrStr, newDataAddrStr); } } /* FTPFixClientDataAddr */ static int BindToEphemeralPortNumber(int sockfd, struct sockaddr *addrp, int ephemLo, int ephemHi) { int i; int result; int rangesize; unsigned short port; if (((int) ephemLo == 0) || ((int) ephemLo >= (int) ephemHi)) { /* Do it the normal way. System will * pick one, typically in the range * of 1024-4999. */ #ifdef ENABLE_IPV6 if (addrp->sa_family == AF_INET6) ((struct sockaddr_in6 *)addrp)->sin6_port = 0; /* Let system pick one. */ else #endif ((struct sockaddr_in *)addrp)->sin_port = 0; /* Let system pick one. */ #ifdef HAVE_SOCKADDR_SA_LEN result = bind(sockfd, addrp, addrp->sa_len); #else result = bind(sockfd, addrp, SA_LEN(addrp)); #endif } else { rangesize = (int) ((int) ephemHi - (int) ephemLo); result = 0; for (i=0; i<10; i++) { port = (unsigned short) (((int) rand() % rangesize) + (int) ephemLo); #ifdef ENABLE_IPV6 if (addrp->sa_family == AF_INET6) ((struct sockaddr_in6 *)addrp)->sin6_port = htons(port); else #endif ((struct sockaddr_in *)addrp)->sin_port = htons(port); #ifdef HAVE_SOCKADDR_SA_LEN result = bind(sockfd, addrp, addrp->sa_len); #else result = bind(sockfd, addrp, SA_LEN(addrp)); #endif 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; int setsbufs; size_t rbs, sbs; /* 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... */ #ifdef ENABLE_IPV6 dataSocket = socket(cip->ourCtlAddr.ss_family, SOCK_STREAM, 0); #else dataSocket = socket(AF_INET, SOCK_STREAM, 0); #endif if (dataSocket < 0) { FTPLogError(cip, kDoPerror, "Could not get a data socket.\n"); result = kErrNewStreamSocket; cip->errNo = kErrNewStreamSocket; return result; } if ((cip->dataSocketRBufSize != 0) || (cip->dataSocketSBufSize != 0)) { (void) SetSocketBufSize(dataSocket, cip->dataSocketRBufSize, cip->dataSocketSBufSize); } else if (GetSocketBufSize(dataSocket, &rbs, &sbs) == 0) { /* Use maximum size buffers that qualify for TCP Small Windows * for file transfers. */ setsbufs = 0; if (rbs < 65536) { rbs = 65536; setsbufs++; } if (sbs < 65536) { sbs = 65536; setsbufs++; } if (setsbufs > 0) { (void) SetSocketBufSize(dataSocket, rbs, sbs); } } if ((cip->hasPASV == kCommandNotAvailable) || (mode == kSendPortMode)) { tryPort: cip->ourDataAddr = cip->ourCtlAddr; #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, (struct sockaddr *)&cip->ourDataAddr, (int) cip->ephemLo, (int) cip->ephemHi) < 0) #endif { FTPLogError(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, (struct sockaddr *)&cip->ourDataAddr)) < 0) goto bad; if (listen(dataSocket, 1) < 0) { FTPLogError(cip, kDoPerror, "listen failed"); result = kErrListenDataSocket; cip->errNo = kErrListenDataSocket; goto bad; } if (cip->ourDataAddr.ss_family == AF_INET) FTPFixClientDataAddr(cip); if ((result = FTPSendPort(cip, (struct sockaddr *)&cip->ourDataAddr)) < 0) goto bad; cip->dataPortMode = kSendPortMode; } else { /* Passive mode. Let the other side decide where to send. */ cip->servDataAddr = cip->servCtlAddr; cip->ourDataAddr = cip->ourCtlAddr; if (FTPSendPassive(cip, (struct sockaddr *)&cip->servDataAddr, &weirdPort) < 0) { FTPLogError(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; } if (cip->servDataAddr.ss_family == AF_INET) FTPFixServerDataAddr(cip); #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, (struct sockaddr *)&cip->ourDataAddr, (int) cip->ephemLo, (int) cip->ephemHi) < 0) #endif { FTPLogError(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, (struct sockaddr *)&cip->ourDataAddr)) < 0) goto bad; #ifdef NO_SIGNALS result = SConnect(dataSocket, (struct sockaddr *)&cip->servDataAddr, (int) cip->connTimeout); #else /* NO_SIGNALS */ if (cip->connTimeout > 0) (void) alarm(cip->connTimeout); result = connect(dataSocket, (struct sockaddr *) &cip->servDataAddr, #ifdef HAVE_SOCKADDR_SA_LEN cip->servDataAddr.ss_len #else SA_LEN((struct sockaddr*)&cip->servDataAddr) #endif ); if (cip->connTimeout > 0) (void) alarm(0); #endif /* NO_SIGNALS */ #ifdef NO_SIGNALS if (result == kTimeoutErr) { if (mode == kFallBackToSendPortMode) { FTPLogError(cip, kDontPerror, "Data connection timed out.\n"); FTPLogError(cip, kDontPerror, "Falling back to PORT instead of PASV mode.\n"); (void) DisposeSocket(dataSocket); cip->hasPASV = kCommandNotAvailable; goto tryPort2; } FTPLogError(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 FTPLogError(cip, kDontPerror, "Server sent back a bogus port number.\nI will fall back to PORT instead of PASV mode.\n"); if (mode == kFallBackToSendPortMode) { (void) DisposeSocket(dataSocket); cip->hasPASV = kCommandNotAvailable; goto tryPort2; } result = kErrServerSentBogusPortNumber; cip->errNo = kErrServerSentBogusPortNumber; goto bad; } if (mode == kFallBackToSendPortMode) { FTPLogError(cip, kDoPerror, "connect failed.\n"); FTPLogError(cip, kDontPerror, "Falling back to PORT instead of PASV mode.\n"); (void) DisposeSocket(dataSocket); cip->hasPASV = kCommandNotAvailable; goto tryPort2; } FTPLogError(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, (struct sockaddr *) &cip->ourDataAddr)) < 0) goto bad; cip->dataPortMode = kPassiveMode; cip->hasPASV = kCommandAvailable; } (void) SetSocketKeepAlive(dataSocket, 1); /* Data connection is a non-interactive data stream, so * high throughput is desired, at the expense of low * response time. */ (void) SetSocketTypeOfService(dataSocket, IPTOS_THROUGHPUT); cip->dataSocket = dataSocket; return (0); bad: (void) DisposeSocket(dataSocket); return (result); } /* OpenDataConnection */ int AcceptDataConnection(const FTPCIPtr cip) { int newSocket; #ifndef NO_SIGNALS int len; #endif unsigned short remoteDataPort; unsigned short remoteCtrlPort; char ctrlAddrStr[64], dataAddrStr[64]; /* 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, (struct sockaddr *)&cip->servDataAddr, (int) cip->connTimeout); #else /* NO_SIGNALS */ len = (int) sizeof(struct sockaddr_storage); 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) DisposeSocket(cip->dataSocket); if (newSocket < 0) { FTPLogError(cip, kDoPerror, "Could not accept a data connection.\n"); cip->dataSocket = kClosedFileDescriptor; cip->errNo = kErrAcceptDataSocket; return (kErrAcceptDataSocket); } if (cip->allowProxyForPORT == 0) { AddrToAddrStr(ctrlAddrStr, sizeof(ctrlAddrStr), (struct sockaddr *)&cip->servCtlAddr, 0, "%h"); AddrToAddrStr(dataAddrStr, sizeof(dataAddrStr), (struct sockaddr *)&cip->servDataAddr, 0, "%h"); if (strcmp(ctrlAddrStr, dataAddrStr) != 0) { FTPLogError(cip, kDontPerror, "Data connection from %s did not originate from remote server %s!\n", dataAddrStr, ctrlAddrStr); (void) DisposeSocket(newSocket); cip->dataSocket = kClosedFileDescriptor; cip->errNo = kErrProxyDataConnectionsDisabled; return (kErrProxyDataConnectionsDisabled); } } if (cip->require20 != 0) { #ifdef ENABLE_IPV6 if (cip->servDataAddr.ss_family == AF_INET6) { struct sockaddr_in6 *servDataAddr = (struct sockaddr_in6 *) &cip->servDataAddr; struct sockaddr_in6 *servCtlAddr = (struct sockaddr_in6 *) &cip->servCtlAddr; remoteDataPort = ntohs(servDataAddr->sin6_port); remoteCtrlPort = ntohs(servCtlAddr->sin6_port); if ((int) remoteDataPort != ((int) remoteCtrlPort - 1)) { FTPLogError(cip, kDontPerror, "Data connection did not originate on correct port!\n"); (void) DisposeSocket(newSocket); cip->dataSocket = kClosedFileDescriptor; cip->errNo = kErrAcceptDataSocket; return (kErrAcceptDataSocket); } else if (memcmp(&servDataAddr->sin6_addr, &servCtlAddr->sin6_addr, sizeof(servDataAddr->sin6_addr)) != 0) { FTPLogError(cip, kDontPerror, "Data connection did not originate from remote server!\n"); (void) DisposeSocket(newSocket); cip->dataSocket = kClosedFileDescriptor; cip->errNo = kErrAcceptDataSocket; return (kErrAcceptDataSocket); } } else { #endif struct sockaddr_in *servDataAddr = (struct sockaddr_in *) &cip->servDataAddr; struct sockaddr_in *servCtlAddr = (struct sockaddr_in *) &cip->servCtlAddr; remoteDataPort = ntohs(servDataAddr->sin_port); remoteCtrlPort = ntohs(servCtlAddr->sin_port); if ((int) remoteDataPort != ((int) remoteCtrlPort - 1)) { FTPLogError(cip, kDontPerror, "Data connection did not originate on correct port!\n"); (void) DisposeSocket(newSocket); cip->dataSocket = kClosedFileDescriptor; cip->errNo = kErrAcceptDataSocket; return (kErrAcceptDataSocket); } else if (memcmp(&servDataAddr->sin_addr.s_addr, &servCtlAddr->sin_addr.s_addr, sizeof(servDataAddr->sin_addr.s_addr)) != 0) { FTPLogError(cip, kDontPerror, "Data connection did not originate on correct port (expecting %d, got %d)!\n", (int) remoteCtrlPort - 1, (int) remoteDataPort); (void) DisposeSocket(newSocket); cip->errNo = kErrDataConnOriginatedFromBadPort; cip->errNo = kErrAcceptDataSocket; return (kErrDataConnOriginatedFromBadPort); } #ifdef ENABLE_IPV6 } #endif } 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(). */ FTPCloseControlConnection(cip); CloseDataConnection(cip); } /* HangupOnServer */ void SendTelnetInterrupt(const FTPCIPtr cip) { unsigned 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] = (unsigned char) IAC; msg[1] = (unsigned char) IP; #if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__) (void) send(cip->ctrlSocketW, (const char *) msg, 2, 0); #else (void) send(cip->ctrlSocketW, (const char *) msg, 2, 0); #endif /* 2. User system sends the Telnet "Sync" signal. */ msg[0] = (unsigned char) IAC; msg[1] = (unsigned char) DM; #if (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__) if (send(cip->ctrlSocketW, (const char *) msg, 2, MSG_OOB) != 2) #else if (send(cip->ctrlSocketW, (const char *) msg, 2, MSG_OOB) != 2) #endif FTPLogError(cip, kDoPerror, "Could not send an urgent message.\n"); } /* SendTelnetInterrupt */ /* eof FTP.c */