Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
… into hotfix/properly_close_sockets

# Conflicts:
#	fineftp-server/src/ftp_session.cpp
#	fineftp-server/src/ftp_session.h
#	fineftp-server/src/server.cpp
#	fineftp-server/src/server_impl.cpp
#	fineftp-server/src/server_impl.h
  • Loading branch information
FlorianReimold committed Feb 12, 2025
2 parents 1d4d224 + 1f08ffa commit 792d40a
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 51 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(FtpSession*)>& completion_handler)
FtpSession::FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function<void(FtpSession*)>& completion_handler, std::ostream& output, std::ostream& error)
: completion_handler_ (completion_handler)
, user_database_ (user_database)
, io_service_ (io_service)
Expand All @@ -49,6 +56,8 @@ namespace fineftp
, data_acceptor_ (io_service)
, data_socket_strand_ (io_service)
, timer_ (io_service)
, output_(output)
, error_(error)
{
}

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

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

Expand Down Expand Up @@ -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;
}
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(FtpSession*)>& completion_handler);
FtpSession(asio::io_service& io_service, const UserDatabase& user_database, const std::function<void(FtpSession*)>& 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 @@ -210,5 +210,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_(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
Expand Down
Loading

0 comments on commit 792d40a

Please sign in to comment.