diff --git a/include/download/download.hpp b/include/download/download.hpp index 4415efe..f8b3e0c 100644 --- a/include/download/download.hpp +++ b/include/download/download.hpp @@ -30,7 +30,7 @@ #include "common.hpp" #define APP_TITLE "Universal-Updater" -#define VERSION_STRING "2.5.0" +#define VERSION_STRING "2.5.1" // The Release Fetch struct. struct ReleaseFetch { diff --git a/include/screens/ftpScreen.hpp b/include/screens/ftpScreen.hpp deleted file mode 100644 index f1ea0dc..0000000 --- a/include/screens/ftpScreen.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/* -* This file is part of Universal-Updater -* Copyright (C) 2019-2020 Universal-Team -* -* 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 . -* -* Additional Terms 7.b and 7.c of GPLv3 apply to this file: -* * Requiring preservation of specified reasonable legal notices or -* author attributions in that material or in the Appropriate Legal -* Notices displayed by works containing it. -* * Prohibiting misrepresentation of the origin of that material, -* or requiring that modified versions of such material be marked in -* reasonable ways as different from the original version. -*/ - -#ifndef _UNIVERSAL_UPDATER_FTP_SCREEN_HPP -#define _UNIVERSAL_UPDATER_FTP_SCREEN_HPP - -#include "common.hpp" -#include "structs.hpp" - -#include - -class FTPScreen : public Screen { -public: - void Draw(void) const override; - void Logic(u32 hDown, u32 hHeld, touchPosition touch) override; - -private: - int ftpEnabled = 1; - const std::vector arrowPos = { - {0, 215, 25, 25} // Back Arrow. - }; -}; - -#endif \ No newline at end of file diff --git a/include/utils/console.h b/include/utils/console.h deleted file mode 100644 index 78aef52..0000000 --- a/include/utils/console.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#ifdef _3DS -#include <3ds.h> -#define ESC(x) "\x1b[" #x -#define RESET ESC(0m) -#define BLACK ESC(30m) -#define RED ESC(31;1m) -#define GREEN ESC(32;1m) -#define YELLOW ESC(33;1m) -#define BLUE ESC(34;1m) -#define MAGENTA ESC(35;1m) -#define CYAN ESC(36;1m) -#define WHITE ESC(37;1m) -#else -#define ESC(x) -#define RESET -#define BLACK -#define RED -#define GREEN -#define YELLOW -#define BLUE -#define MAGENTA -#define CYAN -#define WHITE -#endif - -void console_init(void); - -__attribute__((format(printf,1,2))) -void console_set_status(const char *fmt, ...); - -__attribute__((format(printf,1,2))) -void console_print(const char *fmt, ...); - -__attribute__((format(printf,1,2))) -void debug_print(const char *fmt, ...); - -void console_render(void); diff --git a/include/utils/ftp.h b/include/utils/ftp.h deleted file mode 100644 index 19dcef8..0000000 --- a/include/utils/ftp.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include <3ds.h> - -/*! Loop status */ -typedef enum -{ - LOOP_CONTINUE, /*!< Continue looping */ - LOOP_RESTART, /*!< Reinitialize */ - LOOP_EXIT, /*!< Terminate looping */ -} loop_status_t; - -extern bool isTransfering; -extern char ftp_accepted_connection[50], ftp_file_transfer[100]; - -int ftp_init(void); -loop_status_t ftp_loop(void); -void ftp_exit(void); \ No newline at end of file diff --git a/source/download/download.cpp b/source/download/download.cpp index 89bf984..b06804c 100644 --- a/source/download/download.cpp +++ b/source/download/download.cpp @@ -69,9 +69,10 @@ extern u32 selected; extern u32 unselected; CURL *hnd; // Needed to display download speed properly? +CURLcode curlResult; + curl_off_t downloadTotal = 1; //Dont initialize with 0 to avoid division by zero later curl_off_t downloadNow = 0; -curl_off_t downloadSpeed = 0; static FILE *downfile = NULL; static size_t file_buffer_pos = 0; @@ -152,12 +153,9 @@ static size_t file_handle_data(char *ptr, size_t size, size_t nmemb, void *userd } Result downloadToFile(std::string url, std::string path) { - Result retcode = 0; downloadTotal = 1; - downloadNow = 0; int res; - CURLcode cres; printf("Downloading from:\n%s\nto:\n%s\n", url.c_str(), path.c_str()); const char* filepath = path.c_str(); @@ -197,12 +195,11 @@ Result downloadToFile(std::string url, std::string path) { curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); curl_easy_setopt(hnd, CURLOPT_STDERR, stdout); - cres = curl_easy_perform(hnd); - downloadSpeed = 0; + curlResult = curl_easy_perform(hnd); curl_easy_cleanup(hnd); - if (cres != CURLE_OK) { - retcode = -cres; + if (curlResult != CURLE_OK) { + retcode = -curlResult; goto exit; } @@ -216,6 +213,7 @@ Result downloadToFile(std::string url, std::string path) { retcode = -3; goto exit; } + fflush(downfile); exit: @@ -232,23 +230,26 @@ exit: if (socubuf) { free(socubuf); } + if (downfile) { fclose(downfile); downfile = NULL; } + if (g_buffers[0]) { free(g_buffers[0]); g_buffers[0] = NULL; } + if (g_buffers[1]) { free(g_buffers[1]); g_buffers[1] = NULL; } + g_index = 0; file_buffer_pos = 0; file_toCommit_size = 0; writeError = false; - return retcode; } @@ -456,16 +457,16 @@ int SelectRelease(std::vector bruh) { if (hDown & KEY_TOUCH) { if (config->viewMode() == 0) { - for(int i=0;i 40+(i*57) && touch.py < 40+(i*57)+45) { + for(int i = 0; i < ENTRIES_PER_SCREEN && i < (int)bruh.size(); i++) { + if (touch.py > 40+(i*57) && touch.py < 40+(i*57)+45) { if (bruh.size() != 0) { return screenPos + i; } } } } else if (config->viewMode() == 1) { - for(int i=0;i (i+1)*27 && touch.py < (i+2)*27) { + for(int i = 0; i < ENTRIES_PER_LIST && i < (int)bruh.size(); i++) { + if (touch.py > (i+1)*27 && touch.py < (i+2)*27) { if (bruh.size() != 0) { return screenPosList + i; } @@ -475,13 +476,13 @@ int SelectRelease(std::vector bruh) { } if (config->viewMode() == 0) { - if(selectedRelease < screenPos) { + if (selectedRelease < screenPos) { screenPos = selectedRelease; } else if (selectedRelease > screenPos + ENTRIES_PER_SCREEN - 1) { screenPos = selectedRelease - ENTRIES_PER_SCREEN + 1; } } else if (config->viewMode() == 1) { - if(selectedRelease < screenPosList) { + if (selectedRelease < screenPosList) { screenPosList = selectedRelease; } else if (selectedRelease > screenPosList + ENTRIES_PER_LIST - 1) { screenPosList = selectedRelease - ENTRIES_PER_LIST + 1; @@ -606,6 +607,7 @@ Result downloadFromRelease(std::string url, std::string asset, std::string path, result_buf = NULL; result_sz = 0; result_written = 0; + if (assetUrl.empty()) { ret = DL_ERROR_GIT; } else { @@ -839,22 +841,32 @@ std::string getLatestCommit(std::string repo, std::string array, std::string ite void displayProgressBar() { char str[256]; while(showProgressBar) { - if (downloadTotal < 1.0f) { - downloadTotal = 1.0f; - } - if (downloadTotal < downloadNow) { - downloadTotal = downloadNow; - } - - if (progressbarType == ProgressBar::Downloading) curl_easy_getinfo(hnd, CURLINFO_SPEED_DOWNLOAD_T, &downloadSpeed); // Get download speed. - switch(progressbarType) { case ProgressBar::Downloading: - snprintf(str, sizeof(str), "%s / %s (%.2f%%) \n\n\n\n\n %s %lld %s", - formatBytes(downloadNow).c_str(), - formatBytes(downloadTotal).c_str(), - ((float)downloadNow/(float)downloadTotal) * 100.0f, - Lang::get("DOWNLOAD_SPEED").c_str(), (downloadSpeed / 1000), Lang::get("KB_PER_SECOND").c_str()); + if (downloadTotal < 1.0f) { + downloadTotal = 1.0f; + } + + if (downloadTotal < downloadNow) { + downloadTotal = downloadNow; + } + + if (!curlResult) { + curl_off_t speed; + curlResult = curl_easy_getinfo(hnd, CURLINFO_SPEED_DOWNLOAD_T, &speed); + if (!curlResult) { + snprintf(str, sizeof(str), "%s / %s (%.2f%%) \n\n\n\n\n %s %lld %s", + formatBytes(downloadNow).c_str(), + formatBytes(downloadTotal).c_str(), + ((float)downloadNow/(float)downloadTotal) * 100.0f, + Lang::get("DOWNLOAD_SPEED").c_str(), (speed / 1000), Lang::get("KB_PER_SECOND").c_str()); + } else { + snprintf(str, sizeof(str), "%s / %s (%.2f%%) \n\n\n\n\n %s", + formatBytes(downloadNow).c_str(), + formatBytes(downloadTotal).c_str(), + ((float)downloadNow/(float)downloadTotal) * 100.0f); + } + } break; case ProgressBar::Extracting: snprintf(str, sizeof(str), "%s / %s (%.2f%%)", diff --git a/source/screens/ftpScreen.cpp b/source/screens/ftpScreen.cpp deleted file mode 100644 index 386b40d..0000000 --- a/source/screens/ftpScreen.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* -* This file is part of Universal-Updater -* Copyright (C) 2019-2020 Universal-Team -* -* 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 . -* -* Additional Terms 7.b and 7.c of GPLv3 apply to this file: -* * Requiring preservation of specified reasonable legal notices or -* author attributions in that material or in the Appropriate Legal -* Notices displayed by works containing it. -* * Prohibiting misrepresentation of the origin of that material, -* or requiring that modified versions of such material be marked in -* reasonable ways as different from the original version. -*/ - -#include "ftpScreen.hpp" - -#include -#include -#include - -extern "C" { - #include "ftp.h" -} - -extern std::unique_ptr config; -extern bool touching(touchPosition touch, Structs::ButtonPos button); -extern touchPosition touch; - -void FTPScreen::Draw(void) const { - ftp_init(); - Result ret = 0; - char buf[137], hostname[128]; - u32 wifiStatus = 0; - ret = gethostname(hostname, sizeof(hostname)); - - while(ftpEnabled == 1) { - ftp_loop(); - Gui::clearTextBufs(); - C3D_FrameBegin(C3D_FRAME_SYNCDRAW); - GFX::DrawTop(); - Gui::DrawStringCentered(0, config->useBars() ? 0 : 2, 0.7f, config->textColor(), Lang::get("FTP_MODE"), 400); - if (fadealpha > 0) Gui::Draw_Rect(0, 0, 400, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); // Fade in/out effect - GFX::DrawBottom(); - GFX::DrawArrow(0, 218, 0, 1); - ret = ACU_GetWifiStatus(&wifiStatus); - - if ((wifiStatus != 0) && R_SUCCEEDED(ret)) { - Gui::DrawStringCentered(0, 40, 0.48f, config->textColor(), Lang::get("FTP_INITIALIZED"), 320); - snprintf(buf, 137, "IP: %s:5000", R_FAILED(ret)? Lang::get("FAILED_GET_IP").c_str() : hostname); - - if (strlen(ftp_accepted_connection) != 0) { - Gui::DrawStringCentered(0, 80, 0.45f, config->textColor(), ftp_accepted_connection, 320); - } - - if (strlen(ftp_file_transfer) != 0) { - Gui::DrawStringCentered(0, 150, 0.45f, config->textColor(), ftp_file_transfer, 320); - } - - } else { - Gui::DrawStringCentered(0, 40, 0.48f, config->textColor(), Lang::get("FAILED_INITIALIZE_FTP"), 320); - snprintf(buf, 18, Lang::get("WIFI_NOT_ENABLED").c_str()); - } - - Gui::DrawStringCentered(0, 60, 0.48, config->textColor(), buf, 320); - Gui::DrawStringCentered(0, 222, 0.48f, config->textColor(), Lang::get("B_FTP_EXIT"), 320); - if (fadealpha > 0) Gui::Draw_Rect(0, 0, 320, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); // Fade in/out effect - Gui::clearTextBufs(); - C3D_FrameEnd(0); - hidScanInput(); - hidTouchRead(&touch); - u32 hDown = hidKeysDown(); - - if ((hDown & KEY_B) || (hDown & KEY_TOUCH && touching(touch, arrowPos[0]))) - break; - } - memset(ftp_accepted_connection, 0, 20); // Empty accepted connection address. - memset(ftp_file_transfer, 0, 50); // Empty transfer status. - ftp_exit(); - - Gui::screenBack(false); - return; -} - -// Needed here, otherwise it won't compile. -void FTPScreen::Logic(u32 hDown, u32 hHeld, touchPosition touch) { } \ No newline at end of file diff --git a/source/screens/mainMenu.cpp b/source/screens/mainMenu.cpp index 1bee2cd..c137a5d 100644 --- a/source/screens/mainMenu.cpp +++ b/source/screens/mainMenu.cpp @@ -25,7 +25,7 @@ */ #include "config.hpp" -#include "ftpScreen.hpp" +#include "credits.hpp" #include "mainMenu.hpp" #include "scriptHelper.hpp" #include "scriptlist.hpp" @@ -51,7 +51,7 @@ void MainMenu::Draw(void) const { GFX::DrawButton(mainButtons[0].x, mainButtons[0].y, "UniStore"); GFX::DrawButton(mainButtons[1].x, mainButtons[1].y, Lang::get("SCRIPTS")); GFX::DrawButton(mainButtons[2].x, mainButtons[2].y, Lang::get("SETTINGS")); - GFX::DrawButton(mainButtons[3].x, mainButtons[3].y, "FTP"); + GFX::DrawButton(mainButtons[3].x, mainButtons[3].y, Lang::get("CREDITS")); // Selector. Animation::Button(mainButtons[Selection].x, mainButtons[Selection].y, .060); @@ -90,7 +90,7 @@ void MainMenu::Logic(u32 hDown, u32 hHeld, touchPosition touch) { Gui::setScreen(std::make_unique(), config->screenFade(), true); break; case 3: - Gui::setScreen(std::make_unique(), false, true); + Gui::setScreen(std::make_unique(), config->screenFade(), true); break; } } @@ -103,7 +103,7 @@ void MainMenu::Logic(u32 hDown, u32 hHeld, touchPosition touch) { } else if (touching(touch, mainButtons[2])) { Gui::setScreen(std::make_unique(), config->screenFade(), true); } else if (touching(touch, mainButtons[3])) { - Gui::setScreen(std::make_unique(), false, true); + Gui::setScreen(std::make_unique(), config->screenFade(), true); } } } \ No newline at end of file diff --git a/source/screens/settings.cpp b/source/screens/settings.cpp index 6d115af..544fa2d 100644 --- a/source/screens/settings.cpp +++ b/source/screens/settings.cpp @@ -24,7 +24,6 @@ * reasonable ways as different from the original version. */ -#include "credits.hpp" #include "keyboard.hpp" #include "settings.hpp" @@ -59,7 +58,7 @@ void Settings::DrawSubMenu(void) const { GFX::DrawButton(mainButtons[0].x, mainButtons[0].y, Lang::get("LANGUAGE")); GFX::DrawButton(mainButtons[1].x, mainButtons[1].y, Lang::get("COLORS")); - GFX::DrawButton(mainButtons[2].x, mainButtons[2].y, Lang::get("CREDITS")); + GFX::DrawButton(mainButtons[2].x, mainButtons[2].y, Lang::get("CHANGE_BAR_STYLE")); // Selector. Animation::Button(mainButtons[Selection].x, mainButtons[Selection].y, .060); if (fadealpha > 0) Gui::Draw_Rect(0, 0, 320, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); // Fade in/out effect @@ -97,6 +96,7 @@ void Settings::DrawLanguageSelection(void) const { Gui::DrawStringCentered(0, 50+(i*57), 0.7f, config->textColor(), line1, 320); } + if (fadealpha > 0) Gui::Draw_Rect(0, 0, 320, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); // Fade in/out effect } @@ -215,6 +215,7 @@ void Settings::DrawColorChanging(void) const { GFX::DrawButton(mainButtons[2].x, mainButtons[2].y, ColorHelper::getColorName(config->buttonColor(), 0).c_str(), C2D_Color32(0, 0, 255, 255)); } } + if (fadealpha > 0) Gui::Draw_Rect(0, 0, 320, 240, C2D_Color32(fadecolor, fadecolor, fadecolor, fadealpha)); // Fade in/out effect } @@ -226,9 +227,8 @@ void Settings::DrawMiscSettings(void) const { GFX::DrawArrow(0, 218, 0, 1); GFX::DrawButton(mainButtons2[0].x, mainButtons2[0].y, Lang::get("CHANGE_MUSICFILE")); - GFX::DrawButton(mainButtons2[1].x, mainButtons2[1].y, Lang::get("CHANGE_BAR_STYLE")); - GFX::DrawButton(mainButtons2[2].x, mainButtons2[2].y, Lang::get("CHANGE_KEY_DELAY")); - GFX::DrawButton(mainButtons2[3].x, mainButtons2[3].y, Lang::get("TOGGLE_FADE")); + GFX::DrawButton(mainButtons2[1].x, mainButtons2[1].y, Lang::get("CHANGE_KEY_DELAY")); + GFX::DrawButton(mainButtons2[2].x, mainButtons2[2].y, Lang::get("TOGGLE_FADE")); // Selector. Animation::Button(mainButtons2[Selection].x, mainButtons2[Selection].y, .060); @@ -241,27 +241,19 @@ void Settings::MiscSettingsLogic(u32 hDown, u32 hHeld, touchPosition touch) { std::string tempMusic = selectFilePath(Lang::get("SELECT_MUSIC_FILE"), "sdmc:/", {"wav"}, 2); if (tempMusic != "") { config->musicPath(tempMusic); - changesMade = true; } } else if (Selection == 1) { - if (config->useBars() == true) config->useBars(false); - else if (config->useBars() == false) config->useBars(true); - changesMade = true; - } else if (Selection == 2) { config->keyDelay(Input::getUint(255, Lang::get("ENTER_KEY_DELAY"))); - changesMade = true; - } else if (Selection == 3) { + } else if (Selection == 2) { if (config->screenFade()) { if (Msg::promptMsg(Lang::get("TOGGLE_FADE_DISABLE"))) { config->screenFade(false); Msg::DisplayWarnMsg(Lang::get("DISABLED")); - changesMade = true; } } else { if (Msg::promptMsg(Lang::get("TOGGLE_FADE_ENABLE"))) { config->screenFade(true); Msg::DisplayWarnMsg(Lang::get("ENABLED")); - changesMade = true; } } } @@ -272,27 +264,19 @@ void Settings::MiscSettingsLogic(u32 hDown, u32 hHeld, touchPosition touch) { std::string tempMusic = selectFilePath(Lang::get("SELECT_MUSIC_FILE"), "sdmc:/", {"wav"}, 2); if (tempMusic != "") { config->musicPath(tempMusic); - changesMade = true; } } else if (touching(touch, mainButtons2[1])) { - if (config->useBars() == true) config->useBars(false); - else if (config->useBars() == false) config->useBars(true); - changesMade = true; - } else if (touching(touch, mainButtons2[2])) { config->keyDelay(Input::getUint(255, Lang::get("ENTER_KEY_DELAY"))); - changesMade = true; - } else if (touching(touch, mainButtons2[3])) { + } else if (touching(touch, mainButtons2[2])) { if (config->screenFade()) { if (Msg::promptMsg(Lang::get("TOGGLE_FADE_DISABLE"))) { config->screenFade(false); Msg::DisplayWarnMsg(Lang::get("DISABLED")); - changesMade = true; } } else { if (Msg::promptMsg(Lang::get("TOGGLE_FADE_ENABLE"))) { config->screenFade(true); Msg::DisplayWarnMsg(Lang::get("ENABLED")); - changesMade = true; } } } @@ -307,15 +291,13 @@ void Settings::MiscSettingsLogic(u32 hDown, u32 hHeld, touchPosition touch) { if (hDown & KEY_SELECT) { if (config->progressDisplay()) { if (Msg::promptMsg(Lang::get("PROGRESS_BAR_DISABLE"))) { - config->progressDisplay(false); - Msg::DisplayWarnMsg(Lang::get("DISABLED")); - changesMade = true; + config->progressDisplay(false); + Msg::DisplayWarnMsg(Lang::get("DISABLED")); } } else { if (Msg::promptMsg(Lang::get("PROGRESS_BAR_ENABLE"))) { config->progressDisplay(true); Msg::DisplayWarnMsg(Lang::get("ENABLED")); - changesMade = true; } } } @@ -354,7 +336,8 @@ void Settings::SubMenuLogic(u32 hDown, u32 hHeld, touchPosition touch) { mode = 2; break; case 2: - Gui::setScreen(std::make_unique(), config->screenFade(), true); + if (config->useBars()) config->useBars(false); + else config->useBars(true); break; } } @@ -368,7 +351,8 @@ void Settings::SubMenuLogic(u32 hDown, u32 hHeld, touchPosition touch) { screenPos = 0; mode = 2; } else if (touching(touch, mainButtons[2])) { - Gui::setScreen(std::make_unique(), config->screenFade(), true); + if (config->useBars()) config->useBars(false); + else config->useBars(true); } } @@ -410,7 +394,6 @@ void Settings::LanguageSelection(u32 hDown, u32 hHeld, touchPosition touch) { if (hDown & KEY_A) { config->language(langsTemp[selectedLang]); Lang::load(config->language()); - changesMade = true; mode = 0; } @@ -459,7 +442,6 @@ void Settings::colorChanging(u32 hDown, u32 hHeld, touchPosition touch) { } } else { - if ((hDown & KEY_SELECT) || (hDown & KEY_TOUCH && touching(touch, arrowPos[3]))) { colorSelection = colorMode; dropDownMenu = true; @@ -507,7 +489,6 @@ void Settings::colorChanging(u32 hDown, u32 hHeld, touchPosition touch) { } else if (colorMode == 11) { config->buttonColor(RGBA8(red, ColorHelper::getColorValue(config->buttonColor(), 1), ColorHelper::getColorValue(config->buttonColor(), 0), 255)); } - changesMade = true; } } else if (touching(touch, mainButtons[1])) { int temp = Input::getUint(255, Lang::get("ENTER_GREEN_RGB")); @@ -538,7 +519,6 @@ void Settings::colorChanging(u32 hDown, u32 hHeld, touchPosition touch) { } else if (colorMode == 11) { config->buttonColor(RGBA8(ColorHelper::getColorValue(config->buttonColor(), 2), green, ColorHelper::getColorValue(config->buttonColor(), 0), 255)); } - changesMade = true; } } else if (touching(touch, mainButtons[2])) { int temp = Input::getUint(255, Lang::get("ENTER_BLUE_RGB")); @@ -569,11 +549,11 @@ void Settings::colorChanging(u32 hDown, u32 hHeld, touchPosition touch) { } else if (colorMode == 11) { config->buttonColor(RGBA8(ColorHelper::getColorValue(config->buttonColor(), 2), ColorHelper::getColorValue(config->buttonColor(), 1), blue, 255)); } - changesMade = true; } } } } + if (colorSelection < screenPos) { screenPos = colorSelection; } else if (colorSelection > screenPos + ENTRIES_PER_SCREEN - 1) { diff --git a/source/utils/console.c b/source/utils/console.c deleted file mode 100644 index 0c383c1..0000000 --- a/source/utils/console.c +++ /dev/null @@ -1,237 +0,0 @@ -#include "console.h" - -#include -#include -#include -#include -#include -#include - -#ifdef _3DS -#include <3ds.h> - -static PrintConsole status_console; -static PrintConsole main_console; -static PrintConsole tcp_console; -#if ENABLE_LOGGING -static bool disable_logging = false; -#endif - -/*! initialize console subsystem */ -void -console_init(void) -{ - consoleInit(GFX_TOP, &status_console); - consoleSetWindow(&status_console, 0, 0, 50, 1); - - consoleInit(GFX_TOP, &main_console); - consoleSetWindow(&main_console, 0, 1, 50, 29); - - consoleInit(GFX_BOTTOM, &tcp_console); - - consoleSelect(&main_console); -} - -/*! set status bar contents - * - * @param[in] fmt format string - * @param[in] ... format arguments - */ -void -console_set_status(const char *fmt, ...) -{ - va_list ap; - - consoleSelect(&status_console); - va_start(ap, fmt); - vprintf(fmt, ap); -#ifdef ENABLE_LOGGING - vfprintf(stderr, fmt, ap); -#endif - va_end(ap); - consoleSelect(&main_console); -} - -/*! add text to the console - * - * @param[in] fmt format string - * @param[in] ... format arguments - */ -void -console_print(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vprintf(fmt, ap); -#ifdef ENABLE_LOGGING - if (!disable_logging) - vfprintf(stderr, fmt, ap); -#endif - va_end(ap); -} - -/*! print debug message - * - * @param[in] fmt format string - * @param[in] ... format arguments - */ -void -debug_print(const char *fmt, ...) -{ -#ifdef ENABLE_LOGGING - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); -#endif -} - -/*! print tcp tables */ -static void -print_tcp_table(void) -{ - static SOCU_TCPTableEntry tcp_entries[32]; - socklen_t optlen; - size_t i; - int rc, lines = 0; - -#ifdef ENABLE_LOGGING - disable_logging = true; -#endif - - consoleSelect(&tcp_console); - console_print("\x1b[0;0H\x1b[K"); - optlen = sizeof(tcp_entries); - rc = SOCU_GetNetworkOpt(SOL_CONFIG, NETOPT_TCP_TABLE, tcp_entries, &optlen); - if(rc != 0 && errno != ENODEV) - console_print(RED "tcp table: %d %s\n\x1b[J\n" RESET, errno, strerror(errno)); - else if(rc == 0) - { - for(i = 0; lines < 30 && i < optlen / sizeof(SOCU_TCPTableEntry); ++i) - { - SOCU_TCPTableEntry *entry = &tcp_entries[i]; - struct sockaddr_in *local = (struct sockaddr_in*)&entry->local; - struct sockaddr_in *remote = (struct sockaddr_in*)&entry->remote; - - console_print(GREEN "%stcp[%zu]: ", i == 0 ? "" : "\n", i); - switch(entry->state) - { - case TCP_STATE_CLOSED: - console_print("CLOSED\x1b[K"); - local = remote = NULL; - break; - - case TCP_STATE_LISTEN: - console_print("LISTEN\x1b[K"); - remote = NULL; - break; - - case TCP_STATE_ESTABLISHED: - console_print("ESTABLISHED\x1b[K"); - break; - - case TCP_STATE_FINWAIT1: - console_print("FINWAIT1\x1b[K"); - break; - - case TCP_STATE_FINWAIT2: - console_print("FINWAIT2\x1b[K"); - break; - - case TCP_STATE_CLOSE_WAIT: - console_print("CLOSE_WAIT\x1b[K"); - break; - - case TCP_STATE_LAST_ACK: - console_print("LAST_ACK\x1b[K"); - break; - - case TCP_STATE_TIME_WAIT: - console_print("TIME_WAIT\x1b[K"); - break; - - default: - console_print("State %lu\x1b[K", entry->state); - break; - } - - ++lines; - - if(local && (lines++ < 30)) - console_print("\n Local %s:%u\x1b[K", inet_ntoa(local->sin_addr), - ntohs(local->sin_port)); - - if(remote && (lines++ < 30)) - console_print("\n Peer %s:%u\x1b[K", inet_ntoa(remote->sin_addr), - ntohs(remote->sin_port)); - } - - console_print(RESET "\x1b[J"); - } - else - console_print("\x1b[2J"); - - consoleSelect(&main_console); - -#ifdef ENABLE_LOGGING - disable_logging = false; -#endif -} - -/*! draw console to screen */ -void -console_render(void) -{ - /* print tcp table */ - print_tcp_table(); - - /* flush framebuffer */ - gfxFlushBuffers(); - gspWaitForVBlank(); - gfxSwapBuffers(); -} -#else - -/* this is a lot easier when you have a real console */ - -void -console_init(void) -{ -} - -void -console_set_status(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - fputc('\n', stdout); -} - -void -console_print(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); -} - -void -debug_print(const char *fmt, ...) -{ -#ifdef ENABLE_LOGGING - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); -#endif -} - -void console_render(void) -{ -} -#endif \ No newline at end of file diff --git a/source/utils/ftp.c b/source/utils/ftp.c deleted file mode 100644 index 1ba5adc..0000000 --- a/source/utils/ftp.c +++ /dev/null @@ -1,4046 +0,0 @@ -/* This FTP server implementation is based on RFC 959, - * (https://tools.ietf.org/html/rfc959), RFC 3659 - * (https://tools.ietf.org/html/rfc3659) and suggested implementation details - * from https://cr.yp.to/ftp/filesystem.html - */ - -#include "console.h" -#include "ftp.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef _3DS -#define lstat stat -#else -#include -#define BIT(x) (1<<(x)) -#endif - - -bool isTransfering; -char ftp_accepted_connection[50], ftp_file_transfer[100]; - -#define POLL_UNKNOWN (~(POLLIN|POLLPRI|POLLOUT)) - -#define XFER_BUFFERSIZE 32768 -#define SOCK_BUFFERSIZE 32768 -#define FILE_BUFFERSIZE 65536 -#define CMD_BUFFERSIZE 4096 -#define SOCU_ALIGN 0x1000 -#define SOCU_BUFFERSIZE 0x100000 -#define LISTEN_PORT 5000 -#ifdef _3DS -#define DATA_PORT (LISTEN_PORT+1) -#else -#define DATA_PORT 0 /* ephemeral port */ -#endif - -typedef struct ftp_session_t ftp_session_t; - -#define FTP_DECLARE(x) static void x(ftp_session_t *session, const char *args) -FTP_DECLARE(ABOR); -FTP_DECLARE(ALLO); -FTP_DECLARE(APPE); -FTP_DECLARE(CDUP); -FTP_DECLARE(CWD); -FTP_DECLARE(DELE); -FTP_DECLARE(FEAT); -FTP_DECLARE(HELP); -FTP_DECLARE(LIST); -FTP_DECLARE(MDTM); -FTP_DECLARE(MKD); -FTP_DECLARE(MLSD); -FTP_DECLARE(MLST); -FTP_DECLARE(MODE); -FTP_DECLARE(NLST); -FTP_DECLARE(NOOP); -FTP_DECLARE(OPTS); -FTP_DECLARE(PASS); -FTP_DECLARE(PASV); -FTP_DECLARE(PORT); -FTP_DECLARE(PWD); -FTP_DECLARE(QUIT); -FTP_DECLARE(REST); -FTP_DECLARE(RETR); -FTP_DECLARE(RMD); -FTP_DECLARE(RNFR); -FTP_DECLARE(RNTO); -FTP_DECLARE(SIZE); -FTP_DECLARE(STAT); -FTP_DECLARE(STOR); -FTP_DECLARE(STOU); -FTP_DECLARE(STRU); -FTP_DECLARE(SYST); -FTP_DECLARE(TYPE); -FTP_DECLARE(USER); - -/*! session state */ -typedef enum -{ - COMMAND_STATE, /*!< waiting for a command */ - DATA_CONNECT_STATE, /*!< waiting for connection after PASV command */ - DATA_TRANSFER_STATE, /*!< data transfer in progress */ -} session_state_t; - -/*! ftp_session_set_state flags */ -typedef enum -{ - CLOSE_PASV = BIT(0), /*!< Close the pasv_fd */ - CLOSE_DATA = BIT(1), /*!< Close the data_fd */ -} set_state_flags_t; - -/*! ftp_session_t flags */ -typedef enum -{ - SESSION_BINARY = BIT(0), /*!< data transfers in binary mode */ - SESSION_PASV = BIT(1), /*!< have pasv_addr ready for data transfer command */ - SESSION_PORT = BIT(2), /*!< have peer_addr ready for data transfer command */ - SESSION_RECV = BIT(3), /*!< data transfer in source mode */ - SESSION_SEND = BIT(4), /*!< data transfer in sink mode */ - SESSION_RENAME = BIT(5), /*!< last command was RNFR and buffer contains path */ - SESSION_URGENT = BIT(6), /*!< in telnet urgent mode */ -} session_flags_t; - -/*! ftp_xfer_dir mode */ -typedef enum -{ - XFER_DIR_LIST, /*!< Long list */ - XFER_DIR_MLSD, /*!< Machine list directory */ - XFER_DIR_MLST, /*!< Machine list */ - XFER_DIR_NLST, /*!< Short list */ - XFER_DIR_STAT, /*!< Stat command */ -} xfer_dir_mode_t; - -typedef enum -{ - SESSION_MLST_TYPE = BIT(0), - SESSION_MLST_SIZE = BIT(1), - SESSION_MLST_MODIFY = BIT(2), - SESSION_MLST_PERM = BIT(3), - SESSION_MLST_UNIX_MODE = BIT(4), -} session_mlst_flags_t; - -/*! ftp session */ -struct ftp_session_t -{ - char cwd[32768]; /*!< current working directory */ - char lwd[4096]; /*!< list working directory */ - struct sockaddr_in peer_addr; /*!< peer address for data connection */ - struct sockaddr_in pasv_addr; /*!< listen address for PASV connection */ - int cmd_fd; /*!< socket for command connection */ - int pasv_fd; /*!< listen socket for PASV */ - int data_fd; /*!< socket for data transfer */ - time_t timestamp; /*!< time from last command */ - session_flags_t flags; /*!< session flags */ - xfer_dir_mode_t dir_mode; /*!< dir transfer mode */ - session_mlst_flags_t mlst_flags; /*!< session MLST flags */ - session_state_t state; /*!< session state */ - ftp_session_t *next; /*!< link to next session */ - ftp_session_t *prev; /*!< link to prev session */ - - loop_status_t (*transfer)(ftp_session_t*); /*! data transfer callback */ - char buffer[XFER_BUFFERSIZE]; /*! persistent data between callbacks */ - char file_buffer[FILE_BUFFERSIZE]; /*! stdio file buffer */ - char cmd_buffer[CMD_BUFFERSIZE]; /*! command buffer */ - size_t bufferpos; /*! persistent buffer position between callbacks */ - size_t buffersize; /*! persistent buffer size between callbacks */ - size_t cmd_buffersize; - uint64_t filepos; /*! persistent file position between callbacks */ - uint64_t filesize; /*! persistent file size between callbacks */ - FILE *fp; /*! persistent open file pointer between callbacks */ - DIR *dp; /*! persistent open directory pointer between callbacks */ -}; - -/*! ftp command descriptor */ -typedef struct ftp_command -{ - const char *name; /*!< command name */ - void (*handler)(ftp_session_t*, const char*); /*!< command callback */ -} ftp_command_t; - -/*! ftp command list */ -static ftp_command_t ftp_commands[] = -{ -/*! ftp command */ -#define FTP_COMMAND(x) { #x, x, } -/*! ftp alias */ -#define FTP_ALIAS(x,y) { #x, y, } - FTP_COMMAND(ABOR), - FTP_COMMAND(ALLO), - FTP_COMMAND(APPE), - FTP_COMMAND(CDUP), - FTP_COMMAND(CWD), - FTP_COMMAND(DELE), - FTP_COMMAND(FEAT), - FTP_COMMAND(HELP), - FTP_COMMAND(LIST), - FTP_COMMAND(MDTM), - FTP_COMMAND(MKD), - FTP_COMMAND(MLSD), - FTP_COMMAND(MLST), - FTP_COMMAND(MODE), - FTP_COMMAND(NLST), - FTP_COMMAND(NOOP), - FTP_COMMAND(OPTS), - FTP_COMMAND(PASS), - FTP_COMMAND(PASV), - FTP_COMMAND(PORT), - FTP_COMMAND(PWD), - FTP_COMMAND(QUIT), - FTP_COMMAND(REST), - FTP_COMMAND(RETR), - FTP_COMMAND(RMD), - FTP_COMMAND(RNFR), - FTP_COMMAND(RNTO), - FTP_COMMAND(SIZE), - FTP_COMMAND(STAT), - FTP_COMMAND(STOR), - FTP_COMMAND(STOU), - FTP_COMMAND(STRU), - FTP_COMMAND(SYST), - FTP_COMMAND(TYPE), - FTP_COMMAND(USER), - FTP_ALIAS(XCUP, CDUP), - FTP_ALIAS(XCWD, CWD), - FTP_ALIAS(XMKD, MKD), - FTP_ALIAS(XPWD, PWD), - FTP_ALIAS(XRMD, RMD), -}; -/*! number of ftp commands */ -static const size_t num_ftp_commands = sizeof(ftp_commands)/sizeof(ftp_commands[0]); - -static void update_free_space(void); - -/*! compare ftp command descriptors - * - * @param[in] p1 left side of comparison (ftp_command_t*) - * @param[in] p2 right side of comparison (ftp_command_t*) - * - * @returns <0 if p1 < p2 - * @returns 0 if p1 == p2 - * @returns >0 if p1 > p2 - */ -static int -ftp_command_cmp(const void *p1, - const void *p2) -{ - ftp_command_t *c1 = (ftp_command_t*)p1; - ftp_command_t *c2 = (ftp_command_t*)p2; - - /* ordered by command name */ - return strcasecmp(c1->name, c2->name); -} - -#ifdef _3DS -/*! SOC service buffer */ -static u32 *SOCU_buffer = NULL; - -/*! Whether LCD is powered */ -static bool lcd_power = true; - -/*! aptHook cookie */ -static aptHookCookie cookie; -#endif - -/*! server listen address */ -static struct sockaddr_in serv_addr; -/*! listen file descriptor */ -static int listenfd = -1; -#ifdef _3DS -/*! current data port */ -static in_port_t data_port = DATA_PORT; -#endif -/*! list of ftp sessions */ -static ftp_session_t *sessions = NULL; -/*! socket buffersize */ -static int sock_buffersize = SOCK_BUFFERSIZE; -/*! server start time */ -static time_t start_time = 0; - -/*! Allocate a new data port - * - * @returns next data port - */ -static in_port_t -next_data_port(void) -{ -#ifdef _3DS - if(++data_port >= 10000) - data_port = DATA_PORT; - return data_port; -#else - return 0; /* ephemeral port */ -#endif -} - -/*! set a socket to non-blocking - * - * @param[in] fd socket - * - * @returns error - */ -static int -ftp_set_socket_nonblocking(int fd) -{ - int rc, flags; - - /* get the socket flags */ - flags = fcntl(fd, F_GETFL, 0); - if(flags == -1) - { - console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* add O_NONBLOCK to the socket flags */ - rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK); - if(rc != 0) - { - console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - return 0; -} - -/*! set socket options - * - * @param[in] fd socket - * - * @returns failure - */ -static int -ftp_set_socket_options(int fd) -{ - int rc; - - /* increase receive buffer size */ - rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, - &sock_buffersize, sizeof(sock_buffersize)); - if(rc != 0) - { - console_print(RED "setsockopt: SO_RCVBUF %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* increase send buffer size */ - rc = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, - &sock_buffersize, sizeof(sock_buffersize)); - if(rc != 0) - { - console_print(RED "setsockopt: SO_SNDBUF %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - return 0; -} - -/*! close a socket - * - * @param[in] fd socket to close - * @param[in] connected whether this socket is connected - */ -static void -ftp_closesocket(int fd, - bool connected) -{ - int rc; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - struct pollfd pollinfo; - - if(connected) - { - /* get peer address and print */ - rc = getpeername(fd, (struct sockaddr*)&addr, &addrlen); - if(rc != 0) - { - console_print(RED "getpeername: %d %s\n" RESET, errno, strerror(errno)); - console_print(YELLOW "closing connection to fd=%d\n" RESET, fd); - } - else - console_print(YELLOW "closing connection to %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - /* shutdown connection */ - rc = shutdown(fd, SHUT_WR); - if(rc != 0) - console_print(RED "shutdown: %d %s\n" RESET, errno, strerror(errno)); - - /* wait for client to close connection */ - pollinfo.fd = fd; - pollinfo.events = POLLIN; - pollinfo.revents = 0; - rc = poll(&pollinfo, 1, 250); - if(rc < 0) - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - } - - /* set linger to 0 */ - struct linger linger; - linger.l_onoff = 1; - linger.l_linger = 0; - rc = setsockopt(fd, SOL_SOCKET, SO_LINGER, - &linger, sizeof(linger)); - if(rc != 0) - console_print(RED "setsockopt: SO_LINGER %d %s\n" RESET, - errno, strerror(errno)); - - /* close socket */ - rc = close(fd); - if(rc != 0) - console_print(RED "close: %d %s\n" RESET, errno, strerror(errno)); -} - -/*! close command socket on ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_cmd(ftp_session_t *session) -{ - /* close command socket */ - if(session->cmd_fd >= 0) - ftp_closesocket(session->cmd_fd, true); - session->cmd_fd = -1; -} - -/*! close listen socket on ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_pasv(ftp_session_t *session) -{ - /* close pasv socket */ - if(session->pasv_fd >= 0) - { - console_print(YELLOW "stop listening on %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); - - ftp_closesocket(session->pasv_fd, false); - } - - session->pasv_fd = -1; -} - -/*! close data socket on ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_data(ftp_session_t *session) -{ - /* close data connection */ - if(session->data_fd >= 0 && session->data_fd != session->cmd_fd) - ftp_closesocket(session->data_fd, true); - session->data_fd = -1; - - /* clear send/recv flags */ - session->flags &= ~(SESSION_RECV|SESSION_SEND); -} - -/*! close open file for ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_file(ftp_session_t *session) -{ - int rc; - - if(session->fp != NULL) - { - rc = fclose(session->fp); - if(rc != 0) - console_print(RED "fclose: %d %s\n" RESET, errno, strerror(errno)); - } - - session->fp = NULL; - session->filepos = 0; -} - -/*! open file for reading for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for error - */ -static int -ftp_session_open_file_read(ftp_session_t *session) -{ - int rc; - struct stat st; - - /* open file in read mode */ - session->fp = fopen(session->buffer, "rb"); - if(session->fp == NULL) - { - console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - - /* it's okay if this fails */ - errno = 0; - rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); - if(rc != 0) - { - console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); - } - - /* get the file size */ - rc = fstat(fileno(session->fp), &st); - if(rc != 0) - { - console_print(RED "fstat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - session->filesize = st.st_size; - - if(session->filepos != 0) - { - rc = fseek(session->fp, session->filepos, SEEK_SET); - if(rc != 0) - { - console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - } - - return 0; -} - -/*! read from an open file for ftp session - * - * @param[in] session ftp session - * - * @returns bytes read - */ -static ssize_t -ftp_session_read_file(ftp_session_t *session) -{ - ssize_t rc; - - /* read file at current position */ - rc = fread(session->buffer, 1, sizeof(session->buffer), session->fp); - if(rc < 0) - { - console_print(RED "fread: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* adjust file position */ - session->filepos += rc; - - return rc; -} - -/*! open file for writing for ftp session - * - * @param[in] session ftp session - * @param[in] append whether to append - * - * @returns -1 for error - * - * @note truncates file - */ -static int -ftp_session_open_file_write(ftp_session_t *session, - bool append) -{ - int rc; - const char *mode = "wb"; - - if(append) - mode = "ab"; - else if(session->filepos != 0) - mode = "r+b"; - - /* open file in write mode */ - session->fp = fopen(session->buffer, mode); - if(session->fp == NULL) - { - console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - - update_free_space(); - - /* it's okay if this fails */ - errno = 0; - rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); - if(rc != 0) - { - console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); - } - - /* check if this had REST but not APPE */ - if(session->filepos != 0 && !append) - { - /* seek to the REST offset */ - rc = fseek(session->fp, session->filepos, SEEK_SET); - if(rc != 0) - { - console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } - } - - return 0; -} - -/*! write to an open file for ftp session - * - * @param[in] session ftp session - * - * @returns bytes written - */ -static ssize_t -ftp_session_write_file(ftp_session_t *session) -{ - ssize_t rc; - - /* write to file at current position */ - rc = fwrite(session->buffer + session->bufferpos, - 1, session->buffersize - session->bufferpos, - session->fp); - if(rc < 0) - { - console_print(RED "fwrite: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - else if(rc == 0) - console_print(RED "fwrite: wrote 0 bytes\n" RESET); - - /* adjust file position */ - session->filepos += rc; - - update_free_space(); - return rc; -} - -/*! close current working directory for ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_cwd(ftp_session_t *session) -{ - int rc; - - /* close open directory pointer */ - if(session->dp != NULL) - { - rc = closedir(session->dp); - if(rc != 0) - console_print(RED "closedir: %d %s\n" RESET, errno, strerror(errno)); - } - session->dp = NULL; -} - -/*! open current working directory for ftp session - * - * @param[in] session ftp session - * - * @return -1 for failure - */ -static int -ftp_session_open_cwd(ftp_session_t *session) -{ - /* open current working directory */ - session->dp = opendir(session->cwd); - if(session->dp == NULL) - { - console_print(RED "opendir '%s': %d %s\n" RESET, session->cwd, errno, strerror(errno)); - return -1; - } - - return 0; -} - -/*! set state for ftp session - * - * @param[in] session ftp session - * @param[in] state state to set - * @param[in] flags flags - */ -static void -ftp_session_set_state(ftp_session_t *session, - session_state_t state, - set_state_flags_t flags) -{ - session->state = state; - - /* close pasv and data sockets */ - if(flags & CLOSE_PASV) - ftp_session_close_pasv(session); - if(flags & CLOSE_DATA) - ftp_session_close_data(session); - - if(state == COMMAND_STATE) - { - /* close file/cwd */ - ftp_session_close_file(session); - ftp_session_close_cwd(session); - } -} - -/*! fill directory entry - * - * @param[in] session ftp session - * @param[in] st stat data - * @param[in] path path to fill - * @param[in] len path length - * @param[in] type type fact - * - * @returns errno - */ -static int -ftp_session_fill_dirent_type(ftp_session_t *session, const struct stat *st, - const char *path, size_t len, const char *type) -{ - session->buffersize = 0; - - if(session->dir_mode == XFER_DIR_MLSD - || session->dir_mode == XFER_DIR_MLST) - { - if(session->dir_mode == XFER_DIR_MLST) - session->buffer[session->buffersize++] = ' '; - - if(session->mlst_flags & SESSION_MLST_TYPE) - { - /* type fact */ - if(!type) - { - type = "???"; - if(S_ISREG(st->st_mode)) - type = "file"; - else if(S_ISDIR(st->st_mode)) - type = "dir"; -#ifndef _3DS - else if(S_ISLNK(st->st_mode)) - type = "os.unix=symlink"; - else if(S_ISCHR(st->st_mode)) - type = "os.unix=character"; - else if(S_ISBLK(st->st_mode)) - type = "os.unix=block"; - else if(S_ISFIFO(st->st_mode)) - type = "os.unix=fifo"; - else if(S_ISSOCK(st->st_mode)) - type = "os.unix=socket"; -#endif - } - - session->buffersize += - sprintf(session->buffer + session->buffersize, "Type=%s;", type); - } - - if(session->mlst_flags & SESSION_MLST_SIZE) - { - /* size fact */ - session->buffersize += - sprintf(session->buffer + session->buffersize, "Size=%lld;", - (signed long long)st->st_size); - } - - if(session->mlst_flags & SESSION_MLST_MODIFY) - { - /* mtime fact */ - struct tm *tm = gmtime(&st->st_mtime); - if(tm == NULL) - return errno; - - session->buffersize += - strftime(session->buffer + session->buffersize, - sizeof(session->buffer) - session->buffersize, - "Modify=%Y%m%d%H%M%S;", tm); - if(session->buffersize == 0) - return EOVERFLOW; - } - - if(session->mlst_flags & SESSION_MLST_PERM) - { - /* permission fact */ - strcpy(session->buffer + session->buffersize, "Perm="); - session->buffersize += strlen("Perm="); - - /* append permission */ - if(S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'a'; - - /* create permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'c'; - - /* delete permission */ - session->buffer[session->buffersize++] = 'd'; - - /* chdir permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IXUSR)) - session->buffer[session->buffersize++] = 'e'; - - /* rename permission */ - session->buffer[session->buffersize++] = 'f'; - - /* list permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IRUSR)) - session->buffer[session->buffersize++] = 'l'; - - /* mkdir permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'm'; - - /* delete permission */ - if(S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'p'; - - /* read permission */ - if(S_ISREG(st->st_mode) && (st->st_mode & S_IRUSR)) - session->buffer[session->buffersize++] = 'r'; - - /* write permission */ - if(S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'w'; - - session->buffer[session->buffersize++] = ';'; - } - - if(session->mlst_flags & SESSION_MLST_UNIX_MODE) - { - /* unix mode fact */ - mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX | S_ISGID | S_ISUID; - session->buffersize += - sprintf(session->buffer + session->buffersize, "UNIX.mode=0%lo;", - (unsigned long)(st->st_mode & mask)); - } - - /* make sure space precedes name */ - if(session->buffer[session->buffersize-1] != ' ') - session->buffer[session->buffersize++] = ' '; - } - else if(session->dir_mode != XFER_DIR_NLST) - { - if(session->dir_mode == XFER_DIR_STAT) - session->buffer[session->buffersize++] = ' '; - - /* perms nlinks owner group size */ - session->buffersize += - sprintf(session->buffer + session->buffersize, - "%c%c%c%c%c%c%c%c%c%c %lu 3DS 3DS %lld ", - S_ISREG(st->st_mode) ? '-' : - S_ISDIR(st->st_mode) ? 'd' : -#ifndef _3DS - S_ISLNK(st->st_mode) ? 'l' : - S_ISCHR(st->st_mode) ? 'c' : - S_ISBLK(st->st_mode) ? 'b' : - S_ISFIFO(st->st_mode) ? 'p' : - S_ISSOCK(st->st_mode) ? 's' : -#endif - '?', - st->st_mode & S_IRUSR ? 'r' : '-', - st->st_mode & S_IWUSR ? 'w' : '-', - st->st_mode & S_IXUSR ? 'x' : '-', - st->st_mode & S_IRGRP ? 'r' : '-', - st->st_mode & S_IWGRP ? 'w' : '-', - st->st_mode & S_IXGRP ? 'x' : '-', - st->st_mode & S_IROTH ? 'r' : '-', - st->st_mode & S_IWOTH ? 'w' : '-', - st->st_mode & S_IXOTH ? 'x' : '-', - (unsigned long)st->st_nlink, - (signed long long)st->st_size); - - /* timestamp */ - struct tm *tm = gmtime(&st->st_mtime); - if(tm) - { - const char *fmt = "%b %e %Y "; - if(session->timestamp > st->st_mtime - && session->timestamp - st->st_mtime < (60*60*24*365/2)) - { - fmt = "%b %e %H:%M "; - } - - session->buffersize += - strftime(session->buffer + session->buffersize, - sizeof(session->buffer) - session->buffersize, - fmt, tm); - } - else - { - session->buffersize += - sprintf(session->buffer + session->buffersize, "Jan 1 1970 "); - } - } - - if(session->buffersize + len + 2 > sizeof(session->buffer)) - { - /* buffer will overflow */ - return EOVERFLOW; - } - - /* copy path */ - memcpy(session->buffer+session->buffersize, path, len); - len = session->buffersize + len; - session->buffer[len++] = '\r'; - session->buffer[len++] = '\n'; - session->buffersize = len; - - return 0; -} - -/*! fill directory entry - * - * @param[in] session ftp session - * @param[in] st stat data - * @param[in] path path to fill - * @param[in] len path length - * - * @returns errno - */ -static int -ftp_session_fill_dirent(ftp_session_t *session, const struct stat *st, - const char *path, size_t len) -{ - return ftp_session_fill_dirent_type(session, st, path, len, NULL); -} - -/*! transfer loop - * - * Try to transfer as much data as the sockets will allow without blocking - * - * @param[in] session ftp session - */ -static void -ftp_session_transfer(ftp_session_t *session) -{ - int rc; - do - { - rc = session->transfer(session); - } while(rc == 0); -} - -/*! encode a path - * - * @param[in] path path to encode - * @param[in,out] len path length - * @param[in] quotes whether to encode quotes - * - * @returns encoded path - * - * @note The caller must free the returned path - */ -static char* -encode_path(const char *path, - size_t *len, - bool quotes) -{ - bool enc = false; - size_t i, diff = 0; - char *out, *p = (char*)path; - - /* check for \n that needs to be encoded */ - if(memchr(p, '\n', *len) != NULL) - enc = true; - - if(quotes) - { - /* check for " that needs to be encoded */ - p = (char*)path; - do - { - p = memchr(p, '"', path + *len - p); - if(p != NULL) - { - ++p; - ++diff; - } - } while(p != NULL); - } - - /* check if an encode was needed */ - if(!enc && diff == 0) - return strdup(path); - - /* allocate space for encoded path */ - p = out = (char*)malloc(*len + diff); - if(out == NULL) - return NULL; - - /* copy the path while performing encoding */ - for(i = 0; i < *len; ++i) - { - if(*path == '\n') - { - /* encoded \n is \0 */ - *p++ = 0; - } - else if(quotes && *path == '"') - { - /* encoded " is "" */ - *p++ = '"'; - *p++ = '"'; - } - else - *p++ = *path; - ++path; - } - - *len += diff; - return out; -} - -/*! decode a path - * - * @param[in] session ftp session - * @param[in] len command length - */ -static void -decode_path(ftp_session_t *session, - size_t len) -{ - size_t i; - - /* decode \0 from the first command */ - for(i = 0; i < len; ++i) - { - /* this is an encoded \n */ - if(session->cmd_buffer[i] == 0) - session->cmd_buffer[i] = '\n'; - } -} - -/*! fill cdir directory entry - * - * @param[in] session ftp session - * @param[in] path path to fill - * - * @returns errno - */ -static int -ftp_session_fill_dirent_cdir(ftp_session_t *session, const char *path) -{ - int rc; - struct stat st; - char *buffer; - size_t len; - - rc = stat(path, &st); - /* double-check this was a directory */ - if(rc == 0 && !S_ISDIR(st.st_mode)) - { - /* shouldn't happen but just in case */ - rc = -1; - errno = ENOTDIR; - } - if(rc != 0) - return errno; - - /* encode \n in path */ - len = strlen(path); - buffer = encode_path(path, &len, false); - if(!buffer) - return ENOMEM; - - /* fill dirent with listed directory as type=cdir */ - rc = ftp_session_fill_dirent_type(session, &st, buffer, len, "cdir"); - free(buffer); - - return rc; -} - -/*! send a response on the command socket - * - * @param[in] session ftp session - * @param[in] buffer buffer to send - * @param[in] len buffer length - */ -static void -ftp_send_response_buffer(ftp_session_t *session, - const char *buffer, - size_t len) -{ - ssize_t rc, to_send; - - if(session->cmd_fd < 0) - return; - - /* send response */ - to_send = len; - console_print(GREEN "%s" RESET, buffer); - rc = send(session->cmd_fd, buffer, to_send, 0); - if(rc < 0) - { - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - else if(rc != to_send) - { - console_print(RED "only sent %u/%u bytes\n" RESET, - (unsigned int)rc, (unsigned int)to_send); - ftp_session_close_cmd(session); - } -} - -__attribute__((format(printf,3,4))) -/*! send ftp response to ftp session's peer - * - * @param[in] session ftp session - * @param[in] code response code - * @param[in] fmt format string - * @param[in] ... format arguments - */ -static void -ftp_send_response(ftp_session_t *session, - int code, - const char *fmt, ...) -{ - static char buffer[CMD_BUFFERSIZE]; - ssize_t rc; - va_list ap; - - if(session->cmd_fd < 0) - return; - - /* print response code and message to buffer */ - va_start(ap, fmt); - if(code > 0) - rc = sprintf(buffer, "%d ", code); - else - rc = sprintf(buffer, "%d-", -code); - rc += vsnprintf(buffer+rc, sizeof(buffer)-rc, fmt, ap); - va_end(ap); - - if(rc >= sizeof(buffer)) - { - /* couldn't fit message; just send code */ - console_print(RED "%s: buffersize too small\n" RESET, __func__); - if(code > 0) - rc = sprintf(buffer, "%d \r\n", code); - else - rc = sprintf(buffer, "%d-\r\n", -code); - } - - ftp_send_response_buffer(session, buffer, rc); -} - -/*! destroy ftp session - * - * @param[in] session ftp session - * - * @returns the next session in the list - */ -static ftp_session_t* -ftp_session_destroy(ftp_session_t *session) -{ - ftp_session_t *next = session->next; - - /* close all sockets/files */ - ftp_session_close_cmd(session); - ftp_session_close_pasv(session); - ftp_session_close_data(session); - ftp_session_close_file(session); - ftp_session_close_cwd(session); - - /* unlink from sessions list */ - if(session->next) - session->next->prev = session->prev; - if(session == sessions) - sessions = session->next; - else - { - session->prev->next = session->next; - if(session == sessions->prev) - sessions->prev = session->prev; - } - - /* deallocate */ - free(session); - - return next; -} - -/*! allocate new ftp session - * - * @param[in] listen_fd socket to accept connection from - */ -static void -ftp_session_new(int listen_fd) -{ - ssize_t rc; - int new_fd; - ftp_session_t *session; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - - /* accept connection */ - new_fd = accept(listen_fd, (struct sockaddr*)&addr, &addrlen); - if(new_fd < 0) - { - console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); - return; - } - - console_print(CYAN "accepted connection from %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - snprintf(ftp_accepted_connection, sizeof(ftp_accepted_connection), "Accepted connection: %s:%u", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - /* allocate a new session */ - session = (ftp_session_t*)calloc(1, sizeof(ftp_session_t)); - if(session == NULL) - { - console_print(RED "failed to allocate session\n" RESET); - ftp_closesocket(new_fd, true); - return; - } - - /* initialize session */ - strcpy(session->cwd, "/"); - session->peer_addr.sin_addr.s_addr = INADDR_ANY; - session->cmd_fd = new_fd; - session->pasv_fd = -1; - session->data_fd = -1; - session->mlst_flags = SESSION_MLST_TYPE - | SESSION_MLST_SIZE - | SESSION_MLST_MODIFY - | SESSION_MLST_PERM; - session->state = COMMAND_STATE; - - /* link to the sessions list */ - if(sessions == NULL) - { - sessions = session; - session->prev = session; - } - else - { - sessions->prev->next = session; - session->prev = sessions->prev; - sessions->prev = session; - } - - /* copy socket address to pasv address */ - addrlen = sizeof(session->pasv_addr); - rc = getsockname(new_fd, (struct sockaddr*)&session->pasv_addr, &addrlen); - if(rc != 0) - { - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 451, "Failed to get connection info\r\n"); - ftp_session_destroy(session); - return; - } - - session->cmd_fd = new_fd; - - /* send initiator response */ - ftp_send_response(session, 220, "Hello!\r\n"); -} - -/*! accept PASV connection for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for failure - */ -static int -ftp_session_accept(ftp_session_t *session) -{ - int rc, new_fd; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - - if(session->flags & SESSION_PASV) - { - /* clear PASV flag */ - session->flags &= ~SESSION_PASV; - - /* tell the peer that we're ready to accept the connection */ - ftp_send_response(session, 150, "Ready\r\n"); - - /* accept connection from peer */ - new_fd = accept(session->pasv_fd, (struct sockaddr*)&addr, &addrlen); - if(new_fd < 0) - { - console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "Failed to establish connection\r\n"); - return -1; - } - - /* set the socket to non-blocking */ - rc = ftp_set_socket_nonblocking(new_fd); - if(rc != 0) - { - ftp_closesocket(new_fd, true); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "Failed to establish connection\r\n"); - return -1; - } - - console_print(CYAN "accepted connection from %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - /* we are ready to transfer data */ - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - session->data_fd = new_fd; - - return 0; - } - else - { - /* peer didn't send PASV command */ - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); - return -1; - } -} - -/*! connect to peer for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for failure - */ -static int -ftp_session_connect(ftp_session_t *session) -{ - int rc; - - /* clear PORT flag */ - session->flags &= ~SESSION_PORT; - - /* create a new socket */ - session->data_fd = socket(AF_INET, SOCK_STREAM, 0); - if(session->data_fd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* set socket options */ - rc = ftp_set_socket_options(session->data_fd); - if(rc != 0) - { - ftp_closesocket(session->data_fd, false); - session->data_fd = -1; - return -1; - } - - /* set socket to non-blocking */ - rc = ftp_set_socket_nonblocking(session->data_fd); - if(rc != 0) - return -1; - - /* connect to peer */ - rc = connect(session->data_fd, (struct sockaddr*)&session->peer_addr, - sizeof(session->peer_addr)); - if(rc != 0) - { - if(errno != EINPROGRESS) - { - console_print(RED "connect: %d %s\n" RESET, errno, strerror(errno)); - ftp_closesocket(session->data_fd, false); - session->data_fd = -1; - return -1; - } - } - else - { - console_print(CYAN "connected to %s:%u\n" RESET, - inet_ntoa(session->peer_addr.sin_addr), - ntohs(session->peer_addr.sin_port)); - - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - ftp_send_response(session, 150, "Ready\r\n"); - } - - return 0; -} - -/*! read command for ftp session - * - * @param[in] session ftp session - * @param[in] events poll events - */ -static void -ftp_session_read_command(ftp_session_t *session, - int events) -{ - char *buffer, *args, *next = NULL; - size_t i, len; - int atmark; - ssize_t rc; - ftp_command_t key, *command; - - /* check out-of-band data */ - if(events & POLLPRI) - { - session->flags |= SESSION_URGENT; - - /* check if we are at the urgent marker */ - atmark = sockatmark(session->cmd_fd); - if(atmark < 0) - { - console_print(RED "sockatmark: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - - if(!atmark) - { - /* discard in-band data */ - rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), 0); - if(rc < 0 && errno != EWOULDBLOCK) - { - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - - return; - } - - /* retrieve the urgent data */ - rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), MSG_OOB); - if(rc < 0) - { - /* EWOULDBLOCK means out-of-band data is on the way */ - if(errno == EWOULDBLOCK) - return; - - /* error retrieving out-of-band data */ - console_print(RED "recv (oob): %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - - /* reset the command buffer */ - session->cmd_buffersize = 0; - return; - } - - /* prepare to receive data */ - buffer = session->cmd_buffer + session->cmd_buffersize; - len = sizeof(session->cmd_buffer) - session->cmd_buffersize; - if(len == 0) - { - /* error retrieving command */ - console_print(RED "Exceeded command buffer size\n" RESET); - ftp_session_close_cmd(session); - return; - } - - /* retrieve command data */ - rc = recv(session->cmd_fd, buffer, len, 0); - if(rc < 0) - { - /* error retrieving command */ - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - if(rc == 0) - { - /* peer closed connection */ - debug_print("peer closed connection\n"); - ftp_session_close_cmd(session); - return; - } - else - { - session->cmd_buffersize += rc; - len = sizeof(session->cmd_buffer) - session->cmd_buffersize; - - if(session->flags & SESSION_URGENT) - { - /* look for telnet data mark */ - for(i = 0; i < session->cmd_buffersize; ++i) - { - if((unsigned char)session->cmd_buffer[i] == 0xF2) - { - /* ignore all data that precedes the data mark */ - if(i < session->cmd_buffersize - 1) - memmove(session->cmd_buffer, session->cmd_buffer + i + 1, len - i - 1); - session->cmd_buffersize -= i + 1; - session->flags &= ~SESSION_URGENT; - break; - } - } - } - - /* loop through commands */ - while(true) - { - /* must have at least enough data for the delimiter */ - if(session->cmd_buffersize < 1) - return; - - /* look for \r\n or \n delimiter */ - for(i = 0; i < session->cmd_buffersize; ++i) - { - if(i < session->cmd_buffersize-1 - && session->cmd_buffer[i] == '\r' - && session->cmd_buffer[i+1] == '\n') - { - /* we found a \r\n delimiter */ - session->cmd_buffer[i] = 0; - next = &session->cmd_buffer[i+2]; - break; - } - else if(session->cmd_buffer[i] == '\n') - { - /* we found a \n delimiter */ - session->cmd_buffer[i] = 0; - next = &session->cmd_buffer[i+1]; - break; - } - } - - /* check if a delimiter was found */ - if(i == session->cmd_buffersize) - return; - - /* decode the command */ - decode_path(session, i); - - /* split command from arguments */ - args = buffer = session->cmd_buffer; - while(*args && !isspace((int)*args)) - ++args; - if(*args) - *args++ = 0; - - /* look up the command */ - key.name = buffer; - command = bsearch(&key, ftp_commands, - num_ftp_commands, sizeof(ftp_command_t), - ftp_command_cmp); - - /* update command timestamp */ - session->timestamp = time(NULL); - - /* execute the command */ - if(command == NULL) - { - /* send header */ - ftp_send_response(session, 502, "Invalid command \""); - - /* send command */ - len = strlen(buffer); - buffer = encode_path(buffer, &len, false); - if(buffer != NULL) - ftp_send_response_buffer(session, buffer, len); - else - ftp_send_response_buffer(session, key.name, strlen(key.name)); - free(buffer); - - /* send args (if any) */ - if(*args != 0) - { - ftp_send_response_buffer(session, " ", 1); - - len = strlen(args); - buffer = encode_path(args, &len, false); - if(buffer != NULL) - ftp_send_response_buffer(session, buffer, len); - else - ftp_send_response_buffer(session, args, strlen(args)); - free(buffer); - } - - /* send footer */ - ftp_send_response_buffer(session, "\"\r\n", 3); - } - else if(session->state != COMMAND_STATE) - { - /* only some commands are available during data transfer */ - if(strcasecmp(command->name, "ABOR") != 0 - && strcasecmp(command->name, "STAT") != 0 - && strcasecmp(command->name, "QUIT") != 0) - { - ftp_send_response(session, 503, "Invalid command during transfer\r\n"); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_session_close_cmd(session); - } - else - command->handler(session, args); - } - else - { - /* clear RENAME flag for all commands except RNTO */ - if(strcasecmp(command->name, "RNTO") != 0) - session->flags &= ~SESSION_RENAME; - - command->handler(session, args); - } - - /* remove executed command from the command buffer */ - len = session->cmd_buffer + session->cmd_buffersize - next; - if(len > 0) - memmove(session->cmd_buffer, next, len); - session->cmd_buffersize = len; - } - } -} - -/*! poll sockets for ftp session - * - * @param[in] session ftp session - * - * @returns next session - */ -static ftp_session_t* -ftp_session_poll(ftp_session_t *session) -{ - int rc; - struct pollfd pollinfo[2]; - nfds_t nfds = 1; - - /* the first pollfd is the command socket */ - pollinfo[0].fd = session->cmd_fd; - pollinfo[0].events = POLLIN | POLLPRI; - pollinfo[0].revents = 0; - - switch(session->state) - { - case COMMAND_STATE: - /* we are waiting to read a command */ - break; - - case DATA_CONNECT_STATE: - if(session->flags & SESSION_PASV) - { - /* we are waiting for a PASV connection */ - pollinfo[1].fd = session->pasv_fd; - pollinfo[1].events = POLLIN; - } - else - { - /* we are waiting to complete a PORT connection */ - pollinfo[1].fd = session->data_fd; - pollinfo[1].events = POLLOUT; - } - pollinfo[1].revents = 0; - nfds = 2; - break; - - case DATA_TRANSFER_STATE: - /* we need to transfer data */ - pollinfo[1].fd = session->data_fd; - if(session->flags & SESSION_RECV) - pollinfo[1].events = POLLIN; - else - pollinfo[1].events = POLLOUT; - pollinfo[1].revents = 0; - nfds = 2; - break; - } - - /* poll the selected sockets */ - rc = poll(pollinfo, nfds, 0); - if(rc < 0) - { - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - else if(rc > 0) - { - /* check the command socket */ - if(pollinfo[0].revents != 0) - { - /* handle command */ - if(pollinfo[0].revents & POLL_UNKNOWN) - console_print(YELLOW "cmd_fd: revents=0x%08X\n" RESET, pollinfo[0].revents); - - /* we need to read a new command */ - if(pollinfo[0].revents & (POLLERR|POLLHUP)) - { - debug_print("cmd revents=0x%x\n", pollinfo[0].revents); - ftp_session_close_cmd(session); - } - else if(pollinfo[0].revents & (POLLIN | POLLPRI)) - ftp_session_read_command(session, pollinfo[0].revents); - } - - /* check the data/pasv socket */ - if(nfds > 1 && pollinfo[1].revents != 0) - { - switch(session->state) - { - case COMMAND_STATE: - /* this shouldn't happen? */ - break; - - case DATA_CONNECT_STATE: - if(pollinfo[1].revents & POLL_UNKNOWN) - console_print(YELLOW "pasv_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); - - /* we need to accept the PASV connection */ - if(pollinfo[1].revents & (POLLERR|POLLHUP)) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Data connection failed\r\n"); - } - else if(pollinfo[1].revents & POLLIN) - { - if(ftp_session_accept(session) != 0) - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - } - else if(pollinfo[1].revents & POLLOUT) - { - - console_print(CYAN "connected to %s:%u\n" RESET, - inet_ntoa(session->peer_addr.sin_addr), - ntohs(session->peer_addr.sin_port)); - - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - ftp_send_response(session, 150, "Ready\r\n"); - } - break; - - case DATA_TRANSFER_STATE: - if(pollinfo[1].revents & POLL_UNKNOWN) - console_print(YELLOW "data_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); - - /* we need to transfer data */ - if(pollinfo[1].revents & (POLLERR|POLLHUP)) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Data connection failed\r\n"); - } - else if(pollinfo[1].revents & (POLLIN|POLLOUT)) - ftp_session_transfer(session); - break; - } - } - } - - /* still connected to peer; return next session */ - if(session->cmd_fd >= 0) - return session->next; - - /* disconnected from peer; destroy it and return next session */ - debug_print("disconnected from peer\n"); - return ftp_session_destroy(session); -} - -/* Update free space in status bar */ -static void -update_free_space(void) -{ -#ifdef _3DS -#define KiB (1024.0) -#define MiB (1024.0*KiB) -#define GiB (1024.0*MiB) - char buffer[16]; - struct statvfs st; - double bytes_free; - int rc, len; - - rc = statvfs("sdmc:/", &st); - if(rc != 0) - console_print(RED "statvfs: %d %s\n" RESET, errno, strerror(errno)); - else - { - bytes_free = (double)st.f_bsize * st.f_bfree; - - if (bytes_free < 1000.0) - len = snprintf(buffer, sizeof(buffer), "%.0lfB", bytes_free); - else if(bytes_free < 10.0*KiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfKiB", floor((bytes_free*100.0)/KiB)/100.0); - else if(bytes_free < 100.0*KiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfKiB", floor((bytes_free*10.0)/KiB)/10.0); - else if(bytes_free < 1000.0*KiB) - len = snprintf(buffer, sizeof(buffer), "%.0lfKiB", floor(bytes_free/KiB)); - else if(bytes_free < 10.0*MiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfMiB", floor((bytes_free*100.0)/MiB)/100.0); - else if(bytes_free < 100.0*MiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfMiB", floor((bytes_free*10.0)/MiB)/10.0); - else if(bytes_free < 1000.0*MiB) - len = snprintf(buffer, sizeof(buffer), "%.0lfMiB", floor(bytes_free/MiB)); - else if(bytes_free < 10.0*GiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfGiB", floor((bytes_free*100.0)/GiB)/100.0); - else if(bytes_free < 100.0*GiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfGiB", floor((bytes_free*10.0)/GiB)/10.0); - else - len = snprintf(buffer, sizeof(buffer), "%.0lfGiB", floor(bytes_free/GiB)); - - console_set_status("\x1b[0;%dH" GREEN "%s", 50-len, buffer); - } -#endif -} - -/*! Update status bar */ -static int -update_status(void) -{ - update_free_space(); - char hostname[128]; - socklen_t addrlen = sizeof(serv_addr); - int rc; - - rc = getsockname(listenfd, (struct sockaddr*)&serv_addr, &addrlen); - if(rc != 0) - { - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - rc = gethostname(hostname, sizeof(hostname)); - if(rc != 0) - { - console_print(RED "gethostname: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - return 0; -} - -#ifdef _3DS -/*! Handle apt events - * - * @param[in] type Event type - * @param[in] closure Callback closure - */ -static void -apt_hook(APT_HookType type, - void *closure) -{ - switch(type) - { - case APTHOOK_ONSUSPEND: - case APTHOOK_ONSLEEP: - /* turn on backlight, or you can't see the home menu! */ - if(R_SUCCEEDED(gspLcdInit())) - { - GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH); - gspLcdExit(); - } - break; - - case APTHOOK_ONRESTORE: - case APTHOOK_ONWAKEUP: - /* restore backlight power state */ - if(R_SUCCEEDED(gspLcdInit())) - { - (lcd_power ? GSPLCD_PowerOnBacklight : GSPLCD_PowerOffBacklight)(GSPLCD_SCREEN_BOTH); - gspLcdExit(); - } - break; - - default: - break; - } -} -#endif - -/*! initialize ftp subsystem */ -int -ftp_init(void) -{ - int rc; - - start_time = time(NULL); - -#ifdef _3DS - Result ret = 0; - u32 wifi = 0; - bool loop = true; - - /* register apt hook */ - aptHook(&cookie, apt_hook, NULL); - - console_print(GREEN "Waiting for wifi...\n" RESET); - - /* wait for wifi to be available */ - /*while((loop = aptMainLoop()) && !wifi && (ret == 0 || ret == 0xE0A09D2E)) - { - ret = 0; - - hidScanInput(); - if(hidKeysDown() & KEY_B) - { - // user canceled - loop = false; - break; - } - - // update the wifi status - ret = ACU_GetWifiStatus(&wifi); - if(ret != 0) - wifi = 0; - }*/ - - ret = 0; - - // update the wifi status - ret = ACU_GetWifiStatus(&wifi); - if(ret != 0) - wifi = 0; - - /* check if there was a wifi error */ - if(ret != 0) - console_print(RED "ACU_GetWifiStatus returns 0x%lx\n" RESET, ret); - - /* check if we need to exit */ - if(!loop || ret != 0) - return -1; - - console_print(GREEN "Ready!\n" RESET); - - /* allocate buffer for SOC service */ - SOCU_buffer = (u32*)memalign(SOCU_ALIGN, SOCU_BUFFERSIZE); - if(SOCU_buffer == NULL) - { - console_print(RED "memalign: failed to allocate\n" RESET); - goto memalign_fail; - } - - /* initialize SOC service */ - ret = socInit(SOCU_buffer, SOCU_BUFFERSIZE); - if(ret != 0) - { - console_print(RED "socInit: 0x%08X\n" RESET, (unsigned int)ret); - goto soc_fail; - } -#endif - - /* allocate socket to listen for clients */ - listenfd = socket(AF_INET, SOCK_STREAM, 0); - if(listenfd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* get address to listen on */ - serv_addr.sin_family = AF_INET; -#ifdef _3DS - serv_addr.sin_addr.s_addr = gethostid(); - serv_addr.sin_port = htons(LISTEN_PORT); -#else - serv_addr.sin_addr.s_addr = INADDR_ANY; - serv_addr.sin_port = htons(LISTEN_PORT); -#endif - - /* reuse address */ - { - int yes = 1; - rc = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - if(rc != 0) - { - console_print(RED "setsockopt: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - } - - /* bind socket to listen address */ - rc = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); - if(rc != 0) - { - console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* listen on socket */ - rc = listen(listenfd, 5); - if(rc != 0) - { - console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* print server address */ - rc = update_status(); - if(rc != 0) - { - ftp_exit(); - return -1; - } - - return 0; - -#ifdef _3DS -soc_fail: - free(SOCU_buffer); - SOCU_buffer = NULL; - -memalign_fail: - return -1; -#endif -} - -/*! deinitialize ftp subsystem */ -void -ftp_exit(void) -{ -#ifdef _3DS - Result ret; -#endif - - /* unhook aptHookCookie */ - aptUnhook(&cookie); - - debug_print("exiting ftp server\n"); - - /* clean up all sessions */ - while(sessions != NULL) - ftp_session_destroy(sessions); - - /* stop listening for new clients */ - if(listenfd >= 0) - ftp_closesocket(listenfd, false); - -#ifdef _3DS - /* deinitialize SOC service */ - console_render(); - console_print(CYAN "Waiting for socExit()...\n" RESET); - - if(SOCU_buffer != NULL) - { - ret = socExit(); - if(ret != 0) - console_print(RED "socExit: 0x%08X\n" RESET, (unsigned int)ret); - free(SOCU_buffer); - } -#endif -} - -/*! ftp look - * - * @returns whether to keep looping - */ -loop_status_t -ftp_loop(void) -{ - int rc; - struct pollfd pollinfo; - ftp_session_t *session; - - /* we will poll for new client connections */ - pollinfo.fd = listenfd; - pollinfo.events = POLLIN; - pollinfo.revents = 0; - - /* poll for a new client */ - rc = poll(&pollinfo, 1, 0); - if(rc < 0) - { - /* wifi got disabled */ - if(errno == ENETDOWN) - return LOOP_RESTART; - - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - return LOOP_EXIT; - } - else if(rc > 0) - { - if(pollinfo.revents & POLLIN) - { - /* we got a new client */ - ftp_session_new(listenfd); - } - else - { - console_print(YELLOW "listenfd: revents=0x%08X\n" RESET, pollinfo.revents); - } - } - - /* poll each session */ - session = sessions; - while(session != NULL) - session = ftp_session_poll(session); - -#ifdef _3DS - /* check if the user wants to exit */ - hidScanInput(); - u32 down = hidKeysDown(); - - if(down & KEY_B) - return LOOP_EXIT; - - /* check if the user wants to toggle the LCD power */ - if(down & KEY_START) - { - lcd_power = !lcd_power; - apt_hook(APTHOOK_ONRESTORE, NULL); - } -#endif - - return LOOP_CONTINUE; -} - -/*! change to parent directory - * - * @param[in] session ftp session - */ -static void -cd_up(ftp_session_t *session) -{ - char *slash = NULL, *p; - - /* remove basename from cwd */ - for(p = session->cwd; *p; ++p) - { - if(*p == '/') - slash = p; - } - *slash = 0; - if(strlen(session->cwd) == 0) - strcat(session->cwd, "/"); -} - -/*! validate a path - * - * @param[in] args path to validate - */ -static int -validate_path(const char *args) -{ - const char *p; - - /* make sure no path components are '..' */ - p = args; - while((p = strstr(p, "/..")) != NULL) - { - if(p[3] == 0 || p[3] == '/') - return -1; - } - - /* make sure there are no '//' */ - if(strstr(args, "//") != NULL) - return -1; - - return 0; -} - -/*! get a path relative to cwd - * - * @param[in] session ftp session - * @param[in] cwd working directory - * @param[in] args path to make - * - * @returns error - * - * @note the output goes to session->buffer - */ -static int -build_path(ftp_session_t *session, - const char *cwd, - const char *args) -{ - int rc; - char *p; - - session->buffersize = 0; - memset(session->buffer, 0, sizeof(session->buffer)); - - /* make sure the input is a valid path */ - if(validate_path(args) != 0) - { - errno = EINVAL; - return -1; - } - - if(args[0] == '/') - { - /* this is an absolute path */ - size_t len = strlen(args); - if(len > sizeof(session->buffer)-1) - { - errno = ENAMETOOLONG; - return -1; - } - - memcpy(session->buffer, args, len); - session->buffersize = len; - } - else - { - /* this is a relative path */ - if(strcmp(cwd, "/") == 0) - rc = snprintf(session->buffer, sizeof(session->buffer), "/%s", - args); - else - rc = snprintf(session->buffer, sizeof(session->buffer), "%s/%s", - cwd, args); - - if(rc >= sizeof(session->buffer)) - { - errno = ENAMETOOLONG; - return -1; - } - - session->buffersize = rc; - } - - /* remove trailing / */ - p = session->buffer + session->buffersize; - while(p > session->buffer && *--p == '/') - { - *p = 0; - --session->buffersize; - } - - /* if we ended with an empty path, it is the root directory */ - if(session->buffersize == 0) - session->buffer[session->buffersize++] = '/'; - - return 0; -} - -/*! transfer a directory listing - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -list_transfer(ftp_session_t *session) -{ - ssize_t rc; - size_t len; - char *buffer; - struct stat st; - struct dirent *dent; - - /* check if we sent all available data */ - if(session->bufferpos == session->buffersize) - { - /* check xfer dir type */ - if(session->dir_mode == XFER_DIR_STAT) - rc = 213; - else - { - rc = 226; - memset(ftp_file_transfer, 0, 50); // Empty transfer status - isTransfering = false; - } - - /* check if this was for a file */ - if(session->dp == NULL) - { - /* we already sent the file's listing */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, rc, "OK\r\n"); - return LOOP_EXIT; - } - - /* get the next directory entry */ - dent = readdir(session->dp); - if(dent == NULL) - { - /* we have exhausted the directory listing */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, rc, "OK\r\n"); - return LOOP_EXIT; - } - - /* TODO I think we are supposed to return entries for . and .. */ - if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) - return LOOP_CONTINUE; - - /* check if this was a NLST */ - if(session->dir_mode == XFER_DIR_NLST) - { - /* NLST gives the whole path name */ - session->buffersize = 0; - if(build_path(session, session->lwd, dent->d_name) == 0) - { - /* encode \n in path */ - len = session->buffersize; - buffer = encode_path(session->buffer, &len, false); - if(buffer != NULL) - { - /* copy to the session buffer to send */ - memcpy(session->buffer, buffer, len); - free(buffer); - session->buffer[len++] = '\r'; - session->buffer[len++] = '\n'; - session->buffersize = len; - } - } - } - else - { -#ifdef _3DS - /* the sdmc directory entry already has the type and size, so no need to do a slow stat */ - u32 magic = *(u32*)session->dp->dirData->dirStruct; - - if(magic == ARCHIVE_DIRITER_MAGIC) - { - archive_dir_t *dir = (archive_dir_t*)session->dp->dirData->dirStruct; - FS_DirectoryEntry *entry = &dir->entry_data[dir->index]; - - if(entry->attributes & FS_ATTRIBUTE_DIRECTORY) - st.st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; - else - st.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; - - if(!(entry->attributes & FS_ATTRIBUTE_READ_ONLY)) - st.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - - st.st_size = entry->fileSize; - st.st_mtime = 0; - - bool getmtime = true; - if(session->dir_mode == XFER_DIR_MLSD - || session->dir_mode == XFER_DIR_MLST) - { - if(!(session->mlst_flags & SESSION_MLST_MODIFY)) - getmtime = false; - } - else if(session->dir_mode == XFER_DIR_NLST) - getmtime = false; - - if((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if(getmtime) - { - uint64_t mtime = 0; - if((rc = archive_getmtime(session->buffer, &mtime)) != 0) - console_print(RED "archive_getmtime '%s': 0x%x\n" RESET, session->buffer, rc); - else - st.st_mtime = mtime; - } - } - else - { - /* lstat the entry */ - if((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if((rc = lstat(session->buffer, &st)) != 0) - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - - if(rc != 0) - { - /* an error occurred */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - return LOOP_EXIT; - } - } -#else - /* lstat the entry */ - if((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if((rc = lstat(session->buffer, &st)) != 0) - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - - if(rc != 0) - { - /* an error occurred */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - return LOOP_EXIT; - } -#endif - /* encode \n in path */ - len = strlen(dent->d_name); - buffer = encode_path(dent->d_name, &len, false); - if(buffer != NULL) - { - rc = ftp_session_fill_dirent(session, &st, buffer, len); - free(buffer); - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "%s\r\n", strerror(rc)); - return LOOP_EXIT; - } - } - else - session->buffersize = 0; - } - session->bufferpos = 0; - } - - /* send any pending data */ - rc = send(session->data_fd, session->buffer + session->bufferpos, - session->buffersize - session->bufferpos, 0); - if(rc <= 0) - { - /* error sending data */ - if(rc < 0) - { - if(errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - } - else - console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we can try to send more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! send a file to the client - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -retrieve_transfer(ftp_session_t *session) -{ - ssize_t rc; - - if(session->bufferpos == session->buffersize) - { - /* we have sent all the data so read some more */ - rc = ftp_session_read_file(session); - if(rc <= 0) - { - /* can't read any more data */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - if(rc < 0) - ftp_send_response(session, 451, "Failed to read file\r\n"); - else - { - ftp_send_response(session, 226, "OK\r\n"); - memset(ftp_file_transfer, 0, 50); // Empty transfer status - isTransfering = false; - } - return LOOP_EXIT; - } - - /* we read some data so reset the session buffer to send */ - session->bufferpos = 0; - session->buffersize = rc; - } - - /* send any pending data */ - rc = send(session->data_fd, session->buffer + session->bufferpos, - session->buffersize - session->bufferpos, 0); - if(rc <= 0) - { - /* error sending data */ - if(rc < 0) - { - if(errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - } - else - console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we can try to send more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! send a file to the client - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -store_transfer(ftp_session_t *session) -{ - ssize_t rc; - - if(session->bufferpos == session->buffersize) - { - /* we have written all the received data, so try to get some more */ - rc = recv(session->data_fd, session->buffer, sizeof(session->buffer), 0); - if(rc <= 0) - { - /* can't read any more data */ - if(rc < 0) - { - if(errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - - if(rc == 0) - ftp_send_response(session, 226, "OK\r\n"); - else - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we received some data so reset the session buffer to write */ - session->bufferpos = 0; - session->buffersize = rc; - } - - rc = ftp_session_write_file(session); - if(rc <= 0) - { - /* error writing data */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 451, "Failed to write file\r\n"); - return LOOP_EXIT; - } - - /* we can try to receive more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! ftp_xfer_file mode */ -typedef enum -{ - XFER_FILE_RETR, /*!< Retrieve a file */ - XFER_FILE_STOR, /*!< Store a file */ - XFER_FILE_APPE, /*!< Append a file */ -} xfer_file_mode_t; - -/*! Transfer a file - * - * @param[in] session ftp session - * @param[in] args ftp arguments - * @param[in] mode transfer mode - * - * @returns failure - */ -static void -ftp_xfer_file(ftp_session_t *session, - const char *args, - xfer_file_mode_t mode) -{ - int rc; - - /* build the path of the file to transfer */ - if(build_path(session, session->cwd, args) != 0) - { - rc = errno; - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 553, "%s\r\n", strerror(rc)); - return; - } - - /* open the file for retrieving or storing */ - if(mode == XFER_FILE_RETR) - rc = ftp_session_open_file_read(session); - else - rc = ftp_session_open_file_write(session, mode == XFER_FILE_APPE); - - if(rc != 0) - { - /* error opening the file */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 450, "failed to open file\r\n"); - return; - } - - if(session->flags & (SESSION_PORT|SESSION_PASV)) - { - ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); - - if(session->flags & SESSION_PORT) - { - /* setup connection */ - rc = ftp_session_connect(session); - if(rc != 0) - { - /* error connecting */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "can't open data connection\r\n"); - return; - } - } - - /* set up the transfer */ - session->flags &= ~(SESSION_RECV|SESSION_SEND); - if(mode == XFER_FILE_RETR) - { - session->flags |= SESSION_SEND; - session->transfer = retrieve_transfer; - } - else - { - session->flags |= SESSION_RECV; - session->transfer = store_transfer; - } - - session->bufferpos = 0; - session->buffersize = 0; - - return; - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); -} - -/*! Transfer a directory - * - * @param[in] session ftp session - * @param[in] args ftp arguments - * @param[in] mode transfer mode - * @param[in] workaround whether to workaround LIST -a - */ -static void -ftp_xfer_dir(ftp_session_t *session, - const char *args, - xfer_dir_mode_t mode, - bool workaround) -{ - ssize_t rc; - size_t len; - struct stat st; - char *buffer; - - /* set up the transfer */ - session->dir_mode = mode; - session->flags &= ~SESSION_RECV; - session->flags |= SESSION_SEND; - - session->transfer = list_transfer; - session->buffersize = 0; - session->bufferpos = 0; - - if(strlen(args) > 0) - { - /* an argument was provided */ - if(build_path(session, session->cwd, args) != 0) - { - /* error building path */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - /* check if this is a directory */ - session->dp = opendir(session->buffer); - if(session->dp == NULL) - { - /* not a directory; check if it is a file */ - rc = stat(session->buffer, &st); - if(rc != 0) - { - /* error getting stat */ - rc = errno; - - /* work around broken clients that think LIST -a is valid */ - if(workaround && mode == XFER_DIR_LIST) - { - if(args[0] == '-' && (args[1] == 'a' || args[1] == 'l')) - { - if(args[2] == 0) - buffer = strdup(args+2); - else - buffer = strdup(args+3); - - if(buffer != NULL) - { - ftp_xfer_dir(session, buffer, mode, false); - free(buffer); - return; - } - - rc = ENOMEM; - } - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - else if(mode == XFER_DIR_MLSD) - { - /* specified file instead of directory for MLSD */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - else if(mode == XFER_DIR_NLST) - { - /* NLST uses full path name */ - len = session->buffersize; - buffer = encode_path(session->buffer, &len, false); - } - else - { - /* everything else uses base name */ - const char *base = strrchr(session->buffer, '/') + 1; - - len = strlen(base); - buffer = encode_path(base, &len, false); - } - - if(buffer) - { - rc = ftp_session_fill_dirent(session, &st, buffer, len); - free(buffer); - } - else - rc = ENOMEM; - - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - else - { - /* it was a directory, so set it as the lwd */ - memcpy(session->lwd, session->buffer, session->buffersize); - session->lwd[session->buffersize] = 0; - session->buffersize = 0; - - if(session->dir_mode == XFER_DIR_MLSD - && (session->mlst_flags & SESSION_MLST_TYPE)) - { - /* send this directory as type=cdir */ - rc = ftp_session_fill_dirent_cdir(session, session->lwd); - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - } - } - else if(ftp_session_open_cwd(session) != 0) - { - /* no argument, but opening cwd failed */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - else - { - /* set the cwd as the lwd */ - strcpy(session->lwd, session->cwd); - session->buffersize = 0; - - if(session->dir_mode == XFER_DIR_MLSD - && (session->mlst_flags & SESSION_MLST_TYPE)) - { - /* send this directory as type=cdir */ - rc = ftp_session_fill_dirent_cdir(session, session->lwd); - if(rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - } - - if(mode == XFER_DIR_MLST || mode == XFER_DIR_STAT) - { - /* this is a little different; we have to send the data over the command socket */ - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV | CLOSE_DATA); - session->data_fd = session->cmd_fd; - session->flags |= SESSION_SEND; - ftp_send_response(session, -213, "Status\r\n"); - return; - } - else if(session->flags & (SESSION_PORT|SESSION_PASV)) - { - ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); - - if(session->flags & SESSION_PORT) - { - /* setup connection */ - rc = ftp_session_connect(session); - if(rc != 0) - { - /* error connecting */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "can't open data connection\r\n"); - } - } - - return; - } - - /* we must have got LIST/MLSD/MLST/NLST without a preceding PORT or PASV */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * F T P C O M M A N D S * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -/*! @fn static void ABOR(ftp_session_t *session, const char *args) - * - * @brief abort a transfer - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(ABOR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - if(session->state == COMMAND_STATE) - { - ftp_send_response(session, 225, "No transfer to abort\r\n"); - return; - } - - /* abort the transfer */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - - /* send response for this request */ - ftp_send_response(session, 225, "Aborted\r\n"); - - /* send response for transfer */ - ftp_send_response(session, 425, "Transfer aborted\r\n"); -} - -/*! @fn static void ALLO(ftp_session_t *session, const char *args) - * - * @brief allocate space - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(ALLO) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 202, "superfluous command\r\n"); -} - -/*! @fn static void APPE(ftp_session_t *session, const char *args) - * - * @brief append data to a file - * - * @note requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(APPE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the file in append mode */ - ftp_xfer_file(session, args, XFER_FILE_APPE); -} - -/*! @fn static void CDUP(ftp_session_t *session, const char *args) - * - * @brief CWD to parent directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(CDUP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* change to parent directory */ - cd_up(session); - - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void CWD(ftp_session_t *session, const char *args) - * - * @brief change working directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(CWD) -{ - struct stat st; - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* .. is equivalent to CDUP */ - if(strcmp(args, "..") == 0) - { - cd_up(session); - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - /* build the new cwd path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* get the path status */ - rc = stat(session->buffer, &st); - if(rc != 0) - { - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - ftp_send_response(session, 550, "unavailable\r\n"); - return; - } - - /* make sure it is a directory */ - if(!S_ISDIR(st.st_mode)) - { - ftp_send_response(session, 553, "not a directory\r\n"); - return; - } - - /* copy the path into the cwd */ - strncpy(session->cwd, session->buffer, sizeof(session->cwd)); - - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void DELE(ftp_session_t *session, const char *args) - * - * @brief delete a file - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(DELE) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the file path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* try to unlink the path */ - rc = unlink(session->buffer); - if(rc != 0) - { - /* error unlinking the file */ - console_print(RED "unlink: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to delete file\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void FEAT(ftp_session_t *session, const char *args) - * - * @brief list server features - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(FEAT) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* list our features */ - ftp_send_response(session, -211, "\r\n" - " MDTM\r\n" - " MLST Type%s;Size%s;Modify%s;Perm%s;UNIX.mode%s;\r\n" - " PASV\r\n" - " SIZE\r\n" - " TVFS\r\n" - " UTF8\r\n" - "\r\n" - "211 End\r\n", - session->mlst_flags & SESSION_MLST_TYPE ? "*" : "", - session->mlst_flags & SESSION_MLST_SIZE ? "*" : "", - session->mlst_flags & SESSION_MLST_MODIFY ? "*" : "", - session->mlst_flags & SESSION_MLST_PERM ? "*" : "", - session->mlst_flags & SESSION_MLST_UNIX_MODE ? "*" : ""); -} - -/*! @fn static void HELP(ftp_session_t *session, const char *args) - * - * @brief print server help - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(HELP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* list our accepted commands */ - ftp_send_response(session, -214, - "The following commands are recognized\r\n" - " ABOR ALLO APPE CDUP CWD DELE FEAT HELP LIST MDTM MKD MLSD MLST MODE\r\n" - " NLST NOOP OPTS PASS PASV PORT PWD QUIT REST RETR RMD RNFR RNTO STAT\r\n" - " STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD XPWD XRMD\r\n" - "214 End\r\n"); -} - -/*! @fn static void LIST(ftp_session_t *session, const char *args) - * - * @brief retrieve a directory listing - * - * @note Requires a PORT or PASV connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(LIST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in LIST mode */ - ftp_xfer_dir(session, args, XFER_DIR_LIST, true); -} - -/*! @fn static void MDTM(ftp_session_t *session, const char *args) - * - * @brief get last modification time - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MDTM) -{ - int rc; -#ifdef _3DS - uint64_t mtime; -#else - struct stat st; -#endif - time_t t_mtime; - struct tm *tm; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - -#ifdef _3DS - rc = archive_getmtime(session->buffer, &mtime); - if(rc != 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - t_mtime = mtime; -#else - rc = stat(session->buffer, &st); - if(rc != 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - t_mtime = st.st_mtime; -#endif - - tm = gmtime(&t_mtime); - if(tm == NULL) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - - session->buffersize = strftime(session->buffer, sizeof(session->buffer), "%Y%m%d%H%M%S", tm); - if(session->buffersize == 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - - session->buffer[session->buffersize] = 0; - - ftp_send_response(session, 213, "%s\r\n", session->buffer); -} -/*! @fn static void MKD(ftp_session_t *session, const char *args) - * - * @brief create a directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MKD) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* try to create the directory */ - rc = mkdir(session->buffer, 0755); - if(rc != 0 && errno != EEXIST) - { - /* mkdir failure */ - console_print(RED "mkdir: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to create directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void MLSD(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MLSD) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in MLSD mode */ - ftp_xfer_dir(session, args, XFER_DIR_MLSD, true); -} - -/*! @fn static void MLST(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MLST) -{ - struct stat st; - int rc; - char *path; - size_t len; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 501, "%s\r\n", strerror(errno)); - return; - } - - /* stat path */ - rc = lstat(session->buffer, &st); - if(rc != 0) - { - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - /* encode \n in path */ - len = session->buffersize; - path = encode_path(session->buffer, &len, true); - if(!path) - { - ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); - return; - } - - session->dir_mode = XFER_DIR_MLST; - rc = ftp_session_fill_dirent(session, &st, path, len); - free(path); - if(rc != 0) - { - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - path = malloc(session->buffersize + 1); - if(!path) - { - ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); - return; - } - - memcpy(path, session->buffer, session->buffersize); - path[session->buffersize] = 0; - ftp_send_response(session, -250, "Status\r\n%s250 End\r\n", path); - free(path); -} - -/*! @fn static void MODE(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MODE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we only accept S (stream) mode */ - if(strcasecmp(args, "S") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - ftp_send_response(session, 504, "unavailable\r\n"); -} - -/*! @fn static void NLST(ftp_session_t *session, const char *args) - * - * @brief retrieve a name list - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(NLST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in NLST mode */ - return ftp_xfer_dir(session, args, XFER_DIR_NLST, false); -} - -/*! @fn static void NOOP(ftp_session_t *session, const char *args) - * - * @brief no-op - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(NOOP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* this is a no-op */ - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void OPTS(ftp_session_t *session, const char *args) - * - * @brief set options - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(OPTS) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we accept the following UTF8 options */ - if(strcasecmp(args, "UTF8") == 0 - || strcasecmp(args, "UTF8 ON") == 0 - || strcasecmp(args, "UTF8 NLST") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - /* check MLST options */ - if(strncasecmp(args, "MLST ", 5) == 0) - { - static const struct - { - const char *name; - session_mlst_flags_t flag; - } mlst_flags[] = - { - { "Type;", SESSION_MLST_TYPE, }, - { "Size;", SESSION_MLST_SIZE, }, - { "Modify;", SESSION_MLST_MODIFY, }, - { "Perm;", SESSION_MLST_PERM, }, - { "UNIX.mode;", SESSION_MLST_UNIX_MODE, }, - }; - static const size_t num_mlst_flags = sizeof(mlst_flags)/sizeof(mlst_flags[0]); - - session_mlst_flags_t flags = 0; - args += 5; - const char *p = args; - while(*p) - { - for(size_t i = 0; i < num_mlst_flags; ++i) - { - if(strncasecmp(mlst_flags[i].name, p, strlen(mlst_flags[i].name)) == 0) - { - flags |= mlst_flags[i].flag; - p += strlen(mlst_flags[i].name)-1; - break; - } - } - - while(*p && *p != ';') - ++p; - - if(*p == ';') - ++p; - } - - session->mlst_flags = flags; - ftp_send_response(session, 200, "MLST OPTS%s%s%s%s%s%s\r\n", - flags ? " " : "", - flags & SESSION_MLST_TYPE ? "Type;" : "", - flags & SESSION_MLST_SIZE ? "Size;" : "", - flags & SESSION_MLST_MODIFY ? "Modify;" : "", - flags & SESSION_MLST_PERM ? "Perm;" : "", - flags & SESSION_MLST_UNIX_MODE ? "UNIX.mode;" : ""); - return; - } - - ftp_send_response(session, 504, "invalid argument\r\n"); -} - -/*! @fn static void PASS(ftp_session_t *session, const char *args) - * - * @brief provide password - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PASS) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* we accept any password */ - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 230, "OK\r\n"); -} - -/*! @fn static void PASV(ftp_session_t *session, const char *args) - * - * @brief request an address to connect to - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PASV) -{ - int rc; - char buffer[INET_ADDRSTRLEN + 10]; - char *p; - in_port_t port; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - memset(buffer, 0, sizeof(buffer)); - - /* reset the state */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - session->flags &= ~(SESSION_PASV|SESSION_PORT); - - /* create a socket to listen on */ - session->pasv_fd = socket(AF_INET, SOCK_STREAM, 0); - if(session->pasv_fd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* set the socket options */ - rc = ftp_set_socket_options(session->pasv_fd); - if(rc != 0) - { - /* failed to set socket options */ - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* grab a new port */ - session->pasv_addr.sin_port = htons(next_data_port()); - -#ifdef _3DS - console_print(YELLOW "binding to %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); -#endif - - /* bind to the port */ - rc = bind(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, - sizeof(session->pasv_addr)); - if(rc != 0) - { - /* failed to bind */ - console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* listen on the socket */ - rc = listen(session->pasv_fd, 1); - if(rc != 0) - { - /* failed to listen */ - console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - -#ifndef _3DS - { - /* get the socket address since we requested an ephemeral port */ - socklen_t addrlen = sizeof(session->pasv_addr); - rc = getsockname(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, - &addrlen); - if(rc != 0) - { - /* failed to get socket address */ - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - } -#endif - - /* we are now listening on the socket */ - console_print(YELLOW "listening on %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); - session->flags |= SESSION_PASV; - - /* print the address in the ftp format */ - port = ntohs(session->pasv_addr.sin_port); - strcpy(buffer, inet_ntoa(session->pasv_addr.sin_addr)); - sprintf(buffer+strlen(buffer), ",%u,%u", - port >> 8, port & 0xFF); - for(p = buffer; *p; ++p) - { - if(*p == '.') - *p = ','; - } - - ftp_send_response(session, 227, "%s\r\n", buffer); -} - -/*! @fn static void PORT(ftp_session_t *session, const char *args) - * - * @brief provide an address for the server to connect to - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PORT) -{ - char *addrstr, *p, *portstr; - int commas = 0, rc; - short port = 0; - unsigned long val; - struct sockaddr_in addr; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* reset the state */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - session->flags &= ~(SESSION_PASV|SESSION_PORT); - - /* dup the args since they are const and we need to change it */ - addrstr = strdup(args); - if(addrstr == NULL) - { - ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); - return; - } - - /* replace a,b,c,d,e,f with a.b.c.d\0e.f */ - for(p = addrstr; *p; ++p) - { - if(*p == ',') - { - if(commas != 3) - *p = '.'; - else - { - *p = 0; - portstr = p+1; - } - ++commas; - } - } - - /* make sure we got the right number of values */ - if(commas != 5) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - - /* parse the address */ - rc = inet_aton(addrstr, &addr.sin_addr); - if(rc == 0) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - - /* parse the port */ - val = 0; - port = 0; - for(p = portstr; *p; ++p) - { - if(!isdigit((int)*p)) - { - if(p == portstr || *p != '.' || val > 0xFF) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - port <<= 8; - port += val; - val = 0; - } - else - { - val *= 10; - val += *p - '0'; - } - } - - /* validate the port */ - if(val > 0xFF || port > 0xFF) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - port <<= 8; - port += val; - - /* fill in the address port and family */ - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - - free(addrstr); - - memcpy(&session->peer_addr, &addr, sizeof(addr)); - - /* we are ready to connect to the client */ - session->flags |= SESSION_PORT; - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void PWD(ftp_session_t *session, const char *args) - * - * @brief print working directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PWD) -{ - static char buffer[CMD_BUFFERSIZE]; - size_t len = sizeof(buffer), i; - char *path; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* encode the cwd */ - len = strlen(session->cwd); - path = encode_path(session->cwd, &len, true); - if(path != NULL) - { - i = sprintf(buffer, "257 \""); - if(i + len + 3 > sizeof(buffer)) - { - /* buffer will overflow */ - free(path); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - ftp_send_response(session, 425, "%s\r\n", strerror(EOVERFLOW)); - return; - } - memcpy(buffer+i, path, len); - free(path); - len += i; - buffer[len++] = '"'; - buffer[len++] = '\r'; - buffer[len++] = '\n'; - - ftp_send_response_buffer(session, buffer, len); - return; - } - - ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); -} - -/*! @fn static void QUIT(ftp_session_t *session, const char *args) - * - * @brief terminate ftp session - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(QUIT) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* disconnect from the client */ - ftp_send_response(session, 221, "disconnecting\r\n"); - ftp_session_close_cmd(session); -} - -/*! @fn static void REST(ftp_session_t *session, const char *args) - * - * @brief restart a transfer - * - * @note sets file position for a subsequent STOR operation - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(REST) -{ - const char *p; - uint64_t pos = 0; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* make sure an argument is provided */ - if(args == NULL) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - /* parse the offset */ - for(p = args; *p; ++p) - { - if(!isdigit((int)*p)) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - if(UINT64_MAX / 10 < pos) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - pos *= 10; - - if(UINT64_MAX - (*p - '0') < pos) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - pos += (*p - '0'); - } - - /* set the restart offset */ - session->filepos = pos; - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void RETR(ftp_session_t *session, const char *args) - * - * @brief retrieve a file - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RETR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - snprintf(ftp_file_transfer, sizeof(ftp_file_transfer), "Sending: %s", args ? args : ""); - isTransfering = true; - - /* open the file to retrieve */ - return ftp_xfer_file(session, args, XFER_FILE_RETR); -} - -/*! @fn static void RMD(ftp_session_t *session, const char *args) - * - * @brief remove a directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RMD) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to remove */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* remove the directory */ - rc = rmdir(session->buffer); - if(rc != 0) - { - /* rmdir error */ - console_print(RED "rmdir: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to delete directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void RNFR(ftp_session_t *session, const char *args) - * - * @brief rename from - * - * @note Must be followed by RNTO - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RNFR) -{ - int rc; - struct stat st; - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to rename from */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* make sure the path exists */ - rc = lstat(session->buffer, &st); - if(rc != 0) - { - /* error getting path status */ - console_print(RED "lstat: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 450, "no such file or directory\r\n"); - return; - } - - /* we are ready for RNTO */ - session->flags |= SESSION_RENAME; - ftp_send_response(session, 350, "OK\r\n"); -} - -/*! @fn static void RNTO(ftp_session_t *session, const char *args) - * - * @brief rename to - * - * @note Must be preceded by RNFR - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RNTO) -{ - static char rnfr[XFER_BUFFERSIZE]; // rename-from buffer - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* make sure the previous command was RNFR */ - if(!(session->flags & SESSION_RENAME)) - { - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); - return; - } - - /* clear the rename state */ - session->flags &= ~SESSION_RENAME; - - /* copy the RNFR path */ - memcpy(rnfr, session->buffer, XFER_BUFFERSIZE); - - /* build the path to rename to */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 554, "%s\r\n", strerror(errno)); - return; - } - - /* rename the file */ - rc = rename(rnfr, session->buffer); - if(rc != 0) - { - /* rename failure */ - console_print(RED "rename: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to rename file/directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void SIZE(ftp_session_t *session, const char *args) - * - * @brief get file size - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(SIZE) -{ - int rc; - struct stat st; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to stat */ - if(build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - rc = stat(session->buffer, &st); - if(rc != 0 || !S_ISREG(st.st_mode)) - { - ftp_send_response(session, 550, "Could not get file size.\r\n"); - return; - } - - ftp_send_response(session, 213, "%" PRIu64 "\r\n", - (uint64_t)st.st_size); -} - -/*! @fn static void STAT(ftp_session_t *session, const char *args) - * - * @brief get status - * - * @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. - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STAT) -{ - time_t uptime = time(NULL) - start_time; - int hours = uptime / 3600; - int minutes = (uptime / 60) % 60; - int seconds = uptime % 60; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - if(session->state == DATA_CONNECT_STATE) - { - /* we are waiting to connect to the client */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Waiting for data connection\r\n" - "211 End\r\n"); - return; - } - else if(session->state == DATA_TRANSFER_STATE) - { - /* we are in the middle of a transfer */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Transferred %" PRIu64 " bytes\r\n" - "211 End\r\n", - session->filepos); - return; - } - - if(strlen(args) == 0) - { - /* no argument provided, send the server status */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Uptime: %02d:%02d:%02d\r\n" - "211 End\r\n", - hours, minutes, seconds); - return; - } - - /* argument provided, open the path in STAT mode */ - ftp_xfer_dir(session, args, XFER_DIR_STAT, false); -} - -/*! @fn static void STOR(ftp_session_t *session, const char *args) - * - * @brief store a file - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STOR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - snprintf(ftp_file_transfer, sizeof(ftp_file_transfer), "Receiving: %s", args ? args : ""); - isTransfering = true; - - /* open the file to store */ - return ftp_xfer_file(session, args, XFER_FILE_STOR); -} - -/*! @fn static void STOU(ftp_session_t *session, const char *args) - * - * @brief store a unique file - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STOU) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* we do not support this yet */ - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 502, "unavailable\r\n"); -} - -/*! @fn static void STRU(ftp_session_t *session, const char *args) - * - * @brief set file structure - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STRU) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we only support F (no structure) mode */ - if(strcasecmp(args, "F") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - ftp_send_response(session, 504, "unavailable\r\n"); -} - -/*! @fn static void SYST(ftp_session_t *session, const char *args) - * - * @brief identify system - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(SYST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we are UNIX compliant with 8-bit characters */ - ftp_send_response(session, 215, "UNIX Type: L8\r\n"); -} - -/*! @fn static void TYPE(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @note transfer mode is always binary - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(TYPE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we always transfer in binary mode */ - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void USER(ftp_session_t *session, const char *args) - * - * @brief provide user name - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(USER) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we accept any user name */ - ftp_send_response(session, 230, "OK\r\n"); -}