Add configuration

This commit is contained in:
Michael Theall 2020-04-24 15:51:43 -05:00
parent d9965f33df
commit 3f31371193
18 changed files with 978 additions and 38 deletions

View File

@ -21,6 +21,7 @@ AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true

View File

@ -71,7 +71,7 @@ CFLAGS := -g -Wall $(OPTIMIZE) -mword-relocations \
CFLAGS += $(INCLUDE) -DARM11 -D_3DS \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
-DNO_IPV6
-DNO_IPV6 -DFTPDCONFIG="\"/config/ftpd/ftpd.cfg\""
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17

View File

@ -11,7 +11,8 @@ CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/linux \
`pkg-config --cflags gl glfw3` \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
-DIMGUI_IMPL_OPENGL_LOADER_GLAD=1
-DIMGUI_IMPL_OPENGL_LOADER_GLAD=1 \
-DFTPDCONFIG="\"ftpd.cfg\""
CFLAGS := $(CPPFLAGS)
CXXFLAGS := $(CPPFLAGS) -std=gnu++17
LDFLAGS := -pthread `pkg-config --libs gl glfw3` -ldl

View File

@ -47,7 +47,7 @@ CFLAGS := -g -Wall $(OPTIMIZE) \
$(ARCH) $(INCLUDE) -DARM9 -DNDS \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
-DNO_IPV6 -DCLASSIC
-DNO_IPV6 -DCLASSIC -DFTPDCONFIG="\"/config/ftpd/ftpd.cfg\""
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) $(OPTIMIZE)

View File

@ -74,6 +74,7 @@ CFLAGS := -g -Wall -Wno-narrowing $(OPTIMIZE) \
CFLAGS += $(INCLUDE) -D__SWITCH__ \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
-DFTPDCONFIG="\"/config/ftpd/ftpd.cfg\"" \
`$(PREFIX)pkg-config --cflags libzstd`
CXXFLAGS := $(CFLAGS) -std=gnu++17 -fno-exceptions -fno-rtti

View File

@ -105,6 +105,7 @@ Build `switch/ftpd.nro`:
- RMD
- RNFR
- RNTO
- SITE
- SIZE
- STAT
- STOR
@ -121,3 +122,12 @@ Build `switch/ftpd.nro`:
## Planned Commands
- STOU
## SITE commands
- Show help: SITE HELP
- Set username: SITE USER <NAME>
- Set password: SITE PASS <PASS>
- Set port: SITE PORT <PORT>
- Set getMTime: SITE MTIME [0|1]
- Save config: SITE SAVE

View File

@ -26,6 +26,7 @@
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <string_view>
namespace fs
{
@ -82,6 +83,9 @@ public:
/// \note Can return partial reads
ssize_t read (void *data_, std::size_t size_);
/// \brief Read line
std::string_view readLine ();
/// \brief Read data
/// \param data_ Output buffer
/// \param size_ Size to read
@ -109,6 +113,12 @@ private:
/// \brief Buffer size
std::size_t m_bufferSize = 0;
/// \brief Line buffer
char *m_lineBuffer = nullptr;
/// \brief Line buffer size
std::size_t m_lineBufferSize = 0;
};
/// Directory object

100
include/ftpConfig.h Normal file
View File

@ -0,0 +1,100 @@
// 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) 2020 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <cstdint>
#include <memory>
#include <string>
class FtpConfig;
using UniqueFtpConfig = std::unique_ptr<FtpConfig>;
/// \brief FTP config
class FtpConfig
{
public:
~FtpConfig ();
/// \brief Create config
static UniqueFtpConfig create ();
/// \brief Load config
/// \param path_ Path to config file
static UniqueFtpConfig load (char const *path_);
/// \brief Save config
/// \param path_ Path to config file
bool save (char const *path_);
/// \brief Get user
std::string const &user () const;
/// \brief Get password
std::string const &pass () const;
/// \brief Get port
std::uint16_t port () const;
#ifdef _3DS
/// \brief Whether to get mtime
/// \note only effective on 3DS
bool getMTime () const;
#endif
/// \brief Set user
/// \param user_ User
void setUser (std::string const &user_);
/// \brief Set password
/// \param pass_ Password
void setPass (std::string const &pass_);
/// \brief Set listen port
/// \param port_ Listen port
bool setPort (std::string const &port_);
/// \brief Set listen port
/// \param port_ Listen port
bool setPort (std::uint16_t port_);
#ifdef _3DS
/// \brief Set whether to get mtime
/// \param getMTime_ Whether to get mtime
void setGetMTime (bool getMTime_);
#endif
private:
FtpConfig ();
/// \brief Username
std::string m_user;
/// \brief Password
std::string m_pass;
/// \brief Listen port
std::uint16_t m_port;
#ifdef _3DS
/// \brief Whether to get mtime
bool m_getMTime = true;
#endif
};

