// 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 . #include "ftpConfig.h" #include "fs.h" #include "log.h" #include "platform.h" #include #include using stat_t = struct stat; #include #include #include #include #include #include #include #include #include #include namespace { #if defined(__WIIU__) && defined(__WUPS__) constexpr std::uint16_t DEFAULT_PORT = 21; #else constexpr std::uint16_t DEFAULT_PORT = 5000; #endif bool mkdirParent (std::string_view const path_) { auto pos = path_.find_first_of ('/'); while (pos != std::string::npos) { auto const dir = std::string (path_.substr (0, pos)); stat_t 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_view strip (std::string_view 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 bool parseInt (T &out_, std::string_view const val_) { auto const rc = std::from_chars (val_.data (), val_.data () + val_.size (), out_); if (rc.ec != std::errc{}) { errno = static_cast (rc.ec); return false; } if (rc.ptr != val_.data () + val_.size ()) { errno = EINVAL; return false; } return true; } } /////////////////////////////////////////////////////////////////////////// FtpConfig::~FtpConfig () = default; FtpConfig::FtpConfig () : m_port (DEFAULT_PORT) { } UniqueFtpConfig FtpConfig::create () { return UniqueFtpConfig (new FtpConfig ()); } UniqueFtpConfig FtpConfig::load (gsl::not_null 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 (std::string_view (line).substr (0, pos)); auto const val = strip (std::string_view (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", gsl::narrow_cast (val.size ()), val.data ()); } #endif #ifdef __SWITCH__ else if (key == "ap") { if (val == "0") config->m_enableAP = false; else if (val == "1") config->m_enableAP = true; else error ("Invalid value for ap: %.*s\n", gsl::narrow_cast (val.size ()), val.data ()); } else if (key == "ssid") config->m_ssid = val; else if (key == "passphrase") config->m_passphrase = val; #endif } config->setPort (port); return config; } #ifndef __NDS__ std::scoped_lock FtpConfig::lockGuard () { return std::scoped_lock (m_lock); } #endif bool FtpConfig::save (gsl::not_null const path_) { if (!mkdirParent (path_.get ())) return false; auto fp = fs::File (); if (!fp.open (path_, "wb")) return false; if (!m_user.empty ()) (void)std::fprintf (fp, "user=%s\n", m_user.c_str ()); if (!m_pass.empty ()) (void)std::fprintf (fp, "pass=%s\n", m_pass.c_str ()); (void)std::fprintf (fp, "port=%u\n", m_port); #ifdef __3DS__ (void)std::fprintf (fp, "mtime=%u\n", m_getMTime); #endif #ifdef __SWITCH__ (void)std::fprintf (fp, "ap=%u\n", m_enableAP); if (!m_ssid.empty ()) (void)std::fprintf (fp, "ssid=%s\n", m_ssid.c_str ()); if (!m_passphrase.empty ()) (void)std::fprintf (fp, "passphrase=%s\n", m_passphrase.c_str ()); #endif return true; } std::string const &FtpConfig::user () const { return m_user; } std::string const &FtpConfig::pass () const { return m_pass; } std::string const &FtpConfig::hostname () const { return m_hostname; } std::uint16_t FtpConfig::port () const { return m_port; } #ifdef __3DS__ bool FtpConfig::getMTime () const { return m_getMTime; } #endif #ifdef __SWITCH__ bool FtpConfig::enableAP () const { return m_enableAP; } std::string const &FtpConfig::ssid () const { return m_ssid; } std::string const &FtpConfig::passphrase () const { return m_passphrase; } #endif void FtpConfig::setUser (std::string user_) { m_user = std::move (user_); } void FtpConfig::setPass (std::string pass_) { m_pass = std::move (pass_); } void FtpConfig::setHostname (std::string hostname_) { m_hostname = std::move (hostname_); } bool FtpConfig::setPort (std::string_view const port_) { std::uint16_t parsed{}; if (!parseInt (parsed, port_)) return false; return setPort (parsed); } bool FtpConfig::setPort (std::uint16_t const port_) { #ifdef __SWITCH__ // Switch is not allowed < 1024, except 0 if (port_ < 1024 && port_ != 0) { errno = EPERM; return false; } #elif defined(__NDS__) || defined(__3DS__) // 3DS is allowed < 1024, but not 0 // NDS is allowed < 1024, but 0 crashes the app if (port_ == 0) { errno = EPERM; return false; } #endif m_port = port_; return true; } #ifdef __3DS__ void FtpConfig::setGetMTime (bool const getMTime_) { m_getMTime = getMTime_; } #endif #ifdef __SWITCH__ void FtpConfig::setEnableAP (bool const enable_) { m_enableAP = enable_; } void FtpConfig::setSSID (std::string_view const ssid_) { m_ssid = ssid_.substr (0, ssid_.find_first_of ('\0')); } void FtpConfig::setPassphrase (std::string_view const passphrase_) { m_passphrase = passphrase_.substr (0, passphrase_.find_first_of ('\0')); } #endif