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"
|
2020-04-08 23:53:47 +02:00
|
|
|
#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>
|
|
|
|
#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
|
2020-04-08 23:53:47 +02:00
|
|
|
#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-08 23:53:47 +02:00
|
|
|
|
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-10 04:21:25 +02:00
|
|
|
FtpServer::FtpServer (std::uint16_t const port_) : m_port (port_), 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
|
|
|
|
{
|
|
|
|
#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);
|
|
|
|
std::fflush (stdout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
2020-04-07 05:38:14 +02:00
|
|
|
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
|
2020-04-08 01:05:51 +02:00
|
|
|
ImGui::SetNextWindowSize (ImVec2 (width, height * 0.5f));
|
2020-04-05 21:16:16 +02:00
|
|
|
#else
|
|
|
|
ImGui::SetNextWindowSize (ImVec2 (width, height));
|
|
|
|
#endif
|
|
|
|
ImGui::Begin (STATUS_STRING,
|
|
|
|
nullptr,
|
|
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
|
|
|
|
|
|
|
|
{
|
|
|
|
auto const lock = std::scoped_lock (s_lock);
|
|
|
|
if (!s_freeSpace.empty ())
|
|
|
|
{
|
|
|
|
ImGui::SameLine ();
|
|
|
|
ImGui::TextUnformatted (s_freeSpace.c_str ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 23:53:47 +02:00
|
|
|
{
|
|
|
|
ImGui::SameLine ();
|
|
|
|
auto const lock = std::scoped_lock (m_lock);
|
|
|
|
if (m_socket)
|
|
|
|
ImGui::TextUnformatted (m_name.c_str ());
|
|
|
|
else
|
|
|
|
ImGui::TextUnformatted ("Waiting for network...");
|
|
|
|
}
|
|
|
|
|
2020-04-05 21:16:16 +02:00
|
|
|
ImGui::Separator ();
|
|
|
|
|
|
|
|
#ifdef _3DS
|
2020-04-06 07:36:03 +02:00
|
|
|
// Fill rest of top screen window
|
2020-04-05 21:16:16 +02:00
|
|
|
ImGui::BeginChild ("Logs", ImVec2 (0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
|
|
|
#else
|
|
|
|
ImGui::BeginChild ("Logs", ImVec2 (0, 200), false, ImGuiWindowFlags_HorizontalScrollbar);
|
|
|
|
#endif
|
2020-04-10 04:21:25 +02:00
|
|
|
drawLog ();
|
2020-04-05 21:16:16 +02:00
|
|
|
ImGui::EndChild ();
|
|
|
|
|
|
|
|
#ifdef _3DS
|
|
|
|
ImGui::End ();
|
|
|
|
|
|
|
|
// bottom screen
|
2020-04-08 01:05:51 +02:00
|
|
|
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,
|
|
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
|
|
|
|
#else
|
|
|
|
ImGui::Separator ();
|
|
|
|
#endif
|
|
|
|
|
2020-04-08 23:53:47 +02:00
|
|
|
{
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
UniqueFtpServer FtpServer::create (std::uint16_t const port_)
|
|
|
|
{
|
|
|
|
updateFreeSpace ();
|
|
|
|
return UniqueFtpServer (new FtpServer (port_));
|
|
|
|
}
|
|
|
|
|
|
|
|
void FtpServer::updateFreeSpace ()
|
|
|
|
{
|
|
|
|
#if defined(_3DS) || defined(__SWITCH__)
|
|
|
|
struct statvfs st;
|
|
|
|
if (::statvfs ("sdmc:/", &st) != 0)
|
|
|
|
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-05 21:16:16 +02:00
|
|
|
auto const lock = std::scoped_lock (s_lock);
|
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
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
std::time_t FtpServer::startTime ()
|
|
|
|
{
|
|
|
|
return s_startTime;
|
|
|
|
}
|
|
|
|
|
2020-04-08 23:53:47 +02:00
|
|
|
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
|
|
|
|
addr.sin_port = htons (m_port);
|
|
|
|
|
|
|
|
auto socket = Socket::create ();
|
|
|
|
if (!socket)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_port != 0 && !socket->setReuseAddress (true))
|
|
|
|
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
|
|
|
|
2020-04-08 23:53:47 +02:00
|
|
|
LOCKED (m_socket = std::move (socket));
|
2020-04-05 21:16:16 +02:00
|
|
|
}
|
|
|
|
|
2020-04-08 23:53:47 +02:00
|
|
|
void FtpServer::handleNetworkLost ()
|
2020-04-05 21:16:16 +02:00
|
|
|
{
|
2020-04-08 23:53:47 +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
|
|
|
}
|
|
|
|
|
|
|
|
void FtpServer::loop ()
|
|
|
|
{
|
2020-04-09 06:36:29 +02:00
|
|
|
if (!m_socket)
|
|
|
|
{
|
|
|
|
if (platform::networkVisible ())
|
|
|
|
handleNetworkFound ();
|
|
|
|
}
|
2020-04-08 23:53:47 +02:00
|
|
|
|
|
|
|
// poll listen socket
|
|
|
|
if (m_socket)
|
2020-04-05 21:16:16 +02:00
|
|
|
{
|
2020-04-08 23:53:47 +02:00
|
|
|
Socket::PollInfo info{*m_socket, POLLIN, 0};
|
|
|
|
if (Socket::poll (&info, 1, 0ms) > 0)
|
2020-04-05 21:16:16 +02:00
|
|
|
{
|
2020-04-08 23:53:47 +02:00
|
|
|
auto socket = m_socket->accept ();
|
|
|
|
if (socket)
|
2020-04-05 21:16:16 +02:00
|
|
|
{
|
2020-04-08 23:53:47 +02:00
|
|
|
auto session = FtpSession::create (std::move (socket));
|
|
|
|
LOCKED (m_sessions.emplace_back (std::move (session)));
|
2020-04-05 21:16:16 +02:00
|
|
|
}
|
2020-04-08 23:53:47 +02:00
|
|
|
else
|
|
|
|
handleNetworkLost ();
|
2020-04-05 21:16:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2020-04-08 23:53:47 +02:00
|
|
|
std::vector<UniqueFtpSession> deadSessions;
|
|
|
|
{
|
|
|
|
// remove dead sessions
|
2020-04-17 22:32:39 +02:00
|
|
|
#ifndef NDS
|
2020-04-08 23:53:47 +02:00
|
|
|
auto lock = std::scoped_lock (m_lock);
|
2020-04-17 22:32:39 +02:00
|
|
|
#endif
|
|
|
|
auto it = std::begin (m_sessions);
|
2020-04-08 23:53:47 +02:00
|
|
|
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))
|
2020-04-08 23:53:47 +02:00
|
|
|
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 ();
|
|
|
|
}
|