diff --git a/ChangeLog b/ChangeLog index a777cda4..a9b219ec 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2010-07-30 Tatsuhiro Tsujikawa + + Added FTP EPSV and EPRT command support. aria2 issues these + commands when address family of local socket is AF_INET6. + * src/FtpConnection.cc + * src/FtpConnection.h + * src/FtpNegotiationCommand.cc + * src/FtpNegotiationCommand.h + * src/SocketCore.cc + * src/SocketCore.h + * test/FtpConnectionTest.cc + 2010-07-30 Tatsuhiro Tsujikawa Fixed the bug that if hostname is numeric, diff --git a/src/FtpConnection.cc b/src/FtpConnection.cc index 8a54f935..d0b22e33 100644 --- a/src/FtpConnection.cc +++ b/src/FtpConnection.cc @@ -190,6 +190,20 @@ bool FtpConnection::sendSize() return socketBuffer_.sendBufferIsEmpty(); } +bool FtpConnection::sendEpsv() +{ + if(socketBuffer_.sendBufferIsEmpty()) { + static const std::string request("EPSV\r\n"); + if(logger_->info()) { + logger_->info(MSG_SENDING_REQUEST, + util::itos(cuid_).c_str(), request.c_str()); + } + socketBuffer_.pushStr(request); + } + socketBuffer_.send(); + return socketBuffer_.sendBufferIsEmpty(); +} + bool FtpConnection::sendPasv() { if(socketBuffer_.sendBufferIsEmpty()) { @@ -206,13 +220,41 @@ bool FtpConnection::sendPasv() SharedHandle FtpConnection::createServerSocket() { + std::pair addrinfo; + socket_->getAddrInfo(addrinfo); SharedHandle serverSocket(new SocketCore()); - serverSocket->bind(0); + serverSocket->bind(addrinfo.first, 0); serverSocket->beginListen(); serverSocket->setNonBlockingMode(); return serverSocket; } +bool FtpConnection::sendEprt(const SharedHandle& serverSocket) +{ + if(socketBuffer_.sendBufferIsEmpty()) { + struct sockaddr_storage sockaddr; + socklen_t len = sizeof(sockaddr); + serverSocket->getAddrInfo(sockaddr, len); + std::pair addrinfo = util::getNumericNameInfo + (reinterpret_cast(&sockaddr), len); + std::string request = "EPRT "; + request += "|"; + request += util::itos(sockaddr.ss_family == AF_INET?1:2); + request += "|"; + request += addrinfo.first; + request += "|"; + request += util::uitos(addrinfo.second); + request += "|\r\n"; + if(logger_->info()) { + logger_->info(MSG_SENDING_REQUEST, + util::itos(cuid_).c_str(), request.c_str()); + } + socketBuffer_.pushStr(request); + } + socketBuffer_.send(); + return socketBuffer_.sendBufferIsEmpty(); +} + bool FtpConnection::sendPort(const SharedHandle& serverSocket) { if(socketBuffer_.sendBufferIsEmpty()) { @@ -453,6 +495,34 @@ unsigned int FtpConnection::receiveMdtmResponse(Time& time) } } +unsigned int FtpConnection::receiveEpsvResponse(uint16_t& port) +{ + std::pair response; + if(bulkReceiveResponse(response)) { + if(response.first == 229) { + port = 0; + std::string::size_type leftParen = response.second.find("("); + std::string::size_type rightParen = response.second.find(")"); + if(leftParen == std::string::npos || rightParen == std::string::npos || + leftParen > rightParen) { + return response.first; + } + std::vector rd; + util::split(response.second.substr(leftParen+1, rightParen-(leftParen+1)), + std::back_inserter(rd), "|", true, true); + uint32_t portTemp = 0; + if(rd.size() == 5 && util::parseUIntNoThrow(portTemp, rd[3])) { + if(0 < portTemp && portTemp <= UINT16_MAX) { + port = portTemp; + } + } + } + return response.first; + } else { + return 0; + } +} + unsigned int FtpConnection::receivePasvResponse (std::pair& dest) { diff --git a/src/FtpConnection.h b/src/FtpConnection.h index 891b6feb..463c2af7 100644 --- a/src/FtpConnection.h +++ b/src/FtpConnection.h @@ -95,8 +95,10 @@ public: bool sendCwd(const std::string& dir); bool sendMdtm(); bool sendSize(); + bool sendEpsv(); bool sendPasv(); SharedHandle createServerSocket(); + bool sendEprt(const SharedHandle& serverSocket); bool sendPort(const SharedHandle& serverSocket); bool sendRest(const SharedHandle& segment); bool sendRetr(); @@ -110,6 +112,7 @@ public: // date cannot be parsed, then assign Time::null() to given time. // If reply is not received yet, returns 0. unsigned int receiveMdtmResponse(Time& time); + unsigned int receiveEpsvResponse(uint16_t& port); unsigned int receivePasvResponse(std::pair& dest); unsigned int receivePwdResponse(std::string& pwd); diff --git a/src/FtpNegotiationCommand.cc b/src/FtpNegotiationCommand.cc index 2c070274..89bf5211 100644 --- a/src/FtpNegotiationCommand.cc +++ b/src/FtpNegotiationCommand.cc @@ -126,9 +126,9 @@ bool FtpNegotiationCommand::executeInternal() { return true; } else if(sequence_ == SEQ_FILE_PREPARATION) { if(getOption()->getAsBool(PREF_FTP_PASV)) { - sequence_ = SEQ_SEND_PASV; + sequence_ = SEQ_PREPARE_PASV; } else { - sequence_ = SEQ_PREPARE_SERVER_SOCKET; + sequence_ = SEQ_PREPARE_PORT; } return false; } else if(sequence_ == SEQ_EXIT) { @@ -391,9 +391,9 @@ bool FtpNegotiationCommand::onFileSizeDetermined(uint64_t totalLength) if(totalLength == 0) { if(getOption()->getAsBool(PREF_FTP_PASV)) { - sequence_ = SEQ_SEND_PASV; + sequence_ = SEQ_PREPARE_PASV; } else { - sequence_ = SEQ_PREPARE_SERVER_SOCKET; + sequence_ = SEQ_PREPARE_PORT; } if(getOption()->getAsBool(PREF_DRY_RUN)) { @@ -514,9 +514,9 @@ bool FtpNegotiationCommand::recvSize() { // wrong file to be downloaded if user-specified URL is wrong. } if(getOption()->getAsBool(PREF_FTP_PASV)) { - sequence_ = SEQ_SEND_PASV; + sequence_ = SEQ_PREPARE_PASV; } else { - sequence_ = SEQ_PREPARE_SERVER_SOCKET; + sequence_ = SEQ_PREPARE_PORT; } return true; } @@ -526,6 +526,22 @@ void FtpNegotiationCommand::afterFileAllocation() setReadCheckSocket(getSocket()); } +bool FtpNegotiationCommand::preparePort() { + afterFileAllocation(); + if(getSocket()->getAddressFamily() == AF_INET6) { + sequence_ = SEQ_PREPARE_SERVER_SOCKET_EPRT; + } else { + sequence_ = SEQ_PREPARE_SERVER_SOCKET; + } + return true; +} + +bool FtpNegotiationCommand::prepareServerSocketEprt() { + serverSocket_ = ftp_->createServerSocket(); + sequence_ = SEQ_SEND_EPRT; + return true; +} + bool FtpNegotiationCommand::prepareServerSocket() { serverSocket_ = ftp_->createServerSocket(); @@ -533,8 +549,30 @@ bool FtpNegotiationCommand::prepareServerSocket() return true; } +bool FtpNegotiationCommand::sendEprt() { + if(ftp_->sendEprt(serverSocket_)) { + disableWriteCheckSocket(); + sequence_ = SEQ_RECV_EPRT; + } else { + setWriteCheckSocket(getSocket()); + } + return false; +} + +bool FtpNegotiationCommand::recvEprt() { + unsigned int status = ftp_->receiveResponse(); + if(status == 0) { + return false; + } + if(status == 200) { + sequence_ = SEQ_SEND_REST; + } else { + sequence_ = SEQ_PREPARE_SERVER_SOCKET; + } + return true; +} + bool FtpNegotiationCommand::sendPort() { - afterFileAllocation(); if(ftp_->sendPort(serverSocket_)) { disableWriteCheckSocket(); sequence_ = SEQ_RECV_PORT; @@ -556,8 +594,45 @@ bool FtpNegotiationCommand::recvPort() { return true; } -bool FtpNegotiationCommand::sendPasv() { +bool FtpNegotiationCommand::preparePasv() { afterFileAllocation(); + if(getSocket()->getAddressFamily() == AF_INET6) { + sequence_ = SEQ_SEND_EPSV; + } else { + sequence_ = SEQ_SEND_PASV; + } + return true; +} + +bool FtpNegotiationCommand::sendEpsv() { + if(ftp_->sendEpsv()) { + disableWriteCheckSocket(); + sequence_ = SEQ_RECV_EPSV; + } else { + setWriteCheckSocket(getSocket()); + } + return true; +} + +bool FtpNegotiationCommand::recvEpsv() { + uint16_t port; + unsigned int status = ftp_->receiveEpsvResponse(port); + if(status == 0) { + return false; + } + if(status == 229) { + std::pair peerInfo; + getSocket()->getPeerInfo(peerInfo); + peerInfo.second = port; + dataConnAddr_ = peerInfo; + return preparePasvConnect(); + } else { + sequence_ = SEQ_SEND_PASV; + return true; + } +} + +bool FtpNegotiationCommand::sendPasv() { if(ftp_->sendPasv()) { disableWriteCheckSocket(); sequence_ = SEQ_RECV_PASV; @@ -576,20 +651,26 @@ bool FtpNegotiationCommand::recvPasv() { if(status != 227) { throw DL_ABORT_EX(StringFormat(EX_BAD_STATUS, status).str()); } - // TODO Should we check to see that dest.first is not in noProxy list? + dataConnAddr_ = dest; + + return preparePasvConnect(); +} + +bool FtpNegotiationCommand::preparePasvConnect() { + // TODO Should we check to see that dataConnAddr_.first is not in + // noProxy list? if(isProxyDefined()) { - dataConnAddr_ = dest; sequence_ = SEQ_RESOLVE_PROXY; return true; } else { // make a data connection to the server. if(getLogger()->info()) { getLogger()->info(MSG_CONNECTING_TO_SERVER, util::itos(getCuid()).c_str(), - dest.first.c_str(), - dest.second); + dataConnAddr_.first.c_str(), + dataConnAddr_.second); } dataSocket_.reset(new SocketCore()); - dataSocket_->establishConnection(dest.first, dest.second); + dataSocket_->establishConnection(dataConnAddr_.first, dataConnAddr_.second); disableReadCheckSocket(); setWriteCheckSocket(dataSocket_); sequence_ = SEQ_SEND_REST_PASV; @@ -691,6 +772,12 @@ bool FtpNegotiationCommand::recvTunnelResponse() bool FtpNegotiationCommand::sendRestPasv(const SharedHandle& segment) { //dataSocket_->setBlockingMode(); + // Check connection is made properly + if(dataSocket_->isReadable(0)) { + std::string error = dataSocket_->getSocketError(); + throw DL_ABORT_EX + (StringFormat(MSG_ESTABLISHING_CONNECTION_FAILED, error.c_str()).str()); + } setReadCheckSocket(getSocket()); disableWriteCheckSocket(); return sendRest(segment); @@ -803,12 +890,26 @@ bool FtpNegotiationCommand::processSequence return sendSize(); case SEQ_RECV_SIZE: return recvSize(); + case SEQ_PREPARE_PORT: + return preparePort(); + case SEQ_PREPARE_SERVER_SOCKET_EPRT: + return prepareServerSocketEprt(); + case SEQ_SEND_EPRT: + return sendEprt(); + case SEQ_RECV_EPRT: + return recvEprt(); case SEQ_PREPARE_SERVER_SOCKET: return prepareServerSocket(); case SEQ_SEND_PORT: return sendPort(); case SEQ_RECV_PORT: return recvPort(); + case SEQ_PREPARE_PASV: + return preparePasv(); + case SEQ_SEND_EPSV: + return sendEpsv(); + case SEQ_RECV_EPSV: + return recvEpsv(); case SEQ_SEND_PASV: return sendPasv(); case SEQ_RECV_PASV: diff --git a/src/FtpNegotiationCommand.h b/src/FtpNegotiationCommand.h index badd14a3..bf80f33b 100644 --- a/src/FtpNegotiationCommand.h +++ b/src/FtpNegotiationCommand.h @@ -62,9 +62,16 @@ public: SEQ_RECV_MDTM, SEQ_SEND_SIZE, SEQ_RECV_SIZE, + SEQ_PREPARE_PORT, + SEQ_PREPARE_SERVER_SOCKET_EPRT, + SEQ_SEND_EPRT, + SEQ_RECV_EPRT, SEQ_PREPARE_SERVER_SOCKET, SEQ_SEND_PORT, SEQ_RECV_PORT, + SEQ_PREPARE_PASV, + SEQ_SEND_EPSV, + SEQ_RECV_EPSV, SEQ_SEND_PASV, SEQ_RECV_PASV, SEQ_RESOLVE_PROXY, @@ -100,11 +107,19 @@ private: bool recvMdtm(); bool sendSize(); bool recvSize(); + bool preparePort(); + bool prepareServerSocketEprt(); + bool sendEprt(); + bool recvEprt(); bool prepareServerSocket(); bool sendPort(); bool recvPort(); + bool preparePasv(); + bool sendEpsv(); + bool recvEpsv(); bool sendPasv(); bool recvPasv(); + bool preparePasvConnect(); bool resolveProxy(); bool sendTunnelRequest(); bool recvTunnelResponse(); diff --git a/src/SocketCore.cc b/src/SocketCore.cc index cb6602f8..7d8b076b 100644 --- a/src/SocketCore.cc +++ b/src/SocketCore.cc @@ -341,11 +341,26 @@ void SocketCore::getAddrInfo(std::pair& addrinfo) const { struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); + getAddrInfo(sockaddr, len); + addrinfo = util::getNumericNameInfo + (reinterpret_cast(&sockaddr), len); +} + +void SocketCore::getAddrInfo +(struct sockaddr_storage& sockaddr, socklen_t& len) const +{ struct sockaddr* addrp = reinterpret_cast(&sockaddr); if(getsockname(sockfd_, addrp, &len) == -1) { throw DL_ABORT_EX(StringFormat(EX_SOCKET_GET_NAME, errorMsg()).str()); } - addrinfo = util::getNumericNameInfo(addrp, len); +} + +int SocketCore::getAddressFamily() const +{ + struct sockaddr_storage sockaddr; + socklen_t len = sizeof(sockaddr); + getAddrInfo(sockaddr, len); + return sockaddr.ss_family; } void SocketCore::getPeerInfo(std::pair& peerinfo) const diff --git a/src/SocketCore.h b/src/SocketCore.h index 67585bc3..7de5a0eb 100644 --- a/src/SocketCore.h +++ b/src/SocketCore.h @@ -159,6 +159,21 @@ public: */ void getAddrInfo(std::pair& addrinfo) const; + /** + * Stores address of this socket to sockaddr. len must be + * initialized to the size of sockaddr. On success, address data is + * stored in sockaddr and actual size of address structure is stored + * in len. + */ + void getAddrInfo + (struct sockaddr_storage& sockaddr, socklen_t& len) const; + + /** + * Returns address family of this socket. + * The socket must be connected or bounded to address. + */ + int getAddressFamily() const; + /** * Stores peer's address and port to peerinfo. * @param peerinfo placeholder to store peer's address and port. diff --git a/test/FtpConnectionTest.cc b/test/FtpConnectionTest.cc index 8390c461..25c93453 100644 --- a/test/FtpConnectionTest.cc +++ b/test/FtpConnectionTest.cc @@ -32,6 +32,7 @@ class FtpConnectionTest:public CppUnit::TestFixture { CPPUNIT_TEST(testSendSize); CPPUNIT_TEST(testReceiveSizeResponse); CPPUNIT_TEST(testSendRetr); + CPPUNIT_TEST(testReceiveEpsvResponse); CPPUNIT_TEST_SUITE_END(); private: SharedHandle serverSocket_; @@ -85,6 +86,7 @@ public: void testSendSize(); void testReceiveSizeResponse(); void testSendRetr(); + void testReceiveEpsvResponse(); }; @@ -299,4 +301,33 @@ void FtpConnectionTest::testSendRetr() std::string(&data[0], &data[len])); } +void FtpConnectionTest::testReceiveEpsvResponse() +{ + serverSocket_->writeData("229 Success (|||12000|)\r\n"); + waitRead(clientSocket_); + uint16_t port = 0; + CPPUNIT_ASSERT_EQUAL((unsigned int)229, ftp_->receiveEpsvResponse(port)); + CPPUNIT_ASSERT_EQUAL((uint16_t)12000, port); + + serverSocket_->writeData("229 Success |||12000|)\r\n"); + CPPUNIT_ASSERT_EQUAL((unsigned int)229, ftp_->receiveEpsvResponse(port)); + CPPUNIT_ASSERT_EQUAL((uint16_t)0, port); + + serverSocket_->writeData("229 Success (|||12000|\r\n"); + CPPUNIT_ASSERT_EQUAL((unsigned int)229, ftp_->receiveEpsvResponse(port)); + CPPUNIT_ASSERT_EQUAL((uint16_t)0, port); + + serverSocket_->writeData("229 Success ()|||12000|\r\n"); + CPPUNIT_ASSERT_EQUAL((unsigned int)229, ftp_->receiveEpsvResponse(port)); + CPPUNIT_ASSERT_EQUAL((uint16_t)0, port); + + serverSocket_->writeData("229 Success )(|||12000|)\r\n"); + CPPUNIT_ASSERT_EQUAL((unsigned int)229, ftp_->receiveEpsvResponse(port)); + CPPUNIT_ASSERT_EQUAL((uint16_t)0, port); + + serverSocket_->writeData("229 Success )(||12000|)\r\n"); + CPPUNIT_ASSERT_EQUAL((unsigned int)229, ftp_->receiveEpsvResponse(port)); + CPPUNIT_ASSERT_EQUAL((uint16_t)0, port); +} + } // namespace aria2