From b8a26f4f8ae7184691923cf85acd21f74b8b1dc1 Mon Sep 17 00:00:00 2001 From: Shahar Hadas Date: Wed, 15 Jan 2025 12:33:36 +0200 Subject: [PATCH] Add ability to redirect log output to custom streams (#75) By default, log output will be printed to cout and cerr, but now custom streams can be provided to redirect them. --------- Co-authored-by: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> --- fineftp-server/include/fineftp/server.h | 38 ++++++++++++++++++---- fineftp-server/src/filesystem.cpp | 6 ++-- fineftp-server/src/filesystem.h | 3 +- fineftp-server/src/ftp_session.cpp | 43 +++++++++++++++---------- fineftp-server/src/ftp_session.h | 5 ++- fineftp-server/src/server.cpp | 19 +++++++---- fineftp-server/src/server_impl.cpp | 27 +++++++++------- fineftp-server/src/server_impl.h | 5 ++- fineftp-server/src/user_database.cpp | 18 ++++++++--- fineftp-server/src/user_database.h | 5 +++ fineftp-server/src/win32/file_man.cpp | 11 +++++++ 11 files changed, 128 insertions(+), 52 deletions(-) diff --git a/fineftp-server/include/fineftp/server.h b/fineftp-server/include/fineftp/server.h index 9e8f074..7637c31 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 3be83a9..109d293 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,13 +56,15 @@ namespace fineftp , data_acceptor_ (io_service) , data_socket_strand_ (io_service) , timer_ (io_service) + , output_(output) + , error_(error) { } FtpSession::~FtpSession() { #ifndef NDEBUG - std::cout << "Ftp Session shutting down" << std::endl; + output_ << "Ftp Session shutting down" << std::endl; #endif // !NDEBUG { @@ -85,7 +94,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")); @@ -121,7 +130,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_ @@ -150,7 +159,7 @@ namespace fineftp } else { - std::cerr << "Command write error for message " << me->command_output_queue_.front() << ec.message() << std::endl; + me->error_ << "Command write error for message " << me->command_output_queue_.front() << ec.message() << std::endl; } } )); @@ -165,12 +174,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 @@ -198,7 +207,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); @@ -412,7 +421,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; } } @@ -423,7 +432,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; } @@ -433,7 +442,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; } @@ -443,7 +452,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; } @@ -1060,7 +1069,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 @@ -1108,7 +1117,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 @@ -1374,7 +1383,7 @@ namespace fineftp if (ec) { - std::cerr << "Data write error: " << ec.message() << std::endl; + me->error_ << "Data write error: " << ec.message() << std::endl; return; } @@ -1415,7 +1424,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 5a08556..a448b9d 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; @@ -209,5 +209,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 6fd0a12..e40038d 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_(std::make_unique(address, port)) + FtpServer::FtpServer(const std::string& address, const uint16_t port, std::ostream& output, std::ostream& error) + : ftp_server_(std::make_unique(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 0d8b151..dd1e00c 100644 --- a/fineftp-server/src/server_impl.cpp +++ b/fineftp-server/src/server_impl.cpp @@ -16,11 +16,14 @@ namespace fineftp { - 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) , acceptor_ (io_service_) , open_connection_count_(0) + , output_ (output) + , error_ (error) {} FtpServerImpl::~FtpServerImpl() @@ -40,14 +43,14 @@ namespace fineftp bool FtpServerImpl::start(size_t thread_count) { - auto ftp_session = std::make_shared(io_service_, ftp_users_, [this]() { open_connection_count_--; }); + auto ftp_session = std::make_shared(io_service_, ftp_users_, [this]() { open_connection_count_--; }, output_, error_); // set up the acceptor to listen on the tcp port asio::error_code make_address_ec; 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; } @@ -56,7 +59,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; } } @@ -66,7 +69,7 @@ namespace fineftp acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true), ec); if (ec) { - std::cerr << "Error setting reuse_address option: " << ec.message() << std::endl; + error_ << "Error setting reuse_address option: " << ec.message() << std::endl; return false; } } @@ -76,7 +79,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; } } @@ -86,13 +89,13 @@ 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; } } #ifndef NDEBUG - 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 acceptor_.async_accept(ftp_session->getSocket() @@ -126,18 +129,18 @@ namespace fineftp if (error) { #ifndef NDEBUG - std::cerr << "Error handling connection: " << error.message() << std::endl; + error_ << "Error handling connection: " << error.message() << std::endl; #endif return; } #ifndef NDEBUG - std::cout << "FTP Client connected: " << ftp_session->getSocket().remote_endpoint().address().to_string() << ":" << ftp_session->getSocket().remote_endpoint().port() << std::endl; + output_ << "FTP Client connected: " << ftp_session->getSocket().remote_endpoint().address().to_string() << ":" << ftp_session->getSocket().remote_endpoint().port() << std::endl; #endif ftp_session->start(); - auto new_session = std::make_shared(io_service_, ftp_users_, [this]() { open_connection_count_--; }); + auto new_session = std::make_shared(io_service_, ftp_users_, [this]() { open_connection_count_--; }, output_, error_); acceptor_.async_accept(new_session->getSocket() , [this, new_session](auto ec) diff --git a/fineftp-server/src/server_impl.h b/fineftp-server/src/server_impl.h index 0a3a90d..7f1c0d5 100644 --- a/fineftp-server/src/server_impl.h +++ b/fineftp-server/src/server_impl.h @@ -20,7 +20,7 @@ namespace fineftp class FtpServerImpl { public: - FtpServerImpl(const std::string& address, uint16_t port); + FtpServerImpl(const std::string& address, uint16_t port, std::ostream& output, std::ostream& error); // Copy (disabled) FtpServerImpl(const FtpServerImpl&) = delete; @@ -59,5 +59,8 @@ namespace fineftp asio::ip::tcp::acceptor acceptor_; std::atomic open_connection_count_; + + 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"