#include "ace/INet/FTP_ClientRequestHandler.h" #if !defined (__ACE_INLINE__) #include "ace/INet/FTP_ClientRequestHandler.inl" #endif #include "ace/INet/INet_Log.h" #include "ace/INet/String_IOStream.h" #include "ace/Auto_Ptr.h" #include "ace/OS_NS_ctype.h" #include "ace/Connector.h" #include "ace/Acceptor.h" #include "ace/SOCK_Acceptor.h" ACE_BEGIN_VERSIONED_NAMESPACE_DECL namespace ACE { namespace FTP { ClientRequestHandler::SessionHolder::SessionHolder () : session_ () { } ClientRequestHandler::SessionHolder::~SessionHolder() { } ClientRequestHandler::SessionFactory::SessionFactory () { } ClientRequestHandler::SessionFactory::~SessionFactory () { } ACE::INet::ConnectionHolder* ClientRequestHandler::SessionFactory::create_connection ( const ACE::INet::ConnectionKey& key) const { INET_TRACE ("FTP::ClientRequestHandler::SessionFactory::create_connection"); const INetConnectionKey& ikey = dynamic_cast (key); SessionHolder* session_holder = 0; ACE_NEW_RETURN (session_holder, SessionHolder (), 0); ACE_Auto_Ptr session_safe_ref (session_holder); (*session_holder)->set_host (ikey.host (), ikey.port ()); if ((*session_holder)->connect (true)) { return session_safe_ref.release (); } return 0; } ClientRequestHandler::Authentication::Authentication ( const ACE_CString& realm, ACE_CString& user, ACE_CString& pw) : ACE::INet::AuthenticationBase (), realm_ (realm), user_ (user), password_ (pw) { } ClientRequestHandler::Authentication::~Authentication () {} const ACE_CString& ClientRequestHandler::Authentication::scheme () const { return URL::protocol (); } const ACE_CString& ClientRequestHandler::Authentication::realm () const { return this->realm_; } const ACE_CString& ClientRequestHandler::Authentication::user () const { return this->user_; } void ClientRequestHandler::Authentication::user (const ACE_CString& usr) { this->user_ = usr; } const ACE_CString& ClientRequestHandler::Authentication::password () const { return this->password_; } void ClientRequestHandler::Authentication::password (const ACE_CString& pw) { this->password_ = pw; } const ACE_CString ClientRequestHandler::anonymous_user_ = "anonymous"; const ACE_CString ClientRequestHandler::empty_; ClientRequestHandler::ClientRequestHandler (bool passive_mode) : session_ (0), use_passive_mode_ (passive_mode), active_port_ (0), out_data_stream_ (0), in_data_stream_ (0), transfer_active_ (false) { this->out_data_stream_.set_interceptor (*this); this->in_data_stream_.set_interceptor (*this); } ClientRequestHandler::~ClientRequestHandler () { this->release_connection (); } bool ClientRequestHandler::is_response_ok () const { return this->response_.is_completed_ok () || (this->response_.is_preliminary_ok () && !this->in_data_stream_.bad ()); } std::istream& ClientRequestHandler::handle_open_request ( const ACE::INet::URL_Base& url) { const URL& ftp_url = dynamic_cast (url); return this->handle_get_request (ftp_url); } std::istream& ClientRequestHandler::handle_get_request ( const URL& ftp_url, bool binary) { if (this->initialize_connection (ftp_url.get_host (), ftp_url.get_port ())) { bool rc = true; if (this->session ()->is_new_connection () || (!ftp_url.get_user_info ().empty () && ftp_url.get_user_info () != this->current_user_)) { if (!this->session ()->is_new_connection ()) rc = this->logout (); if (rc) { ACE_CString user = ftp_url.get_user_info ().empty () ? anonymous_user_ : ftp_url.get_user_info (); ACE_CString password = user; rc = this->handle_credentials (ftp_url, user, password); if (rc) rc = this->login (user, password); } } if (rc) rc = this->finish_transfer (); if (rc) { stream_type* data_stream = this->start_download (ftp_url.get_path (), binary); if (data_stream) { // no need to check prev stream since we just called finish_transfer // to clean everything up this->in_data_stream_.set_stream (data_stream); } else { rc = false; } } if (!rc) { this->close_connection (); this->handle_request_error(ftp_url); } } else { this->handle_connection_error (ftp_url); } return this->response_stream (); } void ClientRequestHandler::on_eof () { this->finish_transfer (); } bool ClientRequestHandler::initialize_connection (const ACE_CString& host, u_short port) { static const SessionFactory session_factory; ACE::INet::ConnectionHolder* pch = 0; if (this->connection_cache ().claim_connection (INetConnectionKey (host, port), pch, session_factory)) { this->session (dynamic_cast (pch)); return true; } else return false; } bool ClientRequestHandler::handle_credentials (const URL& url, ACE_CString& user, ACE_CString& password) { Authentication authentication (url.get_host(), user, password); return URL::authenticate (authentication); } void ClientRequestHandler::handle_request_error (const URL& /*url*/) { } void ClientRequestHandler::handle_connection_error (const URL& /*url*/) { } void ClientRequestHandler::release_connection () { if (this->session_) { this->connection_cache ().release_connection ( INetConnectionKey (this->session ()->get_host (), this->session ()->get_port ()), this->session_); this->session_ = 0; } } void ClientRequestHandler::close_connection () { if (this->session_) { this->connection_cache ().close_connection ( INetConnectionKey (this->session ()->get_host (), this->session ()->get_port ()), this->session_); this->session_ = 0; } } void ClientRequestHandler::quit_connection () { this->logout (); this->close_connection (); } bool ClientRequestHandler::logout () { bool rc = true; if (this->session ()->is_connected ()) { try { this->finish_transfer (); } catch (...) { } this->process_command (Request::FTP_QUIT); rc = this->response_.is_completed_ok (); this->current_user_ = anonymous_user_; this->session ()->close (); } return rc; } bool ClientRequestHandler::login (const ACE_CString& user, const ACE_CString& password) { // read startup message this->session ()->receive_response (this->response_); if (this->response_.is_completed_ok ()) { // send user name this->process_command (Request::FTP_USER, user); if (this->response_.is_intermediate_ok ()) { this->process_command (Request::FTP_PASS, password); } } return this->response_.is_completed_ok (); } bool ClientRequestHandler::is_valid_path (const ACE_CString& path) { return this->process_command (Request::FTP_STAT, path) == Response::COMPLETED_OK; } bool ClientRequestHandler::is_file (const ACE_CString& path) { return (this->is_valid_path (path) && this->process_command (Request::FTP_CWD, path) != Response::COMPLETED_OK); } bool ClientRequestHandler::is_dir (const ACE_CString& path) { return (this->is_valid_path (path) && this->process_command (Request::FTP_CWD, path) == Response::COMPLETED_OK); } bool ClientRequestHandler::set_filetype (bool binary) { return this->process_command (Request::FTP_TYPE, binary ? "I" : "A") == Response::COMPLETED_OK; } ClientRequestHandler::stream_type* ClientRequestHandler::start_download (const ACE_CString& path, bool binary) { if (path.empty () || this->is_dir (path)) { if (this->set_filetype (false)) { return this->open_data_connection (Request::FTP_LIST, path); } } else { if (this->set_filetype (binary)) { return this->open_data_connection (Request::FTP_RETR, path); } } return 0; } ClientRequestHandler::stream_type* ClientRequestHandler::start_upload (const ACE_CString& path, bool binary) { if (this->set_filetype (binary)) { return this->open_data_connection (Request::FTP_STOR, path); } return 0; } bool ClientRequestHandler::finish_transfer () { if (transfer_active_) { stream_type* old_stream = dynamic_cast (this->in_data_stream_.set_stream (0)); if (old_stream) { // decrease ref count to 1 const_cast (old_stream->stream ()).remove_reference (); // also removes last ref count on stream handler delete old_stream; } old_stream = dynamic_cast (this->out_data_stream_.set_stream (0)); { // decrease ref count to 1 const_cast (old_stream->stream ()).remove_reference (); // also removes last ref count on stream handler delete old_stream; } this->transfer_active_ = false; this->session ()->receive_response (this->response_); return this->response_.is_completed_ok (); } return true; } bool ClientRequestHandler::abort_transfer () { if (transfer_active_) { this->session ()->send_interrupt (); this->process_command (Request::FTP_ABOR); if (this->response_.status () == 426) this->session ()->receive_response (this->response_); stream_type* old_stream = dynamic_cast (this->in_data_stream_.set_stream (0)); { // decrease ref count to 1 const_cast (old_stream->stream ()).remove_reference (); // also removes last ref count on stream handler delete old_stream; } old_stream = dynamic_cast (this->out_data_stream_.set_stream (0)); { // decrease ref count to 1 const_cast (old_stream->stream ()).remove_reference (); // also removes last ref count on stream handler delete old_stream; } this->transfer_active_ = false; return this->response_.is_completed_ok (); } return true; } Response::StatusType ClientRequestHandler::process_command (const ACE_CString& cmd, const ACE_CString& arg) { if (this->session ()->send_request (this->request_(cmd) << arg)) { this->session ()->receive_response (this->response_); } else { this->response_(0); } return this->response_.status_type (); } ClientRequestHandler::stream_type* ClientRequestHandler::open_data_connection (const ACE_CString& cmd, const ACE_CString& arg) { if (this->use_passive_mode_) { // get address for passive data connection ACE_INET_Addr data_addr; if (this->get_passive_address (data_addr)) { // establish data connection // copy sync settings from session unsigned long f_reactor = this->session ()->is_reactive() ? ACE_Synch_Options::USE_REACTOR : 0; ACE_Synch_Options sync_opt (ACE_Synch_Options::USE_TIMEOUT | f_reactor, this->session ()->timeout ()); typedef ACE_Connector connector_type; connector_type connector; // create connection object (stream handler) SessionHolder::session_type::connection_type* data_connection = 0; ACE_NEW_NORETURN (data_connection, SessionHolder::session_type::connection_type(sync_opt)); if (data_connection == 0) { return 0; } // connect to data connection address if (connector.connect (data_connection, data_addr, ACE_Synch_Options (0, this->session ()->timeout ())) == -1) { INET_ERROR (1, (LM_ERROR, DLINFO ACE_TEXT ("(%d) ACE_FTP_ClientRequestHandler::open_data_connection - ") ACE_TEXT ("failed to connect to %C:%d\n"), ACE_OS::last_error (), data_addr.get_host_name (), data_addr.get_port_number ())); // as the connection was dynamically allocated // the connector causes it to be destroyed after // the connection failure return 0; } // enable ref counting so we can control when to destroy data_connection->reference_counting_policy ().value ( ACE_Event_Handler::Reference_Counting_Policy::ENABLED); // create io stream for connection stream_type* data_stream = 0; ACE_NEW_NORETURN (data_stream, stream_type (data_connection)); if (data_stream) { if (this->process_command (cmd, arg) == Response::PRELIM_OK) return data_stream; delete data_stream; // decreases ref count on connection } // remove last ref count -> delete data_connection->remove_reference (); } } else { // address for active data connection ACE_INET_Addr data_addr; this->session ()->get_local_addr (data_addr); data_addr.set_port_number (this->active_port_); // copy sync settings from session unsigned long f_reactor = this->session ()->is_reactive() ? ACE_Synch_Options::USE_REACTOR : 0; ACE_Synch_Options sync_opt (ACE_Synch_Options::USE_TIMEOUT | f_reactor, this->session ()->timeout ()); typedef ACE_Oneshot_Acceptor acceptor_type; acceptor_type acceptor; // start data connection acceptor listening and retrieve actual listening address if (acceptor.open (data_addr) == 0 && acceptor.acceptor ().get_local_addr (data_addr) == 0) { // send listen address to peer followed by data command to execute if (this->send_active_address (data_addr) && this->process_command (cmd, arg) == Response::PRELIM_OK) { // create connection object (stream handler) SessionHolder::session_type::connection_type* data_connection = 0; ACE_NEW_NORETURN (data_connection, SessionHolder::session_type::connection_type(sync_opt)); if (data_connection == 0) { return 0; } // accept data connection from peer if (acceptor.accept (data_connection, 0, ACE_Synch_Options (ACE_Synch_Options::USE_TIMEOUT, this->session ()->timeout ())) == -1) { INET_ERROR (1, (LM_ERROR, DLINFO ACE_TEXT ("(%d) ACE_FTP_ClientRequestHandler::open_data_connection - ") ACE_TEXT ("failed to accept connection to %C:%d\n"), ACE_OS::last_error (), data_addr.get_host_name (), data_addr.get_port_number ())); // as the connection was dynamically allocated // the acceptor causes it to be destroyed after // the connection failure return 0; } // enable ref counting so we can control when to destroy data_connection->reference_counting_policy ().value ( ACE_Event_Handler::Reference_Counting_Policy::ENABLED); // create io stream for connection stream_type* data_stream = 0; ACE_NEW_NORETURN (data_stream, stream_type (data_connection)); if (data_stream) { return data_stream; } // remove last ref count -> delete data_connection->remove_reference (); } } } return 0; } bool ClientRequestHandler::get_passive_address (ACE_INET_Addr& addr) { // can we use the RFC 2428 extensions? if (this->session ()->supports_ftp_extensions ()) { if (this->process_command (Request::FTP_EPSV) == Response::COMPLETED_OK) { return this->parse_ext_address (this->response_.response ()[0], addr); } // flag RFC 2428 extensions as unsupported this->session ()->set_ftp_extension_support (false); } // use 'old' command if (this->process_command (Request::FTP_PASV) == Response::COMPLETED_OK) { return this->parse_address (this->response_.response ()[0], addr); } return false; } bool ClientRequestHandler::parse_address (const ACE_CString& str, ACE_INET_Addr& addr) { static const int eof_ = std::char_traits::eof (); ACE::IOS::CString_OStream sos_host; u_short port_hi = 0, port_lo = 0; ACE::IOS::CString_IStream sis (str); sis.ignore (str.length (), '('); int ch = sis.get (); if (ACE_OS::ace_isdigit (ch)) { for (int i=0; i<4 ;++i) { if (ch == ',') { sos_host.put ('.'); ch = sis.get (); } while (ch != eof_ && ACE_OS::ace_isdigit (ch)) { sos_host.put (ch); ch = sis.get (); } } if (ch == ',') { sis >> port_hi; ch = sis.get (); if (ch == ',') { sis >> port_lo; u_short port = port_hi*256 + port_lo; addr.set (port, sos_host.str ().c_str ()); return true; } } } return false; } bool ClientRequestHandler::parse_ext_address (const ACE_CString& str, ACE_INET_Addr& addr) { static const int eof_ = std::char_traits::eof (); ACE::IOS::CString_IStream sis (str); sis.ignore (str.length (), '('); int ch = sis.get (); if (ch != eof_) { int delim = ch; sis.ignore (str.length (), delim); sis.ignore (str.length (), delim); if (sis.peek () != eof_) { u_short port = 0; sis >> port; addr.set (port, this->session ()->get_host ().c_str ()); return true; } } return false; } bool ClientRequestHandler::send_active_address (const ACE_INET_Addr& addr) { ACE::IOS::CString_OStream arg; char ip_buf[128]; if (addr.get_host_addr (ip_buf, sizeof(ip_buf)) == 0) { return false; } u_short port = addr.get_port_number (); // can we use the RFC 2428 extensions? if (this->session ()->supports_ftp_extensions ()) { arg << '|' << (addr.get_type () == AF_INET ? '1' : '2') << '|' << (&ip_buf[0]) << '|' << port << '|'; if (this->process_command (Request::FTP_EPRT, arg.str ()) == Response::COMPLETED_OK) { return true; } // flag RFC 2428 extensions as unsupported this->session ()->set_ftp_extension_support (false); arg.clear (); } // use 'old' command ACE_CString ip_addr = addr.get_host_addr (ip_buf, sizeof(ip_buf)); ACE_CString::size_type pos; while ((pos = ip_addr.find ('.')) != ACE_CString::npos) { ip_addr[pos] = ','; } arg << ip_addr << ',' << (port / 256) // port hi << ',' << (port % 256); // port lo return (this->process_command (Request::FTP_PORT, arg.str ()) == Response::COMPLETED_OK); } } } ACE_END_VERSIONED_NAMESPACE_DECL