Skip to content

Commit

Permalink
Add ability to redirect log output to custom streams (#75)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
ShaharHD and FlorianReimold authored Jan 15, 2025
1 parent 6588420 commit b8a26f4
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 52 deletions.
38 changes: 32 additions & 6 deletions fineftp-server/include/fineftp/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <cstdint>
#include <memory>
#include <string>
#include <iostream>

// IWYU pragma: begin_exports
#include <fineftp/permissions.h>
Expand Down Expand Up @@ -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.
*
Expand All @@ -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.
*/
Expand All @@ -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;
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions fineftp-server/src/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ namespace Filesystem
return can_open_dir;
}

std::map<std::string, FileStatus> dirContent(const std::string& path)
std::map<std::string, FileStatus> dirContent(const std::string& path, std::ostream& error)
{
std::map<std::string, FileStatus> content;
#ifdef WIN32
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion fineftp-server/src/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cstdint>
#include <map>
#include <string>
#include <iostream>

#include <sys/stat.h>

Expand Down Expand Up @@ -68,7 +69,7 @@ namespace fineftp
#endif
};

std::map<std::string, FileStatus> dirContent(const std::string& path);
std::map<std::string, FileStatus> dirContent(const std::string& path, std::ostream& error);

std::string cleanPath(const std::string& path, bool path_is_windows_path, char output_separator);

Expand Down
43 changes: 26 additions & 17 deletions fineftp-server/src/ftp_session.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#include "ftp_session.h"

#include <asio.hpp>

#include <algorithm>
#include <cassert> // assert
#include <cctype> // std::iscntrl, toupper
#include <chrono>
#include <chrono> // IWYU pragma: keep (it is used for special preprocessor defines)
#include <cstddef>
#include <cstdio>
#include <fstream>
Expand All @@ -28,6 +30,11 @@
#include <sys/stat.h>

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include "win_str_convert.h"
#else
#include <unistd.h>
Expand All @@ -37,7 +44,7 @@
namespace fineftp
{

FtpSession::FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function<void()>& completion_handler)
FtpSession::FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function<void()>& completion_handler, std::ostream& output, std::ostream& error)
: completion_handler_ (completion_handler)
, user_database_ (user_database)
, io_service_ (io_service)
Expand All @@ -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

{
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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_
Expand Down Expand Up @@ -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;
}
}
));
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}

Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down
5 changes: 4 additions & 1 deletion fineftp-server/src/ftp_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ namespace fineftp
// Public API
////////////////////////////////////////////////////////
public:
FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function<void()>& completion_handler);
FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function<void()>& completion_handler, std::ostream& output, std::ostream& error);

// Copy (disabled, as we are inheriting from shared_from_this)
FtpSession(const FtpSession&) = delete;
Expand Down Expand Up @@ -209,5 +209,8 @@ namespace fineftp
std::deque<std::shared_ptr<std::vector<char>>> data_buffer_;

asio::steady_timer timer_;

std::ostream& output_; /* Normal output log */
std::ostream& error_; /* Error output log */
};
}
19 changes: 12 additions & 7 deletions fineftp-server/src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

#include "server_impl.h"

#include <cassert> // assert
#include <cstddef> // size_t
#include <cstdint> // uint16_t
#include <memory>
#include <ostream>
#include <string>
#include <cstdint> // uint16_t
#include <cstddef> // size_t
#include <cassert> // assert

#include <fineftp/permissions.h>

namespace fineftp
{
FtpServer::FtpServer(const std::string& address, uint16_t port)
: ftp_server_(std::make_unique<FtpServerImpl>(address, port))
FtpServer::FtpServer(const std::string& address, const uint16_t port, std::ostream& output, std::ostream& error)
: ftp_server_(std::make_unique<FtpServerImpl>(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
Expand Down
Loading

0 comments on commit b8a26f4

Please sign in to comment.