ftpiiu_plugin/source/ftpServer.cpp

483 lines
11 KiB
C++
Raw Normal View History

2020-04-05 21:16:16 +02:00
// 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 "ftpServer.h"
#include "fs.h"
2020-04-10 04:21:25 +02:00
#include "log.h"
#include "platform.h"
2020-04-17 22:32:39 +02:00
#include "socket.h"
2020-04-05 21:16:16 +02:00
#include "imgui.h"
2020-04-17 22:32:39 +02:00
#ifdef NDS
#include <dswifi9.h>
#endif
2020-04-05 21:16:16 +02:00
#include <arpa/inet.h>
2020-04-23 00:15:23 +02:00
#include <sys/statvfs.h>
2020-04-05 21:16:16 +02:00
#include <unistd.h>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <thread>
using namespace std::chrono_literals;
2020-04-17 22:32:39 +02:00
#ifdef NDS
#define LOCKED(x) x
#else
#define LOCKED(x) \
do \
{ \
auto const lock = std::scoped_lock (m_lock); \
x; \
} while (0)
2020-04-17 22:32:39 +02:00
#endif
2020-04-05 21:16:16 +02:00
namespace
{
2020-04-06 07:36:03 +02:00
/// \brief Application start time
auto const s_startTime = std::time (nullptr);
2020-04-17 22:32:39 +02:00
#ifndef NDS
2020-04-06 07:36:03 +02:00
/// \brief Mutex for s_freeSpace
2020-04-05 21:16:16 +02:00
platform::Mutex s_lock;
2020-04-17 22:32:39 +02:00
#endif
2020-04-06 07:36:03 +02:00
/// \brief Free space string
2020-04-05 21:16:16 +02:00
std::string s_freeSpace;
}
///////////////////////////////////////////////////////////////////////////
FtpServer::~FtpServer ()
{
m_quit = true;
2020-04-17 22:32:39 +02:00
#ifndef NDS
2020-04-05 21:16:16 +02:00
m_thread.join ();
2020-04-17 22:32:39 +02:00
#endif
2020-04-05 21:16:16 +02:00
}
2020-04-24 22:51:43 +02:00
FtpServer::FtpServer (UniqueFtpConfig config_) : m_config (std::move (config_)), m_quit (false)
2020-04-05 21:16:16 +02:00
{
2020-04-17 22:32:39 +02:00
#ifndef NDS
2020-04-05 21:16:16 +02:00
m_thread = platform::Thread (std::bind (&FtpServer::threadFunc, this));
2020-04-17 22:32:39 +02:00
#endif
2020-04-05 21:16:16 +02:00
}
void FtpServer::draw ()
{
2020-04-17 22:32:39 +02:00
#ifdef NDS
loop ();
#endif
#ifdef CLASSIC
2020-04-23 00:15:23 +02:00
{
char port[7];
#ifndef NDS
auto lock = std::scoped_lock (m_lock);
#endif
if (m_socket)
std::sprintf (port, ":%u", m_socket->sockName ().port ());
consoleSelect (&g_statusConsole);
std::printf ("\x1b[0;0H\x1b[32;1m%s \x1b[36;1m%s%s",
STATUS_STRING,
m_socket ? m_socket->sockName ().name () : "Waiting on WiFi",
m_socket ? port : "");
#ifndef NDS
char timeBuffer[16];
auto const now = std::time (nullptr);
std::strftime (timeBuffer, sizeof (timeBuffer), "%H:%M:%S", std::localtime (&now));
std::printf (" \x1b[37;1m%s", timeBuffer);
#endif
std::fputs ("\x1b[K", stdout);
std::fflush (stdout);
}
2020-04-17 22:32:39 +02:00
{
#ifndef NDS
auto const lock = std::scoped_lock (s_lock);
#endif
if (!s_freeSpace.empty ())
{
consoleSelect (&g_statusConsole);
std::printf ("\x1b[0;%uH\x1b[32;1m%s",
static_cast<unsigned> (g_statusConsole.windowWidth - s_freeSpace.size () + 1),
s_freeSpace.c_str ());
std::fflush (stdout);
}
}
{
#ifndef NDS
auto lock = std::scoped_lock (m_lock);
#endif
consoleSelect (&g_sessionConsole);
std::fputs ("\x1b[2J", stdout);
for (auto &session : m_sessions)
{
session->draw ();
if (&session != &m_sessions.back ())
std::fputc ('\n', stdout);
}
2020-04-23 00:15:23 +02:00
std::fflush (stdout);
2020-04-17 22:32:39 +02:00
}
2020-04-23 00:15:23 +02:00
drawLog ();
2020-04-17 22:32:39 +02:00
#else
auto const &io = ImGui::GetIO ();
2020-04-05 21:16:16 +02:00
auto const width = io.DisplaySize.x;
auto const height = io.DisplaySize.y;
ImGui::SetNextWindowPos (ImVec2 (0, 0), ImGuiCond_FirstUseEver);
#ifdef _3DS
// top screen
ImGui::SetNextWindowSize (ImVec2 (width, height * 0.5f));
2020-04-05 21:16:16 +02:00
#else
ImGui::SetNextWindowSize (ImVec2 (width, height));
#endif
{
2020-04-23 00:15:23 +02:00
char title[64];
2020-04-05 21:16:16 +02:00
{
2020-04-23 00:15:23 +02:00
auto const serverLock = std::scoped_lock (m_lock);
std::snprintf (title,
sizeof (title),
STATUS_STRING " %s###ftpd",
m_socket ? m_name.c_str () : "Waiting for WiFi...");
2020-04-05 21:16:16 +02:00
}
2020-04-23 00:15:23 +02:00
ImGui::Begin (title,
nullptr,
2020-04-24 22:51:43 +02:00
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
#ifndef _3DS
| ImGuiWindowFlags_MenuBar
#endif
);
}
2020-04-24 22:51:43 +02:00
#ifndef _3DS
showMenu ();
#endif
2020-04-23 00:15:23 +02:00
#ifndef _3DS
2020-04-23 07:25:05 +02:00
ImGui::BeginChild (
2020-04-24 22:51:43 +02:00
"Logs", ImVec2 (0, 0.5f * height), false, ImGuiWindowFlags_HorizontalScrollbar);
2020-04-05 21:16:16 +02:00
#endif
2020-04-10 04:21:25 +02:00
drawLog ();
2020-04-23 00:15:23 +02:00
#ifndef _3DS
2020-04-05 21:16:16 +02:00
ImGui::EndChild ();
2020-04-23 00:15:23 +02:00
#endif
2020-04-05 21:16:16 +02:00
#ifdef _3DS
ImGui::End ();
// bottom screen
ImGui::SetNextWindowSize (ImVec2 (width * 0.8f, height * 0.5f));
ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height * 0.5f), ImGuiCond_FirstUseEver);
2020-04-05 21:16:16 +02:00
ImGui::Begin ("Sessions",
nullptr,
2020-04-24 22:51:43 +02:00
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_MenuBar);
showMenu ();
2020-04-05 21:16:16 +02:00
#else
ImGui::Separator ();
#endif
{
auto lock = std::scoped_lock (m_lock);
for (auto &session : m_sessions)
session->draw ();
}
2020-04-05 21:16:16 +02:00
ImGui::End ();
2020-04-17 22:32:39 +02:00
#endif
2020-04-05 21:16:16 +02:00
}
2020-04-24 22:51:43 +02:00
UniqueFtpServer FtpServer::create ()
2020-04-05 21:16:16 +02:00
{
updateFreeSpace ();
2020-04-24 22:51:43 +02:00
auto config = FtpConfig::load (FTPDCONFIG);
return UniqueFtpServer (new FtpServer (std::move (config)));
2020-04-05 21:16:16 +02:00
}
2020-04-23 00:15:23 +02:00
std::string FtpServer::getFreeSpace ()
{
#ifndef NDS
auto const lock = std::scoped_lock (s_lock);
#endif
return s_freeSpace;
}
2020-04-05 21:16:16 +02:00
void FtpServer::updateFreeSpace ()
{
struct statvfs st;
2020-04-23 00:15:23 +02:00
#if defined(NDS) || defined(_3DS) || defined(__SWITCH__)
2020-04-05 21:16:16 +02:00
if (::statvfs ("sdmc:/", &st) != 0)
2020-04-23 00:15:23 +02:00
#else
if (::statvfs ("/", &st) != 0)
#endif
2020-04-05 21:16:16 +02:00
return;
2020-04-17 22:32:39 +02:00
auto freeSpace = fs::printSize (static_cast<std::uint64_t> (st.f_bsize) * st.f_bfree);
2020-04-23 00:15:23 +02:00
#ifndef NDS
2020-04-05 21:16:16 +02:00
auto const lock = std::scoped_lock (s_lock);
2020-04-23 00:15:23 +02:00
#endif
2020-04-17 22:32:39 +02:00
if (freeSpace != s_freeSpace)
s_freeSpace = std::move (freeSpace);
2020-04-05 21:16:16 +02:00
}
std::time_t FtpServer::startTime ()
{
return s_startTime;
}
void FtpServer::handleNetworkFound ()
2020-04-05 21:16:16 +02:00
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
2020-04-17 22:32:39 +02:00
#if defined(NDS)
addr.sin_addr = Wifi_GetIPInfo (nullptr, nullptr, nullptr, nullptr);
#elif defined(_3DS) || defined(__SWITCH__)
2020-04-05 21:16:16 +02:00
addr.sin_addr.s_addr = gethostid ();
#else
addr.sin_addr.s_addr = INADDR_ANY;
#endif
2020-04-24 22:51:43 +02:00
addr.sin_port = htons (m_config->port ());
2020-04-05 21:16:16 +02:00
auto socket = Socket::create ();
if (!socket)
return;
2020-04-24 22:51:43 +02:00
if (m_config->port () != 0 && !socket->setReuseAddress (true))
2020-04-05 21:16:16 +02:00
return;
if (!socket->bind (addr))
return;
if (!socket->listen (10))
return;
auto const &sockName = socket->sockName ();
auto const name = sockName.name ();
m_name.resize (std::strlen (name) + 3 + 5);
m_name.resize (std::sprintf (&m_name[0], "[%s]:%u", name, sockName.port ()));
2020-04-10 04:21:25 +02:00
info ("Started server at %s\n", m_name.c_str ());
2020-04-05 21:16:16 +02:00
LOCKED (m_socket = std::move (socket));
2020-04-05 21:16:16 +02:00
}
void FtpServer::handleNetworkLost ()
2020-04-05 21:16:16 +02:00
{
{
UniqueSocket sock;
LOCKED (sock = std::move (m_socket));
}
2020-04-10 04:21:25 +02:00
info ("Stopped server at %s\n", m_name.c_str ());
2020-04-05 21:16:16 +02:00
}
2020-04-24 22:51:43 +02:00
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
}
2020-04-05 21:16:16 +02:00
void FtpServer::loop ()
{
if (!m_socket)
{
if (platform::networkVisible ())
handleNetworkFound ();
}
// poll listen socket
if (m_socket)
2020-04-05 21:16:16 +02:00
{
Socket::PollInfo info{*m_socket, POLLIN, 0};
if (Socket::poll (&info, 1, 0ms) > 0)
2020-04-05 21:16:16 +02:00
{
auto socket = m_socket->accept ();
if (socket)
2020-04-05 21:16:16 +02:00
{
2020-04-24 22:51:43 +02:00
auto session = FtpSession::create (*m_config, std::move (socket));
LOCKED (m_sessions.emplace_back (std::move (session)));
2020-04-05 21:16:16 +02:00
}
else
handleNetworkLost ();
2020-04-05 21:16:16 +02:00
}
}
{
std::vector<UniqueFtpSession> deadSessions;
{
// remove dead sessions
2020-04-17 22:32:39 +02:00
#ifndef NDS
auto lock = std::scoped_lock (m_lock);
2020-04-17 22:32:39 +02:00
#endif
auto it = std::begin (m_sessions);
while (it != std::end (m_sessions))
{
auto &session = *it;
if (session->dead ())
{
deadSessions.emplace_back (std::move (session));
it = m_sessions.erase (it);
}
else
++it;
}
}
2020-04-05 21:16:16 +02:00
}
2020-04-06 07:36:03 +02:00
// poll sessions
2020-04-05 21:16:16 +02:00
if (!m_sessions.empty ())
2020-04-07 04:17:30 +02:00
{
if (!FtpSession::poll (m_sessions))
handleNetworkLost ();
2020-04-07 04:17:30 +02:00
}
2020-04-17 22:32:39 +02:00
#ifndef NDS
2020-04-06 07:36:03 +02:00
// avoid busy polling in background thread
2020-04-05 21:16:16 +02:00
else
platform::Thread::sleep (16ms);
2020-04-17 22:32:39 +02:00
#endif
2020-04-05 21:16:16 +02:00
}
void FtpServer::threadFunc ()
{
while (!m_quit)
loop ();
}