// ftpd is a server implementation based on the following: // - RFC 959 (https://tools.ietf.org/html/rfc959) // - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // // Copyright (C) 2024 Michael Theall // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #pragma once #include "fs.h" #include "ftpConfig.h" #include "ioBuffer.h" #include "platform.h" #include "socket.h" #if __has_include() #include #define FTPD_HAS_GLOB 1 #else #define FTPD_HAS_GLOB 0 #endif #include using stat_t = struct stat; #include #include #include #include #include #include #include class FtpSession; using UniqueFtpSession = std::unique_ptr; /// \brief FTP session class FtpSession { public: ~FtpSession (); /// \brief Whether session sockets are all inactive bool dead (); /// \brief Draw session status void draw (); /// \brief Draw session connections void drawConnections (); /// \brief Create session /// \param config_ FTP config /// \param commandSocket_ Command socket static UniqueFtpSession create (FtpConfig &config_, UniqueSocket commandSocket_); /// \brief Poll for activity /// \param sessions_ Sessions to poll static bool poll (std::vector const &sessions_); private: /// \brief Command buffer size constexpr static auto COMMAND_BUFFERSIZE = 4096; #ifdef __NDS__ /// \brief Response buffer size constexpr static auto RESPONSE_BUFFERSIZE = 4096; /// \brief Transfer buffersize constexpr static auto XFER_BUFFERSIZE = 8192; #else /// \brief Response buffer size constexpr static auto RESPONSE_BUFFERSIZE = 16 * 1024; /// \brief Transfer buffersize constexpr static auto XFER_BUFFERSIZE = 32 * 1024; #endif /// \brief File buffersize constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE; #if defined(__NDS__) /// \brief Socket buffer size constexpr static auto SOCK_BUFFERSIZE = 4096; /// \brief Amount of file position history to keep constexpr static auto POSITION_HISTORY = 60; #elif defined(__3DS__) /// \brief Socket buffer size constexpr static auto SOCK_BUFFERSIZE = 32768; /// \brief Amount of file position history to keep constexpr static auto POSITION_HISTORY = 100; #else /// \brief Socket buffer size constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE; /// \brief Amount of file position history to keep constexpr static auto POSITION_HISTORY = 300; #endif /// \brief Session state enum class State { COMMAND, DATA_CONNECT, DATA_TRANSFER, }; /// \brief Transfer file mode enum class XferFileMode { RETR, STOR, APPE, }; /// \brief Transfer directory mode enum class XferDirMode { LIST, MLSD, MLST, NLST, STAT, }; /// \brief Parameterized constructor /// \param config_ FTP config /// \param commandSocket_ Command socket FtpSession (FtpConfig &config_, UniqueSocket commandSocket_); /// \brief Whether session is authorized bool authorized () const; /// \brief Set session state /// \param state_ State to set /// \param closePasv_ Whether to close listening socket /// \param closeData_ Whether to close data socket void setState (State state_, bool closePasv_, bool closeData_); /// \brief Close socket /// \param socket_ Socket to close void closeSocket (SharedSocket &socket_); /// \brief Close command socket void closeCommand (); /// \brief Close passive socket void closePasv (); /// \brief Close data socket void closeData (); /// \brief Change working directory bool changeDir (char const *args_); /// \brief Accept connection as data socket bool dataAccept (); /// \brief Connect data socket bool dataConnect (); /// \brief Perform stat and apply tz offset to mtime /// \param path_ Path to stat /// \param st_ Output stat int tzStat (char const *const path_, stat_t *st_); /// \brief Perform lstat and apply tz offset to mtime /// \param path_ Path to lstat /// \param st_ Output stat int tzLStat (char const *const path_, stat_t *st_); /// \brief Fill directory entry /// \param st_ Entry status /// \param path_ Path name /// \param type_ MLST type int fillDirent (stat_t const &st_, std::string_view path_, char const *type_ = nullptr); /// \brief Fill directory entry /// \param path_ Path name /// \param type_ MLST type int fillDirent (std::string const &path_, char const *type_ = nullptr); /// \brief Transfer file /// \param args_ Command arguments /// \param mode_ Transfer file mode void xferFile (char const *args_, XferFileMode mode_); /// \brief Transfer directory /// \param args_ Command arguments /// \param mode_ Transfer directory mode /// \param workaround_ Workaround broken clients who use LIST -a/-l void xferDir (char const *args_, XferDirMode mode_, bool workaround_); /// \brief Read command /// \param events_ Poll events void readCommand (int events_); /// \brief Write response void writeResponse (); /// \brief Send response /// \param fmt_ Message format __attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...); /// \brief Send response /// \param response_ Response message void sendResponse (std::string_view response_); /// \brief Transfer function bool (FtpSession::*m_transfer) () = nullptr; /// \brief Transfer directory list bool listTransfer (); #if FTPD_HAS_GLOB /// \brief Transfer glob list bool globTransfer (); #endif /// \brief Transfer download bool retrieveTransfer (); /// \brief Transfer upload bool storeTransfer (); #ifndef __NDS__ /// \brief Mutex platform::Mutex m_lock; #endif /// \brief FTP config FtpConfig &m_config; /// \brief Command socket SharedSocket m_commandSocket; /// \brief Data listen socker UniqueSocket m_pasvSocket; /// \brief Data socket SharedSocket m_dataSocket; /// \brief Sockets pending close std::vector m_pendingCloseSocket; /// \brief Command buffer IOBuffer m_commandBuffer; /// \brief Response buffer IOBuffer m_responseBuffer; /// \brief Transfer buffer IOBuffer m_xferBuffer; /// \brief Address from last PORT command SockAddr m_portAddr; /// \brief Current working directory std::string m_cwd = "/"; /// \brief List working directory std::string m_lwd; /// \brief Path from RNFR command std::string m_rename; /// \brief Current work item std::string m_workItem; /// \brief ImGui window name std::string m_windowName; /// \brief ImGui plot widget name std::string m_plotName; /// \brief Position from REST command std::uint64_t m_restartPosition = 0; /// \brief Current file position std::uint64_t m_filePosition = 0; /// \brief File size of current transfer std::uint64_t m_fileSize = 0; /// \brief Last file position update timestamp platform::steady_clock::time_point m_filePositionTime; /// \brief File position history std::uint64_t m_filePositionHistory[POSITION_HISTORY]; /// \brief File position history deltas float m_filePositionDeltas[POSITION_HISTORY]; /// \brief Transfer rate (EWMA low-pass filtered) float m_xferRate; /// \brief Session state State m_state = State::COMMAND; /// \brief File being transferred fs::File m_file; /// \brief Directory being transferred fs::Dir m_dir; #if FTPD_HAS_GLOB /// \brief Glob wrappre class Glob { public: ~Glob () noexcept; Glob () noexcept; /// \brief Perform glob /// \param pattern_ Glob pattern bool glob (char const *pattern_) noexcept; /// \brief Get next glob result /// \note returns nullptr when no more entries exist char const *next () noexcept; private: /// \brief Clear glob void clear () noexcept; /// \brief Glob result std::optional m_glob = std::nullopt; /// \brief Result counter unsigned m_offset = 0; }; /// \brief Glob Glob m_glob; #endif /// \brief Directory transfer mode XferDirMode m_xferDirMode; /// \brief Last activity timestamp time_t m_timestamp; /// \brief Whether user has been authorized bool m_authorizedUser : 1; /// \brief Whether password has been authorized bool m_authorizedPass : 1; /// \brief Whether previous command was PASV bool m_pasv : 1; /// \brief Whether previous command was PORT bool m_port : 1; /// \brief Whether receiving data bool m_recv : 1; /// \brief Whether sending data bool m_send : 1; /// \brief Whether urgent (out-of-band) data is on the way bool m_urgent : 1; /// \brief Whether MLST type fact is enabled bool m_mlstType : 1; /// \brief Whether MLST size fact is enabled bool m_mlstSize : 1; /// \brief Whether MLST modify fact is enabled bool m_mlstModify : 1; /// \brief Whether MLST perm fact is enabled bool m_mlstPerm : 1; /// \brief Whether MLST unix.mode fact is enabled bool m_mlstUnixMode : 1; /// \brief Whether emulating /dev/zero bool m_devZero : 1; /// \brief Abort a transfer /// \param args_ Command arguments void ABOR (char const *args_); /// \brief Allocate space /// \param args_ Command arguments void ALLO (char const *args_); /// \brief Append data to a file /// \param args_ Command arguments void APPE (char const *args_); /// \brief CWD to parent directory /// \param args_ Command arguments void CDUP (char const *args_); /// \brief Change working directory /// \param args_ Command arguments void CWD (char const *args_); /// \brief Delete a file /// \param args_ Command arguments void DELE (char const *args_); /// \brief List server features /// \param args_ Command arguments void FEAT (char const *args_); /// \brief Print server help /// \param args_ Command arguments void HELP (char const *args_); /// \brief List directory /// \param args_ Command arguments void LIST (char const *args_); /// \brief Last modification time /// \param args_ Command arguments void MDTM (char const *args_); /// \brief Create a directory /// \param args_ Command arguments void MKD (char const *args_); /// \brief Machine list directory /// \param args_ Command arguments void MLSD (char const *args_); /// \brief Machine list /// \param args_ Command arguments void MLST (char const *args_); /// \brief Set transfer mode /// \param args_ Command arguments void MODE (char const *args_); /// \brief Name list /// \param args_ Command arguments void NLST (char const *args_); /// \brief No-op /// \param args_ Command arguments void NOOP (char const *args_); /// \brief Set server options /// \param args_ Command arguments void OPTS (char const *args_); /// \brief Password /// \param args_ Command arguments void PASS (char const *args_); /// \brief Request an address to connect to for data transfers /// \param args_ Command arguments void PASV (char const *args_); /// \brief Provide an address to connect to for data transfers /// \param args_ Command arguments void PORT (char const *args_); /// \brief Print working directory /// \param args_ Command arguments void PWD (char const *args_); /// \brief Terminate session /// \param args_ Command arguments void QUIT (char const *args_); /// \brief Restart a file transfer /// \param args_ Command arguments void REST (char const *args_); /// \brief Retrieve a file /// \param args_ Command arguments /// \note Requires a PASV or PORT connection void RETR (char const *args_); /// \brief Remove a directory /// \param args_ Command arguments void RMD (char const *args_); /// \brief Rename from /// \param args_ Command arguments void RNFR (char const *args_); /// \brief Rename to /// \param args_ Command arguments void RNTO (char const *args_); /// \brief Site command /// \param args_ Command arguments void SITE (char const *args_); /// \brief Get file size /// \param args_ Command arguments void SIZE (char const *args_); /// \brief Get status /// \param args_ Command arguments /// \note If no argument is supplied, and a transfer is occurring, get the current transfer /// status. If no argument is supplied, and no transfer is occurring, get the server status. If /// an argument is supplied, this is equivalent to LIST, except the data is sent over the /// command socket. void STAT (char const *args_); /// \brief Store a file /// \param args_ Command arguments void STOR (char const *args_); /// \brief Store a unique file /// \param args_ Command arguments void STOU (char const *args_); /// \brief Set file structure /// \param args_ Command arguments void STRU (char const *args_); /// \brief Identify system /// \param args_ Command arguments void SYST (char const *args_); /// \brief Set representation type /// \param args_ Command arguments void TYPE (char const *args_); /// \brief User name /// \param args_ Command arguments void USER (char const *args_); /// \brief Map of command handlers static std::vector> const handlers; };