diff --git a/fineftp-server/include/fineftp/server.h b/fineftp-server/include/fineftp/server.h index 721d25e..964baee 100644 --- a/fineftp-server/include/fineftp/server.h +++ b/fineftp-server/include/fineftp/server.h @@ -4,6 +4,7 @@ #include #include #include +#include // IWYU pragma: begin_exports #include @@ -40,6 +41,25 @@ namespace fineftp class FtpServer { public: + /** + * @brief Creates an FTP Server instance that will listen on the the given control port and accept connections from the given network interface. + * + * Instead of binding the server to a specific address, the server can + * listen on any interface by providing "0.0.0.0" as address. + * + * Instead of using a predefined port, the operating system can choose a + * free port port. Use port=0, if that behaviour is desired. The chosen port + * can be determined by with getPort(). + * + * This constructor will accept streams for info and error log output. + * + * @param address: The address to accept incoming connections from. Use "0.0.0.0" to accept connections from any address. + * @param port: The port to start the FTP server on. Use 0 to let the operating system choose a free port. Use 21 for using the default FTP port. + * @param output: Stream for info log output. Defaults to std::cout if constructors without that options are used. + * @param error: Stream for error log output. Defaults to std::cerr if constructors without that options are used. + */ + FINEFTP_EXPORT FtpServer(const std::string& address, uint16_t port, std::ostream& output, std::ostream& error); + /** * @brief Creates an FTP Server instance that will listen on the the given control port and accept connections from the given network interface. * @@ -50,6 +70,9 @@ namespace fineftp * free port port. Use port=0, if that behaviour is desired. The chosen port * can be determined by with getPort(). * + * Logs will be printed to std::cout and std::cerr. If you want to use a + * different output stream, use the other constructor. + * * @param port: The port to start the FTP server on. Defaults to 21. * @param host: The host to accept incoming connections from. */ @@ -64,15 +87,18 @@ namespace fineftp * Instead of using a predefined port, the operating system can choose a * free port port. Use port=0, if that behaviour is desired. The chosen port * can be determined by with getPort(). - * + * * This constructor will create an FTP Server binding to IPv4 0.0.0.0 and * Thus accepting connections from any IPv4 address. * For security reasons it might be desirable to bind to a specific IP * address. Use FtpServer(const std::string&, uint16_t) for that purpose. - * + * + * Logs will be printed to std::cout and std::cerr. If you want to use a + * different output stream, use the other constructor. + * * @param port: The port to start the FTP server on. Defaults to 21. */ - FINEFTP_EXPORT FtpServer(uint16_t port = 21); + FINEFTP_EXPORT explicit FtpServer(uint16_t port = 21); // Move FINEFTP_EXPORT FtpServer(FtpServer&&) noexcept; @@ -103,11 +129,11 @@ namespace fineftp * @param password: The user's password * @param local_root_path: A path to any resource on the local filesystem that will be accessed by the user * @param permissions: A bit-mask of what the user will be able to do. - * + * * @return True if adding the user was successful (i.e. it didn't exit already). */ FINEFTP_EXPORT bool addUser(const std::string& username, const std::string& password, const std::string& local_root_path, Permission permissions); - + /** * @brief Adds the "anonymous" / "ftp" user that FTP clients use to access FTP servers without password * @@ -137,7 +163,7 @@ namespace fineftp /** * @brief Returns the number of currently open connections - * + * * @return the number of open connections */ FINEFTP_EXPORT int getOpenConnectionCount() const; diff --git a/fineftp-server/src/filesystem.cpp b/fineftp-server/src/filesystem.cpp index c95aa25..d515f7d 100644 --- a/fineftp-server/src/filesystem.cpp +++ b/fineftp-server/src/filesystem.cpp @@ -283,7 +283,7 @@ namespace Filesystem return can_open_dir; } - std::map dirContent(const std::string& path) + std::map dirContent(const std::string& path, std::ostream& error) { std::map content; #ifdef WIN32 @@ -297,7 +297,7 @@ namespace Filesystem hFind = FindFirstFileW(w_find_file_path.c_str(), &ffd); if (hFind == INVALID_HANDLE_VALUE) { - std::cerr << "FindFirstFile Error" << std::endl; + error << "FindFirstFile Error" << std::endl; return content; } @@ -312,7 +312,7 @@ namespace Filesystem struct dirent *dirp = nullptr; if(dp == nullptr) { - std::cerr << "Error opening directory: " << strerror(errno) << std::endl; + error << "Error opening directory: " << strerror(errno) << std::endl; return content; } diff --git a/fineftp-server/src/filesystem.h b/fineftp-server/src/filesystem.h index 14710d2..6172800 100644 --- a/fineftp-server/src/filesystem.h +++ b/fineftp-server/src/filesystem.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -68,7 +69,7 @@ namespace fineftp #endif }; - std::map dirContent(const std::string& path); + std::map dirContent(const std::string& path, std::ostream& error); std::string cleanPath(const std::string& path, bool path_is_windows_path, char output_separator); diff --git a/fineftp-server/src/ftp_session.cpp b/fineftp-server/src/ftp_session.cpp index 262a775..c505621 100755 --- a/fineftp-server/src/ftp_session.cpp +++ b/fineftp-server/src/ftp_session.cpp @@ -1,9 +1,11 @@ #include "ftp_session.h" +#include + #include #include // assert #include // std::iscntrl, toupper -#include +#include // IWYU pragma: keep (it is used for special preprocessor defines) #include #include #include @@ -28,6 +30,11 @@ #include #ifdef WIN32 + #define WIN32_LEAN_AND_MEAN + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include #include "win_str_convert.h" #else #include @@ -37,7 +44,7 @@ namespace fineftp { - FtpSession::FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function& completion_handler) + FtpSession::FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function& completion_handler, std::ostream& output, std::ostream& error) : completion_handler_ (completion_handler) , user_database_ (user_database) , io_service_ (io_service) @@ -49,6 +56,8 @@ namespace fineftp , data_acceptor_ (io_service) , data_socket_strand_ (io_service) , timer_ (io_service) + , output_(output) + , error_(error) { } @@ -62,7 +71,7 @@ namespace fineftp { asio::error_code ec; command_socket_.set_option(asio::ip::tcp::no_delay(true), ec); - if (ec) std::cerr << "Unable to set socket option tcp::no_delay: " << ec.message() << std::endl; + if (ec) error_ << "Unable to set socket option tcp::no_delay: " << ec.message() << std::endl; command_strand_.post([me = shared_from_this()]() { me->readFtpCommand(); }); sendFtpMessage(FtpMessage(FtpReplyCode::SERVICE_READY_FOR_NEW_USER, "Welcome to fineFTP Server")); @@ -73,7 +82,7 @@ namespace fineftp { #ifndef NDEBUG // TODO: Have a "is stopped" variable, so that this log message is not printed every time - std::cout << "Ftp Session shutting down" << std::endl; + output_ << "Ftp Session shutting down" << std::endl; #endif // !NDEBUG // TODO: protect the two sockets with mutexes, as it is now possible to call stop() from another thread!!! @@ -142,7 +151,7 @@ namespace fineftp void FtpSession::startSendingMessages() { #ifndef NDEBUG - std::cout << "FTP >> " << command_output_queue_.front() << std::endl; + output_ << "FTP >> " << command_output_queue_.front() << std::endl; #endif asio::async_write(command_socket_ @@ -175,7 +184,7 @@ namespace fineftp auto trimmed_message = me->command_output_queue_.front(); trimmed_message.erase(trimmed_message.find_last_not_of("\r\n") + 1); - std::cerr << "Command write error for message " << trimmed_message << ": " << ec.message() << std::endl; + error_ << "Command write error for message " << trimmed_message << ": " << ec.message() << std::endl; } } )); @@ -190,12 +199,12 @@ namespace fineftp { if (ec != asio::error::eof) { - std::cerr << "read_until error: " << ec.message() << std::endl; + me->error_ << "read_until error: " << ec.message() << std::endl; } #ifndef NDEBUG else { - std::cout << "Control connection closed by client." << std::endl; + me->output_ << "Control connection closed by client." << std::endl; } #endif // !NDEBUG // Close the data connection, if it is open @@ -223,7 +232,7 @@ namespace fineftp stream.ignore(2); // Remove the "\r\n" #ifndef NDEBUG - std::cout << "FTP << " << packet_string << std::endl; + me->output_ << "FTP << " << packet_string << std::endl; #endif me->handleFtpCommand(packet_string); @@ -437,7 +446,7 @@ namespace fineftp data_acceptor_.close(ec); if (ec) { - std::cerr << "Error closing data acceptor: " << ec.message() << std::endl; + error_ << "Error closing data acceptor: " << ec.message() << std::endl; } } @@ -448,7 +457,7 @@ namespace fineftp data_acceptor_.open(endpoint.protocol(), ec); if (ec) { - std::cerr << "Error opening data acceptor: " << ec.message() << std::endl; + error_ << "Error opening data acceptor: " << ec.message() << std::endl; sendFtpMessage(FtpReplyCode::SERVICE_NOT_AVAILABLE, "Failed to enter passive mode."); return; } @@ -458,7 +467,7 @@ namespace fineftp data_acceptor_.bind(endpoint, ec); if (ec) { - std::cerr << "Error binding data acceptor: " << ec.message() << std::endl; + error_ << "Error binding data acceptor: " << ec.message() << std::endl; sendFtpMessage(FtpReplyCode::SERVICE_NOT_AVAILABLE, "Failed to enter passive mode."); return; } @@ -468,7 +477,7 @@ namespace fineftp data_acceptor_.listen(asio::socket_base::max_connections, ec); if (ec) { - std::cerr << "Error listening on data acceptor: " << ec.message() << std::endl; + error_ << "Error listening on data acceptor: " << ec.message() << std::endl; sendFtpMessage(FtpReplyCode::SERVICE_NOT_AVAILABLE, "Failed to enter passive mode."); return; } @@ -1085,7 +1094,7 @@ namespace fineftp if (dir_status.canOpenDir()) { sendFtpMessage(FtpReplyCode::FILE_STATUS_OK_OPENING_DATA_CONNECTION, "Sending directory listing"); - sendDirectoryListing(Filesystem::dirContent(local_path)); + sendDirectoryListing(Filesystem::dirContent(local_path, error_)); return; } else @@ -1133,7 +1142,7 @@ namespace fineftp if (dir_status.canOpenDir()) { sendFtpMessage(FtpReplyCode::FILE_STATUS_OK_OPENING_DATA_CONNECTION, "Sending name list"); - sendNameList(Filesystem::dirContent(local_path)); + sendNameList(Filesystem::dirContent(local_path, error_)); return; } else @@ -1399,7 +1408,7 @@ namespace fineftp if (ec) { - std::cerr << "Data write error: " << ec.message() << std::endl; + me->error_ << "Data write error: " << ec.message() << std::endl; return; } @@ -1440,7 +1449,7 @@ namespace fineftp { if (ec) { - std::cerr << "Data transfer aborted: " << ec.message() << std::endl; + me->error_ << "Data transfer aborted: " << ec.message() << std::endl; me->sendFtpMessage(FtpReplyCode::TRANSFER_ABORTED, "Data transfer aborted"); return; } diff --git a/fineftp-server/src/ftp_session.h b/fineftp-server/src/ftp_session.h index 2826cc1..feae018 100755 --- a/fineftp-server/src/ftp_session.h +++ b/fineftp-server/src/ftp_session.h @@ -33,7 +33,7 @@ namespace fineftp // Public API //////////////////////////////////////////////////////// public: - FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function& completion_handler); + FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function& completion_handler, std::ostream& output, std::ostream& error); // Copy (disabled, as we are inheriting from shared_from_this) FtpSession(const FtpSession&) = delete; @@ -210,5 +210,8 @@ namespace fineftp std::deque>> data_buffer_; asio::steady_timer timer_; + + std::ostream& output_; /* Normal output log */ + std::ostream& error_; /* Error output log */ }; } diff --git a/fineftp-server/src/server.cpp b/fineftp-server/src/server.cpp index be981ef..145a71f 100644 --- a/fineftp-server/src/server.cpp +++ b/fineftp-server/src/server.cpp @@ -2,22 +2,27 @@ #include "server_impl.h" +#include // assert +#include // size_t +#include // uint16_t #include +#include #include -#include // uint16_t -#include // size_t -#include // assert #include namespace fineftp { - FtpServer::FtpServer(const std::string& address, uint16_t port) - : ftp_server_(FtpServerImpl::create(address, port)) + FtpServer::FtpServer(const std::string& address, uint16_t port, std::ostream& output, std::ostream& error) + : ftp_server_(FtpServerImpl::create(address, port, output, error)) + {} + + FtpServer::FtpServer(const std::string& address, const uint16_t port) + : FtpServer(address, port, std::cout, std::cerr) {} - FtpServer::FtpServer(uint16_t port) - : FtpServer(std::string("0.0.0.0"), port) + FtpServer::FtpServer(const uint16_t port) + : FtpServer(std::string("0.0.0.0"), port, std::cout, std::cerr) {} // Move diff --git a/fineftp-server/src/server_impl.cpp b/fineftp-server/src/server_impl.cpp index 2a4af54..dbbc712 100644 --- a/fineftp-server/src/server_impl.cpp +++ b/fineftp-server/src/server_impl.cpp @@ -20,11 +20,14 @@ namespace fineftp return std::shared_ptr(new FtpServerImpl(address, port)); } - FtpServerImpl::FtpServerImpl(const std::string& address, uint16_t port) - : port_ (port) + FtpServerImpl::FtpServerImpl(const std::string& address, const uint16_t port, std::ostream& output, std::ostream& error) + : ftp_users_ (output, error) + , port_ (port) , address_ (address) , is_stopped_ (false) , acceptor_ (io_service_) + , output_ (output) + , error_ (error) {} FtpServerImpl::~FtpServerImpl() @@ -49,7 +52,7 @@ namespace fineftp const asio::ip::tcp::endpoint endpoint(asio::ip::make_address(address_, make_address_ec), port_); if (make_address_ec) { - std::cerr << "Error creating address from string \"" << address_<< "\": " << make_address_ec.message() << std::endl; + error_ << "Error creating address from string \"" << address_<< "\": " << make_address_ec.message() << std::endl; return false; } @@ -60,7 +63,7 @@ namespace fineftp acceptor_.open(endpoint.protocol(), ec); if (ec) { - std::cerr << "Error opening acceptor: " << ec.message() << std::endl; + error_ << "Error opening acceptor: " << ec.message() << std::endl; return false; } } @@ -85,7 +88,7 @@ namespace fineftp acceptor_.bind(endpoint, ec); if (ec) { - std::cerr << "Error binding acceptor: " << ec.message() << std::endl; + error_ << "Error binding acceptor: " << ec.message() << std::endl; return false; } } @@ -97,7 +100,7 @@ namespace fineftp acceptor_.listen(asio::socket_base::max_listen_connections, ec); if (ec) { - std::cerr << "Error listening on acceptor: " << ec.message() << std::endl; + error_ << "Error listening on acceptor: " << ec.message() << std::endl; return false; } } @@ -105,7 +108,7 @@ namespace fineftp #ifndef NDEBUG { const std::lock_guard acceptor_lock(acceptor_mutex_); - std::cout << "FTP Server created." << std::endl << "Listening at address " << acceptor_.local_endpoint().address() << " on port " << acceptor_.local_endpoint().port() << ":" << std::endl; + output_ << "FTP Server created." << std::endl << "Listening at address " << acceptor_.local_endpoint().address() << " on port " << acceptor_.local_endpoint().port() << ":" << std::endl; } #endif // NDEBUG @@ -202,7 +205,7 @@ namespace fineftp } #ifndef NDEBUG - std::cout << "FTP Client connected: " << new_ftp_session->getSocket().remote_endpoint().address().to_string() << ":" << new_ftp_session->getSocket().remote_endpoint().port() << std::endl; + output_ << "FTP Client connected: " << new_ftp_session->getSocket().remote_endpoint().address().to_string() << ":" << new_ftp_session->getSocket().remote_endpoint().port() << std::endl; #endif // TODO: review if this is thread safe, if right here the ftp server is shut down and the acceptor is closed. I think, that then the session will still be added to the list of open sessions and kept open. diff --git a/fineftp-server/src/server_impl.h b/fineftp-server/src/server_impl.h index 3fb66f0..a4d7512 100644 --- a/fineftp-server/src/server_impl.h +++ b/fineftp-server/src/server_impl.h @@ -21,8 +21,8 @@ namespace fineftp class FtpServerImpl : public std::enable_shared_from_this { public: - static std::shared_ptr create(const std::string& address, uint16_t port); - + FtpServerImpl(const std::string& address, uint16_t port, std::ostream& output, std::ostream& error); + private: FtpServerImpl(const std::string& address, uint16_t port); @@ -68,5 +68,8 @@ namespace fineftp mutable std::mutex session_list_mutex_; //!< Mutex protecting the list of current sessions std::map> session_list_; //!< List of sessions. Only store weak_ptr, so the sessions can delete themselves. This list is used to stop sessions and count connections. The raw pointers are used to identify the entry even while a session is currently in the destructor, as there is no cross-plattform way of obtaining the raw pointer from the weak pointer in that case. + + std::ostream& output_; /* Normal output log */ + std::ostream& error_; /* Error output log */ }; } diff --git a/fineftp-server/src/user_database.cpp b/fineftp-server/src/user_database.cpp index 499f529..c6d0f14 100644 --- a/fineftp-server/src/user_database.cpp +++ b/fineftp-server/src/user_database.cpp @@ -11,6 +11,16 @@ namespace fineftp { + UserDatabase::UserDatabase(std::ostream& output, std::ostream& error) + : output_(output) + , error_(error) + { +#ifdef NDEBUG + // Avoid unused-private-field warning + static_cast(output_); +#endif // NDEBUG + } + bool UserDatabase::addUser(const std::string& username, const std::string& password, const std::string& local_root_path, Permission permissions) { const std::lock_guard database_lock(database_mutex_); @@ -19,14 +29,14 @@ namespace fineftp { if (anonymous_user_) { - std::cerr << "Error adding user with username \"" << username << "\". The username denotes the anonymous user, which is already present." << std::endl; + error_ << "Error adding user with username \"" << username << "\". The username denotes the anonymous user, which is already present." << std::endl; return false; } else { anonymous_user_ = std::make_shared(password, local_root_path, permissions); #ifndef NDEBUG - std::cout << "Successfully added anonymous user." << std::endl; + output_ << "Successfully added anonymous user." << std::endl; #endif // !NDEBUG return true; } @@ -38,13 +48,13 @@ namespace fineftp { database_.emplace(username, std::make_shared(password, local_root_path, permissions)); #ifndef NDEBUG - std::cout << "Successfully added user \"" << username << "\"." << std::endl; + output_ << "Successfully added user \"" << username << "\"." << std::endl; #endif // !NDEBUG return true; } else { - std::cerr << "Error adding user with username \"" << username << "\". The user already exists." << std::endl; + error_ << "Error adding user with username \"" << username << "\". The user already exists." << std::endl; return false; } } diff --git a/fineftp-server/src/user_database.h b/fineftp-server/src/user_database.h index b5cd54b..0ad1416 100644 --- a/fineftp-server/src/user_database.h +++ b/fineftp-server/src/user_database.h @@ -13,6 +13,8 @@ namespace fineftp class UserDatabase { public: + UserDatabase(std::ostream& output, std::ostream& error); + bool addUser(const std::string& username, const std::string& password, const std::string& local_root_path, Permission permissions); std::shared_ptr getUser(const std::string& username, const std::string& password) const; @@ -23,5 +25,8 @@ namespace fineftp mutable std::mutex database_mutex_; std::map> database_; std::shared_ptr anonymous_user_; + + std::ostream& output_; /* Normal output log */ + std::ostream& error_; /* Error output log */ }; } \ No newline at end of file diff --git a/fineftp-server/src/win32/file_man.cpp b/fineftp-server/src/win32/file_man.cpp index ce53c29..1dadba4 100644 --- a/fineftp-server/src/win32/file_man.cpp +++ b/fineftp-server/src/win32/file_man.cpp @@ -2,9 +2,20 @@ #include "file_man.h" +#include +#include #include +#include #include #include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX + #define NOMINMAX +#endif +#include #include "win_str_convert.h" diff --git a/fineftp-server/version.cmake b/fineftp-server/version.cmake index 6a235a5..56aadc5 100644 --- a/fineftp-server/version.cmake +++ b/fineftp-server/version.cmake @@ -1,3 +1,3 @@ set(FINEFTP_SERVER_VERSION_MAJOR 1) -set(FINEFTP_SERVER_VERSION_MINOR 4) -set(FINEFTP_SERVER_VERSION_PATCH 3) +set(FINEFTP_SERVER_VERSION_MINOR 5) +set(FINEFTP_SERVER_VERSION_PATCH 0)