View File

@ -20,6 +20,7 @@
#pragma once
#include "ftpConfig.h"
#include "ftpSession.h"
#include "platform.h"
#include "socket.h"
@ -44,8 +45,7 @@ public:
void draw ();
/// \brief Create server
/// \param port_ Port to listen on
static UniqueFtpServer create (std::uint16_t port_);
static UniqueFtpServer create ();
/// \brief Get free space
static std::string getFreeSpace ();
@ -58,8 +58,8 @@ public:
private:
/// \brief Paramterized constructor
/// \param port_ Port to listen on
FtpServer (std::uint16_t port_);
/// \param config_ FTP config
FtpServer (UniqueFtpConfig config_);
/// \brief Handle when network is found
void handleNetworkFound ();
@ -67,6 +67,9 @@ private:
/// \brief Handle when network is lost
void handleNetworkLost ();
/// \brief Show menu in the current window
void showMenu ();
/// \brief Server loop
void loop ();
@ -81,6 +84,9 @@ private:
platform::Mutex m_lock;
#endif
/// \brief Config
UniqueFtpConfig m_config;
/// \brief Listen socket
UniqueSocket m_socket;
@ -90,9 +96,25 @@ private:
/// \brief Sessions
std::vector<UniqueFtpSession> m_sessions;
/// \brief Port to listen on
std::uint16_t const m_port;
/// \brief Whether thread should quit
std::atomic<bool> m_quit;
#ifndef CLASSIC
/// \brief Whether to show settings menu
bool m_showSettings = false;
/// \brief User name setting
std::string m_userSetting;
/// \brief Password setting
std::string m_passSetting;
/// \brief Port setting
std::uint16_t m_portSetting;
#ifdef _3DS
/// \brief getMTime setting
bool m_getMTimeSetting;
#endif
#endif
};

View File

@ -21,6 +21,7 @@
#pragma once
#include "fs.h"
#include "ftpConfig.h"
#include "ioBuffer.h"
#include "platform.h"
#include "socket.h"
@ -47,8 +48,9 @@ public:
void draw ();
/// \brief Create session
/// \param config_ FTP config
/// \param commandSocket_ Command socket
static UniqueFtpSession create (UniqueSocket commandSocket_);
static UniqueFtpSession create (FtpConfig &config_, UniqueSocket commandSocket_);
/// \brief Poll for activity
/// \param sessions_ Sessions to poll
@ -122,8 +124,12 @@ private:
};
/// \brief Parameterized constructor
/// \param config_ FTP config
/// \param commandSocket_ Command socket
FtpSession (UniqueSocket commandSocket_);
FtpSession (FtpConfig &config_, UniqueSocket commandSocket_);
/// \brief Whether session is authorized
bool authorized () const;
/// \brief Set session state
/// \param state_ State to set
@ -203,6 +209,9 @@ private:
platform::Mutex m_lock;
#endif
/// \brief FTP config
FtpConfig &m_config;
/// \brief Command socket
SharedSocket m_commandSocket;
@ -278,6 +287,13 @@ private:
/// \brief Directory transfer mode
XferDirMode m_xferDirMode;
/// \brief Last command 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
@ -409,6 +425,10 @@ private:
/// \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_);

View File

@ -23,6 +23,8 @@
#include "imgui.h"
#include "../imgui/imgui_internal.h"
#include "fs.h"
#include "platform.h"
@ -136,6 +138,57 @@ void updateGamepads (ImGuiIO &io_)
io_.NavInputs[out] = std::clamp ((value - min) / (max - min), 0.0f, 1.0f);
}
}
/// \brief Update keyboard inputs
/// \param io_ ImGui IO
void updateKeyboard (ImGuiIO &io_)
{
static enum {
INACTIVE,
KEYBOARD,
CLEARED,
} state = INACTIVE;
switch (state)
{
case INACTIVE:
{
if (!io_.WantTextInput)
return;
auto &textState = ImGui::GetCurrentContext ()->InputTextState;
SwkbdState kbd;
swkbdInit (&kbd, SWKBD_TYPE_NORMAL, 2, -1);
swkbdSetButton (&kbd, SWKBD_BUTTON_LEFT, "Cancel", false);
swkbdSetButton (&kbd, SWKBD_BUTTON_RIGHT, "OK", true);
swkbdSetInitialText (
&kbd, std::string (textState.InitialTextA.Data, textState.InitialTextA.Size).c_str ());
if (textState.UserFlags & ImGuiInputTextFlags_Password)
swkbdSetPasswordMode (&kbd, SWKBD_PASSWORD_HIDE_DELAY);
char buffer[32] = {0};
auto const button = swkbdInputText (&kbd, buffer, sizeof (buffer));
if (button == SWKBD_BUTTON_RIGHT)
io_.AddInputCharactersUTF8 (buffer);
state = KEYBOARD;
break;
}
case KEYBOARD:
// need to skip a frame for active id to really be cleared
ImGui::ClearActiveID ();
state = CLEARED;
break;
case CLEARED:
state = INACTIVE;
break;
}
}
}
bool imgui::ctru::init ()
@ -180,5 +233,6 @@ void imgui::ctru::newFrame ()
updateTouch (io);
updateGamepads (io);
updateKeyboard (io);
}
#endif

View File

@ -23,6 +23,10 @@
#include <cinttypes>
#include <cstdio>
#if defined(NDS) || defined(_3DS) || defined(__SWITCH__)
#define getline __getline
#endif
std::string fs::printSize (std::uint64_t const size_)
{
constexpr std::uint64_t const KiB = 1024;
@ -77,7 +81,10 @@ std::string fs::printSize (std::uint64_t const size_)
}
///////////////////////////////////////////////////////////////////////////
fs::File::~File () = default;
fs::File::~File ()
{
std::free (m_lineBuffer);
}
fs::File::File () = default;
@ -136,6 +143,27 @@ ssize_t fs::File::read (void *const data_, std::size_t const size_)
return std::fread (data_, 1, size_, m_fp.get ());
}
std::string_view fs::File::readLine ()
{
while (true)
{
auto rc = ::getline (&m_lineBuffer, &m_lineBufferSize, m_fp.get ());
if (rc < 0)
return {};
while (rc > 0)
{
if (m_lineBuffer[rc - 1] != '\r' && m_lineBuffer[rc - 1] != '\n')
break;
m_lineBuffer[--rc] = 0;
}
if (rc > 0)
return std::string_view (m_lineBuffer, rc);
}
}
bool fs::File::readAll (void *const data_, std::size_t const size_)
{
auto p = static_cast<char *> (data_);

257
source/ftpConfig.cpp Normal file
View File

@ -0,0 +1,257 @@
// 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) 2020 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 <https://www.gnu.org/licenses/>.
#include "ftpConfig.h"
#include "fs.h"
#include "log.h"
#include <sys/stat.h>
//#include <algorithm>
namespace
{
constexpr std::uint16_t DEFAULT_PORT = 5000;
bool mkdirParent (std::string const &path_)
{
auto pos = path_.find_first_of ('/');
while (pos != std::string::npos)
{
auto const dir = path_.substr (0, pos);
struct stat st;
auto const rc = ::stat (dir.c_str (), &st);
if (rc < 0 && errno != ENOENT)
return false;
if (rc < 0 && errno == ENOENT)
{
auto const rc = ::mkdir (dir.c_str (), 0755);
if (rc < 0)
return false;
}
pos = path_.find_first_of ('/', pos + 1);
}
return true;
}
std::string strip (std::string const &str_)
{
auto const start = str_.find_first_not_of (" \t");
if (start == std::string::npos)
return {};
auto const end = str_.find_last_not_of (" \t");
if (end == std::string::npos)
return str_.substr (start);
return str_.substr (start, end + 1 - start);
}
template <typename T>
bool parseInt (T &out_, std::string const &val_)
{
T val = 0;
for (auto const &c : val_)
{
if (!std::isdigit (c))
{
errno = EINVAL;
return false;
}
if (std::numeric_limits<T>::max () / 10 < val)
{
errno = EOVERFLOW;
return false;
}
val *= 10;
auto const v = c - '0';
if (std::numeric_limits<T>::max () - v < val)
{
errno = EOVERFLOW;
return false;
}
val += v;
}
out_ = val;
return true;
}
}
///////////////////////////////////////////////////////////////////////////
FtpConfig::~FtpConfig () = default;
FtpConfig::FtpConfig () : m_port (DEFAULT_PORT)
{
}
UniqueFtpConfig FtpConfig::create ()
{
return UniqueFtpConfig (new FtpConfig ());
}
UniqueFtpConfig FtpConfig::load (char const *const path_)
{
auto config = create ();
auto fp = fs::File ();
if (!fp.open (path_))
return config;
std::uint16_t port = DEFAULT_PORT;
std::string line;
while (!(line = fp.readLine ()).empty ())
{
auto const pos = line.find_first_of ('=');
if (pos == std::string::npos)
{
error ("Ignoring '%s'\n", line.c_str ());
continue;
}
auto const key = strip (line.substr (0, pos));
auto const val = strip (line.substr (pos + 1));
if (key.empty () || val.empty ())
{
error ("Ignoring '%s'\n", line.c_str ());
continue;
}
if (key == "user")
config->m_user = val;
else if (key == "pass")
config->m_pass = val;
else if (key == "port")
parseInt (port, val);
#ifdef _3DS
else if (key == "mtime")
{
if (val == "0")
config->m_getMTime = false;
else if (val == "1")
config->m_getMTime = true;
else
error ("Invalid value for mtime: %s\n", val.c_str ());
}
#endif
}
config->setPort (port);
return config;
}
bool FtpConfig::save (char const *const path_)
{
if (!mkdirParent (path_))
return false;
auto fp = fs::File ();
if (!fp.open (path_, "wb"))
return false;
if (!m_user.empty ())
std::fprintf (fp, "user=%s\n", m_user.c_str ());
if (!m_pass.empty ())
std::fprintf (fp, "pass=%s\n", m_pass.c_str ());
std::fprintf (fp, "port=%u\n", m_port);
#ifdef _3DS
std::fprintf (fp, "mtime=%u\n", m_getMTime);
#endif
return true;
}
std::string const &FtpConfig::user () const
{
return m_user;
}
std::string const &FtpConfig::pass () const
{
return m_pass;
}
std::uint16_t FtpConfig::port () const
{
return m_port;
}
#ifdef _3DS
bool FtpConfig::getMTime () const
{
return m_getMTime;
}
#endif
void FtpConfig::setUser (std::string const &user_)
{
m_user = user_.substr (0, user_.find_first_of ('\0'));
}
void FtpConfig::setPass (std::string const &pass_)
{
m_pass = pass_.substr (0, pass_.find_first_of ('\0'));
}
bool FtpConfig::setPort (std::string const &port_)
{
std::uint16_t parsed;
if (!parseInt (parsed, port_))
return false;
return setPort (parsed);
}
bool FtpConfig::setPort (std::uint16_t const port_)
{
if (port_ < 1024
#if !defined(NDS) && !defined(_3DS)
&& port_ != 0
#endif
)
{
errno = EPERM;
return false;
}
m_port = port_;
return true;
}
#ifdef _3DS
void FtpConfig::setGetMTime (bool const getMTime_)
{
m_getMTime = getMTime_;
}
#endif

View File

@ -77,7 +77,7 @@ FtpServer::~FtpServer ()
#endif
}
FtpServer::FtpServer (std::uint16_t const port_) : m_port (port_), m_quit (false)
FtpServer::FtpServer (UniqueFtpConfig config_) : m_config (std::move (config_)), m_quit (false)
{
#ifndef NDS
m_thread = platform::Thread (std::bind (&FtpServer::threadFunc, this));
@ -172,12 +172,20 @@ void FtpServer::draw ()
ImGui::Begin (title,
nullptr,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
#ifndef _3DS
| ImGuiWindowFlags_MenuBar
#endif
);
}
#ifndef _3DS
showMenu ();
#endif
#ifndef _3DS
ImGui::BeginChild (
"Logs", ImVec2 (0, 0.55f * height), false, ImGuiWindowFlags_HorizontalScrollbar);
"Logs", ImVec2 (0, 0.5f * height), false, ImGuiWindowFlags_HorizontalScrollbar);
#endif
drawLog ();
#ifndef _3DS
@ -192,7 +200,10 @@ void FtpServer::draw ()
ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height * 0.5f), ImGuiCond_FirstUseEver);
ImGui::Begin ("Sessions",
nullptr,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_MenuBar);
showMenu ();
#else
ImGui::Separator ();
#endif
@ -207,10 +218,13 @@ void FtpServer::draw ()
#endif
}
UniqueFtpServer FtpServer::create (std::uint16_t const port_)
UniqueFtpServer FtpServer::create ()
{
updateFreeSpace ();
return UniqueFtpServer (new FtpServer (port_));
auto config = FtpConfig::load (FTPDCONFIG);
return UniqueFtpServer (new FtpServer (std::move (config)));
}
std::string FtpServer::getFreeSpace ()
@ -256,13 +270,13 @@ void FtpServer::handleNetworkFound ()
#else
addr.sin_addr.s_addr = INADDR_ANY;
#endif
addr.sin_port = htons (m_port);
addr.sin_port = htons (m_config->port ());
auto socket = Socket::create ();
if (!socket)
return;
if (m_port != 0 && !socket->setReuseAddress (true))
if (m_config->port () != 0 && !socket->setReuseAddress (true))
return;
if (!socket->bind (addr))
@ -292,6 +306,115 @@ void FtpServer::handleNetworkLost ()
info ("Stopped server at %s\n", m_name.c_str ());
}
void FtpServer::showMenu ()
{
#ifndef CLASSIC
auto const prevShowSettings = m_showSettings;
if (ImGui::BeginMenuBar ())
{
#if defined(_3DS) || defined(__SWITCH__)
if (ImGui::BeginMenu (u8"Menu \xee\x80\x83")) // Y Button
#else
if (ImGui::BeginMenu ("Menu"))
#endif
{
if (ImGui::MenuItem ("Settings"))
m_showSettings = true;
ImGui::EndMenu ();
}
ImGui::EndMenuBar ();
}
if (!prevShowSettings && m_showSettings)
{
m_userSetting = m_config->user ();
m_userSetting.resize (32);
m_passSetting = m_config->pass ();
m_passSetting.resize (32);
m_portSetting = m_config->port ();
#ifdef _3DS
m_getMTimeSetting = m_config->getMTime ();
#endif
ImGui::OpenPopup ("Settings");
}
#ifdef _3DS
auto const &io = ImGui::GetIO ();
auto const width = io.DisplaySize.x;
auto const height = io.DisplaySize.y;
ImGui::SetNextWindowSize (ImVec2 (width * 0.8f, height * 0.5f));
ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height * 0.5f));
if (ImGui::BeginPopupModal ("Settings",
nullptr,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
#else
if (ImGui::BeginPopupModal ("Settings", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
#endif
{
ImGui::InputText (
"User", &m_userSetting[0], m_userSetting.size (), ImGuiInputTextFlags_AutoSelectAll);
ImGui::InputText ("Pass",
&m_passSetting[0],
m_passSetting.size (),
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_Password);
ImGui::InputScalar ("Port",
ImGuiDataType_U16,
&m_portSetting,
nullptr,
nullptr,
"%u",
ImGuiInputTextFlags_AutoSelectAll);
#ifdef _3DS
ImGui::Checkbox ("Get mtime", &m_getMTimeSetting);
#endif
auto const apply = ImGui::Button ("Apply", ImVec2 (100, 0));
ImGui::SameLine ();
auto const save = ImGui::Button ("Save", ImVec2 (100, 0));
ImGui::SameLine ();
auto const cancel = ImGui::Button ("Cancel", ImVec2 (100, 0));
if (apply || save)
{
m_showSettings = false;
ImGui::CloseCurrentPopup ();
m_config->setUser (m_userSetting);
m_config->setPass (m_passSetting);
m_config->setPort (m_portSetting);
#ifdef _3DS
m_config->setGetMTime (m_getMTimeSetting);
#endif
UniqueSocket socket;
LOCKED (socket = std::move (m_socket));
}
if (save && !m_config->save (FTPDCONFIG))
error ("Failed to save config\n");
if (apply || save || cancel)
{
m_showSettings = false;
ImGui::CloseCurrentPopup ();
}
ImGui::EndPopup ();
}
#endif
}
void FtpServer::loop ()
{
if (!m_socket)
@ -309,7 +432,7 @@ void FtpServer::loop ()
auto socket = m_socket->accept ();
if (socket)
{
auto session = FtpSession::create (std::move (socket));
auto session = FtpSession::create (*m_config, std::move (socket));
LOCKED (m_sessions.emplace_back (std::move (session)));
}
else

View File

@ -262,11 +262,14 @@ FtpSession::~FtpSession ()
closeData ();
}
FtpSession::FtpSession (UniqueSocket commandSocket_)
: m_commandSocket (std::move (commandSocket_)),
FtpSession::FtpSession (FtpConfig &config_, UniqueSocket commandSocket_)
: m_config (config_),
m_commandSocket (std::move (commandSocket_)),
m_commandBuffer (COMMAND_BUFFERSIZE),
m_responseBuffer (RESPONSE_BUFFERSIZE),
m_xferBuffer (XFER_BUFFERSIZE),
m_authorizedUser (false),
m_authorizedPass (false),
m_pasv (false),
m_port (false),
m_recv (false),
@ -278,6 +281,11 @@ FtpSession::FtpSession (UniqueSocket commandSocket_)
m_mlstPerm (true),
m_mlstUnixMode (false)
{
if (m_config.user ().empty ())
m_authorizedUser = true;
if (m_config.pass ().empty ())
m_authorizedPass = true;
char buffer[32];
std::sprintf (buffer, "Session#%p", this);
m_windowName = buffer;
@ -373,9 +381,9 @@ void FtpSession::draw ()
#endif
}
UniqueFtpSession FtpSession::create (UniqueSocket commandSocket_)
UniqueFtpSession FtpSession::create (FtpConfig &config_, UniqueSocket commandSocket_)
{
return UniqueFtpSession (new FtpSession (std::move (commandSocket_)));
return UniqueFtpSession (new FtpSession (config_, std::move (commandSocket_)));
}
bool FtpSession::poll (std::vector<UniqueFtpSession> const &sessions_)
@ -557,8 +565,11 @@ bool FtpSession::poll (std::vector<UniqueFtpSession> const &sessions_)
}
else if (i.revents & (POLLIN | POLLOUT))
{
while (((*session).*(session->m_transfer)) ())
;
for (unsigned i = 0; i < 10; ++i)
{
if (!((*session).*(session->m_transfer)) ())
break;
}
}
}
}
@ -568,6 +579,11 @@ bool FtpSession::poll (std::vector<UniqueFtpSession> const &sessions_)
return true;
}
bool FtpSession::authorized () const
{
return m_authorizedUser && m_authorizedPass;
}
void FtpSession::setState (State const state_, bool const closePasv_, bool const closeData_)
{
m_state = state_;
@ -989,8 +1005,10 @@ int FtpSession::fillDirent (struct stat const &st_, std::string_view const path_
if (!tm)
return errno;
auto fmt = "%b %e %H:%M ";
rc = std::strftime (&buffer[pos], size - pos, fmt, tm);
auto fmt = "%b %e %Y ";
if (m_timestamp > st_.st_mtime && m_timestamp - st_.st_mtime < (60 * 60 * 24 * 365 / 2))
fmt = "%b %e %H:%M ";
rc = std::strftime (&buffer[pos], size - pos, fmt, tm);
if (rc < 0)
return errno;
if (static_cast<std::size_t> (rc) > size - pos)
@ -1009,6 +1027,7 @@ int FtpSession::fillDirent (struct stat const &st_, std::string_view const path_
buffer[pos++] = '\n';
m_xferBuffer.markUsed (pos);
LOCKED (m_filePosition += pos);
return 0;
}
@ -1145,6 +1164,7 @@ void FtpSession::xferDir (char const *const args_, XferDirMode const mode_, bool
m_recv = false;
m_send = true;
m_filePosition = 0;
m_xferBuffer.clear ();
m_transfer = &FtpSession::listTransfer;
@ -1233,7 +1253,7 @@ void FtpSession::xferDir (char const *const args_, XferDirMode const mode_, bool
// everything else uses basename
auto const pos = path.find_last_of ('/');
assert (pos != std::string::npos);
name = encodePath (std::string_view (path).substr (pos));
name = encodePath (std::string_view (path).substr (pos + 1));
}
auto const rc = fillDirent (st, name);
@ -1420,6 +1440,7 @@ void FtpSession::readCommand (int const events_)
return ::strcasecmp (lhs_.first.data (), rhs_) < 0;
});
m_timestamp = std::time (nullptr);
if (it == std::end (handlers) || ::strcasecmp (it->first.data (), command) != 0)
{
std::string response = "502 Invalid command \"";
@ -1585,13 +1606,17 @@ bool FtpSession::listTransfer ()
if (m_xferDirMode == XferDirMode::NLST)
{
// NLST gives the whole path name
auto const path = encodePath (buildPath (m_lwd, dent->d_name));
auto const path = encodePath (buildPath (m_lwd, dent->d_name)) + "\r\n";
if (m_xferBuffer.freeSize () < path.size ())
{
sendResponse ("501 %s\r\n", std::strerror (ENOMEM));
setState (State::COMMAND, true, true);
return false;
}
std::memcpy (m_xferBuffer.freeArea (), path.data (), path.size ());
m_xferBuffer.markUsed (path.size ());
LOCKED (m_filePosition += path.size ());
}
else
{
@ -1629,7 +1654,7 @@ bool FtpSession::listTransfer ()
else if (m_xferDirMode == XferDirMode::NLST)
getmtime = false;
if (getmtime)
if (getmtime && m_config.getMTime ())
{
std::uint64_t mtime = 0;
auto const rc = archive_getmtime (fullPath.c_str (), &mtime);
@ -1801,6 +1826,13 @@ void FtpSession::ALLO (char const *args_)
void FtpSession::APPE (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// open the file in append mode
xferFile (args_, XferFileMode::APPE);
}
@ -1809,6 +1841,12 @@ void FtpSession::CDUP (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
if (!changeDir (".."))
{
sendResponse ("550 %s\r\n", std::strerror (errno));
@ -1822,6 +1860,12 @@ void FtpSession::CWD (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
if (!changeDir (args_))
{
sendResponse ("550 %s\r\n", std::strerror (errno));
@ -1835,6 +1879,12 @@ void FtpSession::DELE (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
// build the path to remove
auto const path = buildResolvedPath (m_cwd, args_);
if (path.empty ())
@ -1885,6 +1935,13 @@ void FtpSession::HELP (char const *args_)
void FtpSession::LIST (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// open the path in LIST mode
xferDir (args_, XferDirMode::LIST, true);
}
@ -1892,6 +1949,13 @@ void FtpSession::LIST (char const *args_)
void FtpSession::MDTM (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
sendResponse ("502 Command not implemented\r\n");
}
@ -1899,6 +1963,12 @@ void FtpSession::MKD (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
// build the path to create
auto const path = buildResolvedPath (m_cwd, args_);
if (path.empty ())
@ -1920,6 +1990,13 @@ void FtpSession::MKD (char const *args_)
void FtpSession::MLSD (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// open the path in MLSD mode
xferDir (args_, XferDirMode::MLSD, true);
}
@ -1977,6 +2054,13 @@ void FtpSession::MODE (char const *args_)
void FtpSession::NLST (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// open the path in NLST mode
xferDir (args_, XferDirMode::NLST, false);
}
@ -2048,11 +2132,34 @@ void FtpSession::OPTS (char const *args_)
void FtpSession::PASS (char const *args_)
{
setState (State::COMMAND, false, false);
sendResponse ("230 OK\r\n");
m_authorizedPass = false;
if (!m_config.user ().empty () && !m_authorizedUser)
{
sendResponse ("430 User not authorized\r\n");
return;
}
if (m_config.pass ().empty () || m_config.pass () == args_)
{
m_authorizedPass = true;
sendResponse ("230 OK\r\n");
return;
}
sendResponse ("430 Invalid password\r\n");
}
void FtpSession::PASV (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// reset state
setState (State::COMMAND, true, true);
m_pasv = false;
@ -2117,6 +2224,13 @@ void FtpSession::PASV (char const *args_)
void FtpSession::PORT (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// reset state
setState (State::COMMAND, true, true);
m_pasv = false;
@ -2203,6 +2317,12 @@ void FtpSession::PWD (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
auto const path = encodePath (m_cwd);
std::string response = "257 \"";
@ -2222,6 +2342,12 @@ void FtpSession::REST (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
// parse the offset
std::uint64_t pos = 0;
for (auto p = args_; *p; ++p)
@ -2250,6 +2376,13 @@ void FtpSession::REST (char const *args_)
void FtpSession::RETR (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// open the file to retrieve
xferFile (args_, XferFileMode::RETR);
}
@ -2258,6 +2391,12 @@ void FtpSession::RMD (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
// build the path to remove
auto const path = buildResolvedPath (m_cwd, args_);
if (path.empty ())
@ -2281,6 +2420,12 @@ void FtpSession::RNFR (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
// build the path to rename from
auto const path = buildResolvedPath (m_cwd, args_);
if (path.empty ())
@ -2306,6 +2451,12 @@ void FtpSession::RNTO (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
// make sure the previous command was RNFR
if (m_rename.empty ())
{
@ -2337,10 +2488,99 @@ void FtpSession::RNTO (char const *args_)
sendResponse ("250 OK\r\n");
}
void FtpSession::SITE (char const *args_)
{
setState (State::COMMAND, false, false);
auto const str = std::string (args_);
auto const pos = str.find_first_of (' ');
auto const command = str.substr (0, pos);
auto const arg = pos == std::string::npos ? std::string () : str.substr (pos + 1);
if (::strcasecmp (command.c_str (), "HELP") == 0)
{
sendResponse ("211-\r\n"
" Show this help: SITE HELP\r\n"
" Set username: SITE USER <NAME>\r\n"
" Set password: SITE PASS <PASS>\r\n"
" Set port: SITE PORT <PORT>\r\n"
#ifdef _3DS
" Set getMTime: SITE MTIME [0|1]\r\n"
#endif
" Save config: SITE SAVE\r\n"
"211 End\r\n");
return;
}
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
if (::strcasecmp (command.c_str (), "USER") == 0)
{
m_config.setUser (arg);
sendResponse ("200 OK\r\n");
return;
}
else if (::strcasecmp (command.c_str (), "PASS") == 0)
{
m_config.setPass (arg);
sendResponse ("200 OK\r\n");
return;
}
else if (::strcasecmp (command.c_str (), "PORT") == 0)
{
if (!m_config.setPort (arg))
{
sendResponse ("550 %s\r\n", std::strerror (errno));
return;
}
sendResponse ("200 OK\r\n");
return;
}
#ifdef _3DS
else if (::strcasecmp (command.c_str (), "MTIME") == 0)
{
if (arg == "0")
m_config.setGetMTime (false);
else if (arg == "1")
m_config.setGetMTime (true);
else
{
sendResponse ("550 %s\r\n", std::strerror (EINVAL));
return;
}
}
#endif
else if (::strcasecmp (command.c_str (), "SAVE") == 0)
{
if (!m_config.save (FTPDCONFIG))
{
sendResponse ("550 %s\r\n", std::strerror (errno));
return;
}
sendResponse ("200 OK\r\n");
return;
}
sendResponse ("550 Invalid command\r\n");
}
void FtpSession::SIZE (char const *args_)
{
setState (State::COMMAND, false, false);
if (!authorized ())
{
sendResponse ("530 Not logged in\r\n");
return;
}
// build the path to stat
auto const path = buildResolvedPath (m_cwd, args_);
if (path.empty ())
@ -2371,7 +2611,7 @@ void FtpSession::STAT (char const *args_)
if (m_state == State::DATA_CONNECT)
{
sendResponse ("211-FTP server status\r\n"
" Waitin for data connection\r\n"
" Waiting for data connection\r\n"
"211 End\r\n");
return;
}
@ -2404,11 +2644,25 @@ void FtpSession::STAT (char const *args_)
return;
}
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
xferDir (args_, XferDirMode::STAT, false);
}
void FtpSession::STOR (char const *args_)
{
if (!authorized ())
{
setState (State::COMMAND, false, false);
sendResponse ("530 Not logged in\r\n");
return;
}
// open the file to store
xferFile (args_, XferFileMode::STOR);
}
@ -2450,7 +2704,23 @@ void FtpSession::TYPE (char const *args_)
void FtpSession::USER (char const *args_)
{
setState (State::COMMAND, false, false);
sendResponse ("230 OK\r\n");
m_authorizedUser = false;
if (m_config.user ().empty () || m_config.user () == args_)
{
m_authorizedUser = true;
if (m_config.pass ().empty ())
{
sendResponse ("230 OK\r\n");
return;
}
sendResponse ("331 Need password\r\n");
return;
}
sendResponse ("502 Invalid user\r\n");
}
// clang-format off
@ -2484,6 +2754,7 @@ std::vector<std::pair<std::string_view, void (FtpSession::*) (char const *)>> co
{"RMD", &FtpSession::RMD},
{"RNFR", &FtpSession::RNFR},
{"RNTO", &FtpSession::RNTO},
{"SITE", &FtpSession::SITE},
{"SIZE", &FtpSession::SIZE},
{"STAT", &FtpSession::STAT},
{"STOR", &FtpSession::STOR},

View File

@ -49,7 +49,7 @@ int main (int argc_, char *argv_[])
style.WindowRounding = 0.0f;
#endif
auto server = FtpServer::create (5000);
auto server = FtpServer::create ();
while (platform::loop ())
{

View File

@ -23,6 +23,8 @@
#include "imgui.h"
#include "../imgui/imgui_internal.h"
#include "fs.h"
#include "platform.h"
@ -1376,6 +1378,46 @@ void updateKeyboard (ImGuiIO &io_)
for (int i = 0; i < 256; ++i)
io_.KeysDown[i] = hidKeyboardHeld (static_cast<HidKeyboardScancode> (i));
static enum {
INACTIVE,
KEYBOARD,
CLEARED,
} state = INACTIVE;
switch (state)
{
case INACTIVE:
{
if (!io_.WantTextInput)
return;
auto &textState = ImGui::GetCurrentContext ()->InputTextState;
SwkbdConfig kbd;
swkbdCreate (&kbd, 0);
swkbdConfigMakePresetDefault (&kbd);
swkbdConfigSetInitialText (
&kbd, std::string (textState.InitialTextA.Data, textState.InitialTextA.Size).c_str ());
char buffer[32];
if (R_SUCCEEDED (swkbdShow (&kbd, buffer, sizeof (buffer))))
io_.AddInputCharactersUTF8 (buffer);
state = KEYBOARD;
break;
}
case KEYBOARD:
// need to skip a frame for active id to really be cleared
ImGui::ClearActiveID ();
state = CLEARED;
break;
case CLEARED:
state = INACTIVE;
break;
}
}
}

View File

@ -41,7 +41,7 @@ static SocketInitConfig const s_socketInitConfig = {
.sb_efficiency = 8,
.num_bsd_sessions = 1,
.num_bsd_sessions = 2,
.bsd_service_type = BsdServiceType_User,
};