diff --git a/source/menu/menu.hpp b/source/menu/menu.hpp
index 86e01bfa..894f0333 100644
--- a/source/menu/menu.hpp
+++ b/source/menu/menu.hpp
@@ -1016,6 +1016,7 @@ private:
void _PluginSettings();
void _CategorySettings(bool fromGameSet = false);
bool _Home();
+ bool _HomeFTP_Loop();
bool _ExitTo();
bool _Boot();
void _Paths();
diff --git a/source/menu/menu_download.cpp b/source/menu/menu_download.cpp
index 4ac7afa4..44a7f163 100644
--- a/source/menu/menu_download.cpp
+++ b/source/menu/menu_download.cpp
@@ -419,7 +419,7 @@ s32 CMenu::_networkComplete(s32 ok, void *usrData)
int CMenu::_initNetwork()
{
- while (net_get_status() == -EBUSY || m_thrdNetwork) {}; // Async initialization may be busy, wait to see if it succeeds.
+ while (net_get_status() == -EBUSY && m_thrdNetwork == true) { usleep(100); }; // Async initialization may be busy, wait to see if it succeeds.
if (networkInit) return 0;
if (!_isNetworkAvailable()) return -2;
diff --git a/source/menu/menu_home.cpp b/source/menu/menu_home.cpp
index 72581c30..178e83b5 100644
--- a/source/menu/menu_home.cpp
+++ b/source/menu/menu_home.cpp
@@ -1,11 +1,31 @@
-
+/****************************************************************************
+ * Copyright (C) 2013 FIX94
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ ****************************************************************************/
#include "menu.hpp"
#include "loader/cios.h"
#include "loader/nk.h"
#include "const_str.hpp"
+#include "network/net.h"
+#include "network/ftp.h"
+#include "network/http.h"
+#include "network/FTP_Dir.hpp"
s16 m_homeLblTitle;
s16 m_exittoLblTitle;
+s16 m_homeBtnBack;
s16 m_homeBtnSettings;
s16 m_homeBtnReloadCache;
@@ -15,7 +35,7 @@ s16 m_homeBtnExplorer;
s16 m_homeBtnInstall;
s16 m_homeBtnAbout;
s16 m_homeBtnExitTo;
-s16 m_homeBtnSource;
+s16 m_homeBtnFTP;
s16 m_homeBtnExitToHBC;
s16 m_homeBtnExitToMenu;
@@ -28,6 +48,19 @@ s16 m_homeLblUser[4];
TexData m_homeBg;
+bool CMenu::_HomeFTP_Loop(void)
+{
+ _mainLoopCommon();
+ if(BTN_HOME_PRESSED || BTN_B_PRESSED)
+ return true;
+ else if(BTN_A_PRESSED)
+ {
+ if(m_btnMgr.selected(m_homeBtnBack))
+ return true;
+ }
+ return false;
+}
+
bool CMenu::_Home(void)
{
SetupInput();
@@ -39,8 +72,8 @@ bool CMenu::_Home(void)
/* battery gets refreshed in here... */
_mainLoopCommon();
/* and it always changes so... */
- m_btnMgr.setText(m_homeLblBattery, wfmt(PLAYER_BATTERY_LABEL, min((float)wd[0]->battery_level/2, 100.f),
- min((float)wd[1]->battery_level/2, 100.f), min((float)wd[2]->battery_level/2, 100.f), min((float)wd[3]->battery_level/2, 100.f)));
+ m_btnMgr.setText(m_homeLblBattery, wfmt(PLAYER_BATTERY_LABEL, min((float)wd[0]->battery_level, 100.f),
+ min((float)wd[1]->battery_level, 100.f), min((float)wd[2]->battery_level, 100.f), min((float)wd[3]->battery_level, 100.f)));
if(BTN_A_PRESSED)
{
if(m_btnMgr.selected(m_homeBtnSettings))
@@ -103,14 +136,48 @@ bool CMenu::_Home(void)
_Explorer();
_showHome();
}
- else if(m_btnMgr.selected(m_homeBtnSource))
+ else if(m_btnMgr.selected(m_homeBtnFTP))
{
_hideHome();
- if(!_Source()) //Different source selected
+ /* net init as usual */
+ _initAsyncNetwork();
+ m_btnMgr.show(m_wbfsLblDialog);
+ m_btnMgr.show(m_homeBtnBack);
+ m_btnMgr.setText(m_wbfsLblDialog, _t("dlmsg1", L"Initializing network..."));
+ bool cancel = false;
+ while(!m_exit && m_thrdNetwork == true && net_get_status() == -EBUSY)
{
- LoadView();
+ if(_HomeFTP_Loop())
+ {
+ cancel = true;
+ break;
+ }
+ }
+ if(networkInit == false || cancel == true)
+ {
+ m_btnMgr.hide(m_wbfsLblDialog);
+ m_btnMgr.hide(m_homeBtnBack);
break;
}
+ /* start server finally */
+ ftp_init();
+ in_addr addr;
+ u32 ip = net_gethostip();
+ addr.s_addr = ip;
+ u16 m_ftp_port = 21;
+ int server = create_server(m_ftp_port);
+ m_btnMgr.setText(m_wbfsLblDialog, wfmt(_fmt("dlmsg28", L"Running FTP Server on %s:%u"), inet_ntoa(addr), m_ftp_port));
+ while(!m_exit)
+ {
+ if(_HomeFTP_Loop())
+ break;
+ process_ftp_events(server);
+ }
+ /* cleanup and closing */
+ cleanup_ftp();
+ net_close(server);
+ m_btnMgr.hide(m_wbfsLblDialog);
+ m_btnMgr.hide(m_homeBtnBack);
_showHome();
}
}
@@ -200,7 +267,7 @@ void CMenu::_showHome(void)
m_btnMgr.show(m_homeBtnInstall);
m_btnMgr.show(m_homeBtnAbout);
m_btnMgr.show(m_homeBtnExitTo);
- m_btnMgr.show(m_homeBtnSource);
+ m_btnMgr.show(m_homeBtnFTP);
m_btnMgr.show(m_homeLblBattery);
@@ -237,7 +304,7 @@ void CMenu::_hideHome(bool instant)
m_btnMgr.hide(m_homeBtnInstall, instant);
m_btnMgr.hide(m_homeBtnAbout, instant);
m_btnMgr.hide(m_homeBtnExitTo, instant);
- m_btnMgr.hide(m_homeBtnSource, instant);
+ m_btnMgr.hide(m_homeBtnFTP, instant);
m_btnMgr.hide(m_homeLblBattery, instant);
@@ -269,6 +336,7 @@ void CMenu::_initHomeAndExitToMenu()
m_homeBg = _texture("HOME/BG", "texture", theme.bg, false);
m_homeLblTitle = _addTitle("HOME/TITLE", theme.titleFont, L"", 20, 30, 600, 60, theme.titleFontColor, FTGX_JUSTIFY_CENTER | FTGX_ALIGN_MIDDLE);
+ m_homeBtnBack = _addButton("HOME/BACK_BTN", theme.btnFont, L"", 420, 400, 200, 56, theme.btnFontColor);
_setHideAnim(m_homeLblTitle, "HOME/TITLE", 0, 0, -2.f, 0.f);
@@ -280,10 +348,12 @@ void CMenu::_initHomeAndExitToMenu()
m_homeBtnInstall = _addButton("HOME/INSTALL", theme.btnFont, L"", 330, 100, 250, 56, theme.btnFontColor);
m_homeBtnAbout = _addButton("HOME/ABOUT", theme.btnFont, L"", 330, 180, 250, 56, theme.btnFontColor);
m_homeBtnExitTo = _addButton("HOME/EXIT_TO", theme.btnFont, L"", 330, 260, 250, 56, theme.btnFontColor);
- m_homeBtnSource = _addButton("HOME/SOURCE", theme.btnFont, L"", 330, 340, 250, 56, theme.btnFontColor);
+ m_homeBtnFTP = _addButton("HOME/FTP", theme.btnFont, L"", 330, 340, 250, 56, theme.btnFontColor);
m_homeLblBattery = _addLabel("HOME/BATTERY", theme.btnFont, L"", 0, 420, 640, 56, theme.btnFontColor, FTGX_JUSTIFY_CENTER | FTGX_ALIGN_MIDDLE, theme.btnTexC);
+ _setHideAnim(m_homeBtnBack, "HOME/BACK_BTN", 0, 0, 1.f, -1.f);
+
_setHideAnim(m_homeBtnSettings, "HOME/SETTINGS", 0, 0, -2.f, 0.f);
_setHideAnim(m_homeBtnReloadCache, "HOME/RELOAD_CACHE", 0, 0, -2.f, 0.f);
_setHideAnim(m_homeBtnUpdate, "HOME/UPDATE", 0, 0, -2.f, 0.f);
@@ -292,7 +362,7 @@ void CMenu::_initHomeAndExitToMenu()
_setHideAnim(m_homeBtnInstall, "HOME/INSTALL", 0, 0, -2.f, 0.f);
_setHideAnim(m_homeBtnAbout, "HOME/ABOUT", 0, 0, -2.f, 0.f);
_setHideAnim(m_homeBtnExitTo, "HOME/EXIT_TO", 0, 0, -2.f, 0.f);
- _setHideAnim(m_homeBtnSource, "HOME/SOURCE", 0, 0, -2.f, 0.f);
+ _setHideAnim(m_homeBtnFTP, "HOME/FTP", 0, 0, -2.f, 0.f);
_setHideAnim(m_homeLblBattery, "HOME/BATTERY", 0, 0, -2.f, 0.f);
@@ -322,6 +392,7 @@ void CMenu::_initHomeAndExitToMenu()
void CMenu::_textHome(void)
{
m_btnMgr.setText(m_homeLblTitle, VERSION_STRING);
+ m_btnMgr.setText(m_homeBtnBack, _t("cfg10", L"Back"));
m_btnMgr.setText(m_homeBtnSettings, _t("home1", L"Settings"));
m_btnMgr.setText(m_homeBtnReloadCache, _t("home2", L"Reload Cache"));
m_btnMgr.setText(m_homeBtnUpdate, _t("home3", L"Update"));
@@ -330,7 +401,7 @@ void CMenu::_textHome(void)
m_btnMgr.setText(m_homeBtnInstall, _t("home7", L"Install Game"));
m_btnMgr.setText(m_homeBtnAbout, _t("home4", L"Credits"));
m_btnMgr.setText(m_homeBtnExitTo, _t("home5", L"Exit To"));
- m_btnMgr.setText(m_homeBtnSource, _t("home9", L"Source Menu"));
+ m_btnMgr.setText(m_homeBtnFTP, _t("home10", L"FTP Server"));
}
void CMenu::_textExitTo(void)
diff --git a/source/network/FTP_Dir.cpp b/source/network/FTP_Dir.cpp
new file mode 100644
index 00000000..0f4ac9c7
--- /dev/null
+++ b/source/network/FTP_Dir.cpp
@@ -0,0 +1,260 @@
+/****************************************************************************
+ * Copyright (C) 2013 FIX94
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ ****************************************************************************/
+#include
+#include
+#include "FTP_Dir.hpp"
+#include "gecko/gecko.hpp"
+#include "devicemounter/DeviceHandler.hpp"
+#include "fileOps/fileOps.h"
+#include "gui/fmt.h"
+#include "loader/wbfs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+char main_path[MAXPATHLEN];
+char real_path[MAXPATHLEN];
+u8 cur_part = 0;
+
+void ftp_init(void)
+{
+ memset(main_path, 0, MAXPATHLEN);
+ main_path[0] = '/';
+ memset(real_path, 0, MAXPATHLEN);
+ cur_part = 0;
+}
+
+const char *ftp_getpath(void)
+{
+ return main_path;
+}
+
+static bool check_device(void)
+{
+ u8 i;
+ cur_part = 0;
+ for(i = 0; i < MAXDEVICES; ++i)
+ {
+ if(strncmp(DeviceName[i], main_path+1, strlen(DeviceName[i])) == 0)
+ {
+ cur_part = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool change_real_dir(void)
+{
+ if(main_path[1] == '\0')
+ return true;
+ if(check_device() == true)
+ {
+ strncpy(real_path, fmt("%s:/%s", DeviceName[cur_part], strchr((main_path+1), '/')+1), MAXPATHLEN);
+ return true;
+ }
+ errno = ENOTDIR;
+ return false;
+}
+
+DIR *ftp_diropen()
+{
+ DIR *cur_dir = NULL;
+ if(main_path[1] != '\0')
+ cur_dir = opendir(real_path);
+ if(cur_dir == NULL)
+ errno = EIO;
+ return cur_dir;
+}
+
+int ftp_dirnext(DIR *dir, char *name)
+{
+ int ret = -1;
+ if(main_path[1] == '\0')
+ {
+ while(cur_part < MAXDEVICES)
+ {
+ if(DeviceHandle.IsInserted(cur_part) && DeviceHandle.GetFSType(cur_part) != PART_FS_WBFS)
+ {
+ strncpy(name, DeviceName[cur_part], 8);
+ cur_part++;
+ ret = 0;
+ break;
+ }
+ cur_part++;
+ }
+ }
+ else if(dir != NULL)
+ {
+ while(1)
+ {
+ struct dirent *pent = readdir(dir);
+ if(pent == NULL)
+ break;
+ else if(pent->d_name[0] == '.')
+ continue;
+ strncpy(name, pent->d_name, MAXPATHLEN);
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
+}
+
+void ftp_dirclose(DIR *dir)
+{
+ if(dir != NULL)
+ closedir(dir);
+ dir = NULL;
+}
+
+int ftp_changedir(char *path)
+{
+ int ret = -1;
+ /* main changing */
+ if(strcmp(path, "..") == 0)
+ {
+ /* not in root */
+ if(strlen(main_path) > 1)
+ {
+ /* remove new / */
+ if(strchr(main_path, '/') != NULL)
+ {
+ *(strrchr(main_path, '/')) = '\0';
+ /* remove the path before the new / */
+ if(strchr(main_path, '/') != NULL)
+ *(strrchr(main_path, '/')+1) = '\0';
+ }
+ }
+ }
+ else /* enter a new path, do some checks */
+ {
+ if(path[0] == '/') /* full path */
+ strcpy(main_path, path);
+ else
+ strcat(main_path, path);
+ }
+ char *last = (main_path+strlen(main_path)-1);
+ if(*last != '/')
+ {
+ *(last+1) = '/';
+ *(last+2) = '\0';
+ }
+ if(change_real_dir() == true)
+ ret = 0;
+ return ret;
+}
+
+int ftp_makedir(char *path)
+{
+ int ret = -1;
+ if(main_path[1] != '\0')
+ {
+ char *new_dir = fmt("%s%s", real_path, path);
+ fsop_MakeFolder(new_dir);
+ if(fsop_FolderExist(new_dir) == true)
+ ret = 0;
+ }
+ if(ret < 0)
+ errno = EACCES;
+ return ret;
+}
+
+FILE *ftp_fopen(char *path, char *type)
+{
+ FILE *fp = NULL;
+ if(main_path[1] != '\0')
+ {
+ char *new_file = fmt("%s%s", real_path, path);
+ fp = fopen(new_file, type);
+ if(fp != NULL)
+ gprintf("Opening file %s in type %s\n", new_file, type);
+ }
+ if(fp == NULL)
+ errno = (strcmp(type, "rb") == 0 ? EACCES : EROFS);
+ return fp;
+}
+
+void ftp_fclose(FILE *fp)
+{
+ if(fp != NULL)
+ {
+ fclose(fp);
+ gprintf("Closing file\n");
+ }
+ fp = NULL;
+}
+
+int ftp_stat(char *file, struct stat *st)
+{
+ if(file == NULL || st == NULL)
+ return -1;
+ st->st_mtime = 0;
+ st->st_size = 0;
+ st->st_mode = 0;
+ if(main_path[1] == '\0')
+ st->st_mode |= S_IFDIR;
+ else
+ stat(fmt("%s%s", real_path, file), st);
+ return 0;
+}
+
+int ftp_rename(char *path, char *new_name)
+{
+ int ret = -1;
+ char *old_path = fmt("%s%s", real_path, path);
+ char *new_path = fmt("%s%s", real_path, new_name);
+ if(fsop_FileExist(old_path))
+ {
+ gprintf("Renaming File %s to %s\n", old_path, new_path);
+ ret = rename(old_path, new_path);
+ }
+ else if(fsop_FolderExist(old_path))
+ {
+ gprintf("Renaming Folder %s to %s\n", old_path, new_path);
+ ret = rename(old_path, new_path);
+ }
+ if(ret < 0)
+ errno = EIO;
+ return ret;
+}
+
+int ftp_delete(char *path)
+{
+ int ret = -1;
+ char *old_path = fmt("%s%s", real_path, path);
+ if(fsop_FileExist(old_path))
+ {
+ gprintf("%s is a file, deleting it\n", old_path);
+ fsop_deleteFile(old_path);
+ ret = 0;
+ }
+ else if(fsop_FolderExist(old_path))
+ {
+ gprintf("%s is a folder, deleting it\n", old_path);
+ fsop_deleteFolder(old_path);
+ ret = 0;
+ }
+ if(ret < 0)
+ errno = ENOENT;
+ return ret;
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
diff --git a/source/network/FTP_Dir.hpp b/source/network/FTP_Dir.hpp
new file mode 100644
index 00000000..de4e100f
--- /dev/null
+++ b/source/network/FTP_Dir.hpp
@@ -0,0 +1,50 @@
+/****************************************************************************
+ * Copyright (C) 2013 FIX94
+ *
+ * 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 .
+ ****************************************************************************/
+#ifndef _FTP_DIR_H_
+#define _FTP_DIR_H_
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void ftp_init(void);
+const char *ftp_getpath(void);
+
+DIR *ftp_diropen();
+void ftp_dirclose(DIR *dir);
+int ftp_dirnext(DIR *dir, char *name);
+int ftp_changedir(char *path);
+int ftp_makedir(char *path);
+
+FILE *ftp_fopen(char *path, char *type);
+void ftp_fclose(FILE *fp);
+
+int ftp_stat(char *file, struct stat *st);
+int ftp_rename(char *path, char *new_name);
+int ftp_delete(char *path);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/source/network/ftp.c b/source/network/ftp.c
new file mode 100644
index 00000000..5f4f4f13
--- /dev/null
+++ b/source/network/ftp.c
@@ -0,0 +1,783 @@
+// Copyright 2010 Joseph Jordan
+// This code is licensed to you under the terms of the GNU GPL, version 2;
+// see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "gui/fmt.h"
+#include "gecko/gecko.hpp"
+#include "defines.h"
+#include "svnrev.h"
+#include "FTP_Dir.hpp"
+#include "ftp.h"
+#include "net.h"
+
+#define FTP_BUFFER_SIZE 1024
+#define MAX_CLIENTS 2
+
+static const u16 SRC_PORT = 20;
+static const s32 EQUIT = 696969;
+static const char *CRLF = "\r\n";
+static const u32 CRLF_LENGTH = 2;
+
+static u8 num_clients = 0;
+static u16 passive_port = 1024;
+static char *password = NULL;
+
+typedef s32 (*data_connection_callback)(s32 data_socket, void *arg);
+
+struct client_struct {
+ s32 socket;
+ char representation_type;
+ s32 passive_socket;
+ s32 data_socket;
+ char cwd[MAXPATHLEN];
+ char pending_rename[MAXPATHLEN];
+ off_t restart_marker;
+ struct sockaddr_in address;
+ bool authenticated;
+ char buf[FTP_BUFFER_SIZE];
+ s32 offset;
+ bool data_connection_connected;
+ data_connection_callback data_callback;
+ void *data_connection_callback_arg;
+ void (*data_connection_cleanup)(void *arg);
+ u64 data_connection_timer;
+};
+
+typedef struct client_struct client_t;
+
+static client_t *clients[MAX_CLIENTS] = { NULL };
+
+void set_ftp_password(char *new_password) {
+ if (password) free(password);
+ if (new_password) {
+ password = malloc(strlen(new_password) + 1);
+ if (!password) return;//die("Unable to allocate memory for password", errno);
+ strcpy((char *)password, new_password);
+ } else {
+ password = NULL;
+ }
+}
+
+static bool compare_ftp_password(char *password_attempt) {
+ return !password || !strcmp((char *)password, password_attempt);
+}
+
+/*
+ TODO: support multi-line reply
+*/
+static s32 write_reply(client_t *client, u16 code, char *msg) {
+ u32 msglen = 4 + strlen(msg) + CRLF_LENGTH;
+ char msgbuf[msglen + 1];
+ if (msgbuf == NULL) return -ENOMEM;
+ strncpy(msgbuf, fmt("%u %s\r\n", code, msg), msglen + 1);
+ gprintf("Wrote reply: %s", msgbuf);
+ return send_exact(client->socket, msgbuf, msglen);
+}
+
+static void close_passive_socket(client_t *client) {
+ if (client->passive_socket >= 0) {
+ net_close_blocking(client->passive_socket);
+ client->passive_socket = -1;
+ }
+}
+
+/*
+ result must be able to hold up to maxsplit+1 null-terminated strings of length strlen(s)
+ returns the number of strings stored in the result array (up to maxsplit+1)
+*/
+static u32 split(char *s, char sep, u32 maxsplit, char *result[]) {
+ u32 num_results = 0;
+ u32 result_pos = 0;
+ u32 trim_pos = 0;
+ bool in_word = false;
+ for (; *s; s++) {
+ if (*s == sep) {
+ if (num_results <= maxsplit) {
+ in_word = false;
+ continue;
+ } else if (!trim_pos) {
+ trim_pos = result_pos;
+ }
+ } else if (trim_pos) {
+ trim_pos = 0;
+ }
+ if (!in_word) {
+ in_word = true;
+ if (num_results <= maxsplit) {
+ num_results++;
+ result_pos = 0;
+ }
+ }
+ result[num_results - 1][result_pos++] = *s;
+ result[num_results - 1][result_pos] = '\0';
+ }
+ if (trim_pos) {
+ result[num_results - 1][trim_pos] = '\0';
+ }
+ u32 i = num_results;
+ for (i = num_results; i <= maxsplit; i++) {
+ result[i][0] = '\0';
+ }
+ return num_results;
+}
+
+static s32 ftp_USER(client_t *client, char *username __attribute__((unused))) {
+ return write_reply(client, 331, "User name okay, need password.");
+}
+
+static s32 ftp_PASS(client_t *client, char *password_attempt) {
+ if (compare_ftp_password(password_attempt)) {
+ client->authenticated = true;
+ return write_reply(client, 230, "User logged in, proceed.");
+ } else {
+ return write_reply(client, 530, "Login incorrect.");
+ }
+}
+
+static s32 ftp_REIN(client_t *client, char *rest __attribute__((unused))) {
+ close_passive_socket(client);
+ strcpy(client->cwd, "/");
+ client->representation_type = 'A';
+ client->authenticated = false;
+ return write_reply(client, 220, "Service ready for new user.");
+}
+
+static s32 ftp_QUIT(client_t *client, char *rest __attribute__((unused))) {
+ // TODO: dont quit if xfer in progress
+ s32 result = write_reply(client, 221, "Service closing control connection.");
+ /* reset paths for some strange clients */
+ ftp_init();
+ return result < 0 ? result : -EQUIT;
+}
+
+static s32 ftp_SYST(client_t *client, char *rest __attribute__((unused))) {
+ return write_reply(client, 215, "UNIX Type: L8 Version: wiiflow-ftpii");
+}
+
+static s32 ftp_TYPE(client_t *client, char *rest) {
+ char representation_type[FTP_BUFFER_SIZE], param[FTP_BUFFER_SIZE];
+ char *args[] = { representation_type, param };
+ u32 num_args = split(rest, ' ', 1, args);
+ if (num_args == 0) {
+ return write_reply(client, 501, "Syntax error in parameters.");
+ } else if ((!strcasecmp("A", representation_type) && (!*param || !strcasecmp("N", param))) ||
+ (!strcasecmp("I", representation_type) && num_args == 1)) {
+ client->representation_type = *representation_type;
+ } else {
+ return write_reply(client, 501, "Syntax error in parameters.");
+ }
+ char msg[15];
+ strncpy(msg, fmt("Type set to %s.", representation_type), 15);
+ return write_reply(client, 200, msg);
+}
+
+static s32 ftp_MODE(client_t *client, char *rest) {
+ if (!strcasecmp("S", rest)) {
+ return write_reply(client, 200, "Mode S ok.");
+ } else {
+ return write_reply(client, 501, "Syntax error in parameters.");
+ }
+}
+
+static s32 ftp_PWD(client_t *client, char *rest __attribute__((unused))) {
+ char msg[MAXPATHLEN + 24];
+ // TODO: escape double-quotes
+ strncpy(msg, fmt("\"%s\" is current directory.", ftp_getpath()), MAXPATHLEN + 24);
+ return write_reply(client, 257, msg);
+}
+
+static s32 ftp_CWD(client_t *client, char *path) {
+ s32 result;
+ if (ftp_changedir(path) == 0) {
+ result = write_reply(client, 250, "CWD command successful.");
+ } else {
+ result = write_reply(client, 550, strerror(errno));
+ }
+ return result;
+}
+
+static s32 ftp_CDUP(client_t *client, char *rest __attribute__((unused))) {
+ s32 result;
+ if (ftp_changedir("..") == 0) {
+ result = write_reply(client, 250, "CDUP command successful.");
+ } else {
+ result = write_reply(client, 550, strerror(errno));
+ }
+ return result;
+}
+
+static s32 ftp_DELE(client_t *client, char *path) {
+ if (ftp_delete(path) == 0) {
+ return write_reply(client, 250, "File or directory removed.");
+ } else {
+ return write_reply(client, 550, strerror(errno));
+ }
+}
+
+static s32 ftp_MKD(client_t *client, char *path) {
+ if (!*path) {
+ return write_reply(client, 501, "Syntax error in parameters.");
+ }
+ if (ftp_makedir(path) == 0) {
+ char msg[MAXPATHLEN + 21];
+ char abspath[MAXPATHLEN];
+ strcpy(abspath, client->cwd);
+ //vrt_chdir(abspath, path); // TODO: error checking
+ // TODO: escape double-quotes
+ strncpy(msg, fmt("\"%s\" directory created.", abspath), MAXPATHLEN + 21);
+ return write_reply(client, 257, msg);
+ } else {
+ return write_reply(client, 550, strerror(errno));
+ }
+}
+
+static s32 ftp_RNFR(client_t *client, char *path) {
+ strcpy(client->pending_rename, path);
+ return write_reply(client, 350, "Ready for RNTO.");
+}
+
+static s32 ftp_RNTO(client_t *client, char *path) {
+ if (!*client->pending_rename) {
+ return write_reply(client, 503, "RNFR required first.");
+ }
+ s32 result;
+ if (ftp_rename(client->pending_rename, path) == 0) {
+ result = write_reply(client, 250, "Rename successful.");
+ } else {
+ result = write_reply(client, 550, strerror(errno));
+ }
+ *client->pending_rename = '\0';
+ return result;
+}
+
+static s32 ftp_SIZE(client_t *client, char *path) {
+ struct stat st;
+ if (ftp_stat(path, &st) == 0) {
+ char size_buf[12];
+ strncpy(size_buf, fmt("%llu", st.st_size), 12);
+ return write_reply(client, 213, size_buf);
+ } else {
+ return write_reply(client, 550, strerror(errno));
+ }
+}
+
+static s32 ftp_PASV(client_t *client, char *rest __attribute__((unused))) {
+ close_passive_socket(client);
+ client->passive_socket = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
+ if (client->passive_socket < 0) {
+ return write_reply(client, 520, "Unable to create listening socket.");
+ }
+ set_blocking(client->passive_socket, false);
+ struct sockaddr_in bindAddress;
+ memset(&bindAddress, 0, sizeof(bindAddress));
+ bindAddress.sin_family = AF_INET;
+ if (passive_port < 1024) passive_port = 1024;
+ bindAddress.sin_port = htons(passive_port++);
+ bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
+ s32 result;
+ if ((result = net_bind(client->passive_socket, (struct sockaddr *)&bindAddress, sizeof(bindAddress))) < 0) {
+ close_passive_socket(client);
+ return write_reply(client, 520, "Unable to bind listening socket.");
+ }
+ if ((result = net_listen(client->passive_socket, 1)) < 0) {
+ close_passive_socket(client);
+ return write_reply(client, 520, "Unable to listen on socket.");
+ }
+ char reply[49];
+ u16 port = bindAddress.sin_port;
+ u32 ip = net_gethostip();
+ struct in_addr addr;
+ addr.s_addr = ip;
+ gprintf("Listening for data connections at %s:%u...\n", inet_ntoa(addr), port);
+ strncpy(reply, fmt("Entering Passive Mode (%u,%u,%u,%u,%u,%u).", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff, (port >> 8) & 0xff, port & 0xff), 49);
+ return write_reply(client, 227, reply);
+}
+
+static s32 ftp_PORT(client_t *client, char *portspec) {
+ u32 h1, h2, h3, h4, p1, p2;
+ if (sscanf(portspec, "%3u,%3u,%3u,%3u,%3u,%3u", &h1, &h2, &h3, &h4, &p1, &p2) < 6) {
+ return write_reply(client, 501, "Syntax error in parameters.");
+ }
+ char addr_str[44];
+ strncpy(addr_str, fmt("%u.%u.%u.%u", h1, h2, h3, h4), 44);
+ struct in_addr sin_addr;
+ if (!inet_aton(addr_str, &sin_addr)) {
+ return write_reply(client, 501, "Syntax error in parameters.");
+ }
+ close_passive_socket(client);
+ u16 port = ((p1 &0xff) << 8) | (p2 & 0xff);
+ client->address.sin_addr = sin_addr;
+ client->address.sin_port = htons(port);
+ gprintf("Set client address to %s:%u\n", addr_str, port);
+ return write_reply(client, 200, "PORT command successful.");
+}
+
+typedef s32 (*data_connection_handler)(client_t *client);
+
+static s32 prepare_data_connection_active(client_t *client) {
+ s32 data_socket = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
+ if (data_socket < 0) return data_socket;
+ set_blocking(data_socket, false);
+ struct sockaddr_in bindAddress;
+ memset(&bindAddress, 0, sizeof(bindAddress));
+ bindAddress.sin_family = AF_INET;
+ bindAddress.sin_port = htons(SRC_PORT);
+ bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
+ s32 result;
+ if ((result = net_bind(data_socket, (struct sockaddr *)&bindAddress, sizeof(bindAddress))) < 0) {
+ net_close(data_socket);
+ return result;
+ }
+
+ client->data_socket = data_socket;
+ gprintf("Attempting to connect to client at %s:%u\n", inet_ntoa(client->address.sin_addr), ntohs(client->address.sin_port));
+ return 0;
+}
+
+static s32 prepare_data_connection_passive(client_t *client) {
+ client->data_socket = client->passive_socket;
+ gprintf("Waiting for data connections...\n");
+ return 0;
+}
+
+static s32 prepare_data_connection(client_t *client, void *callback, void *arg, void *cleanup) {
+ s32 result = write_reply(client, 150, "Transferring data.");
+ if (result >= 0) {
+ data_connection_handler handler = prepare_data_connection_active;
+ if (client->passive_socket >= 0) handler = prepare_data_connection_passive;
+ result = handler(client);
+ if (result < 0) {
+ result = write_reply(client, 520, "Closing data connection, error occurred during transfer.");
+ } else {
+ client->data_connection_connected = false;
+ client->data_callback = callback;
+ client->data_connection_callback_arg = arg;
+ client->data_connection_cleanup = cleanup;
+ client->data_connection_timer = gettime() + secs_to_ticks(30);
+ }
+ }
+ return result;
+}
+
+static s32 send_nlst(s32 data_socket, DIR *dir) {
+ s32 result = 0;
+ char filename[MAXPATHLEN + 2];
+ while (ftp_dirnext(dir, filename) == 0) {
+ size_t end_index = strlen(filename);
+ filename[end_index] = CRLF[0];
+ filename[end_index + 1] = CRLF[1];
+ filename[end_index + 2] = '\0';
+ if ((result = send_exact(data_socket, filename, strlen(filename))) < 0) {
+ break;
+ }
+ }
+ return result < 0 ? result : 0;
+}
+
+static s32 send_list(s32 data_socket, DIR *dir) {
+ s32 result = 0;
+ char filename[MAXPATHLEN];
+ struct stat st;
+ char line[MAXPATHLEN + 56 + CRLF_LENGTH + 1];
+ while (ftp_dirnext(dir, filename) == 0) {
+ char timestamp[13];
+ ftp_stat(filename, &st);
+ strftime(timestamp, sizeof(timestamp), "%b %d %Y", localtime(&st.st_mtime));
+ strncpy(line, fmt("%crwxr-xr-x 1 0 0 %10llu %s %s\r\n", (st.st_mode & S_IFDIR) ? 'd' : '-', st.st_size, timestamp, filename), MAXPATHLEN + 56 + CRLF_LENGTH + 1);
+ if ((result = send_exact(data_socket, line, strlen(line))) < 0) {
+ break;
+ }
+ }
+ return result < 0 ? result : 0;
+}
+
+static s32 ftp_NLST(client_t *client, char *path) {
+ if (!*path) {
+ path = ".";
+ }
+
+ DIR *dir = ftp_diropen();
+ if (dir == NULL) {
+ return write_reply(client, 550, strerror(errno));
+ }
+
+ s32 result = prepare_data_connection(client, send_nlst, dir, ftp_dirclose);
+ if (result < 0) ftp_dirclose(dir);
+ return result;
+}
+
+static s32 ftp_LIST(client_t *client, char *path) {
+ if (*path == '-') {
+ // handle buggy clients that use "LIST -aL" or similar, at the expense of breaking paths that begin with '-'
+ char flags[FTP_BUFFER_SIZE];
+ char rest[FTP_BUFFER_SIZE];
+ char *args[] = { flags, rest };
+ split(path, ' ', 1, args);
+ path = rest;
+ }
+ if (!*path) {
+ path = ".";
+ }
+ //s32 result = -1;
+ DIR *dir = ftp_diropen();
+ /*if (dir == NULL) {
+ return write_reply(client, 550, strerror(errno));
+ }*/
+ s32 result = prepare_data_connection(client, send_list, dir, ftp_dirclose);
+ if (result < 0) ftp_dirclose(dir);
+ return result;
+}
+
+static s32 ftp_RETR(client_t *client, char *path) {
+ FILE *f = ftp_fopen(path, "rb");
+ if (!f) {
+ return write_reply(client, 550, strerror(errno));
+ }
+
+ int fd = fileno(f);
+ if (client->restart_marker && lseek(fd, client->restart_marker, SEEK_SET) != client->restart_marker) {
+ s32 lseek_error = errno;
+ ftp_fclose(f);
+ client->restart_marker = 0;
+ return write_reply(client, 550, strerror(lseek_error));
+ }
+ client->restart_marker = 0;
+
+ s32 result = prepare_data_connection(client, send_from_file, f, ftp_fclose);
+ if (result < 0) ftp_fclose(f);
+ return result;
+}
+
+static s32 stor_or_append(client_t *client, FILE *f) {
+ if (!f) {
+ return write_reply(client, 550, strerror(errno));
+ }
+ s32 result = prepare_data_connection(client, recv_to_file, f, ftp_fclose);
+ if (result < 0) ftp_fclose(f);
+ return result;
+}
+
+static s32 ftp_STOR(client_t *client, char *path __attribute__((unused))) {
+ FILE *f = ftp_fopen(path, "wb");
+ int fd;
+ if (f) fd = fileno(f);
+ if (f && client->restart_marker && lseek(fd, client->restart_marker, SEEK_SET) != client->restart_marker) {
+ s32 lseek_error = errno;
+ ftp_fclose(f);
+ client->restart_marker = 0;
+ return write_reply(client, 550, strerror(lseek_error));
+ }
+ client->restart_marker = 0;
+
+ return stor_or_append(client, f);
+}
+
+static s32 ftp_APPE(client_t *client, char *path __attribute__((unused))) {
+ return stor_or_append(client, ftp_fopen(path, "ab"));
+}
+
+static s32 ftp_REST(client_t *client, char *offset_str) {
+ off_t offset;
+ if (sscanf(offset_str, "%lli", &offset) < 1 || offset < 0) {
+ return write_reply(client, 501, "Syntax error in parameters.");
+ }
+ client->restart_marker = offset;
+ char msg[FTP_BUFFER_SIZE];
+ strncpy(msg, fmt("Restart position accepted (%lli).", offset), FTP_BUFFER_SIZE);
+ return write_reply(client, 350, msg);
+}
+
+typedef s32 (*ftp_command_handler)(client_t *client, char *args);
+
+static s32 dispatch_to_handler(client_t *client, char *cmd_line, const char **commands, const ftp_command_handler *handlers) {
+ char cmd[FTP_BUFFER_SIZE], rest[FTP_BUFFER_SIZE];
+ char *args[] = { cmd, rest };
+ split(cmd_line, ' ', 1, args);
+ s32 i;
+ for (i = 0; commands[i]; i++) {
+ if (!strcasecmp(commands[i], cmd)) break;
+ }
+ return handlers[i](client, rest);
+}
+
+static s32 ftp_NOOP(client_t *client, char *rest __attribute__((unused))) {
+ return write_reply(client, 200, "NOOP command successful.");
+}
+
+static s32 ftp_SUPERFLUOUS(client_t *client, char *rest __attribute__((unused))) {
+ return write_reply(client, 202, "Command not implemented, superfluous at this site.");
+}
+
+static s32 ftp_NEEDAUTH(client_t *client, char *rest __attribute__((unused))) {
+ return write_reply(client, 530, "Please login with USER and PASS.");
+}
+
+static s32 ftp_UNKNOWN(client_t *client, char *rest __attribute__((unused))) {
+ return write_reply(client, 502, "Command not implemented.");
+}
+
+static const char *unauthenticated_commands[] = { "USER", "PASS", "QUIT", "REIN", "NOOP", NULL };
+static const ftp_command_handler unauthenticated_handlers[] = { ftp_USER, ftp_PASS, ftp_QUIT, ftp_REIN, ftp_NOOP, ftp_NEEDAUTH };
+
+static const char *authenticated_commands[] = {
+ "USER", "PASS", "LIST", "PWD", "CWD", "CDUP",
+ "SIZE", "PASV", "PORT", "TYPE", "SYST", "MODE",
+ "RETR", "STOR", "APPE", "REST", "DELE", "MKD",
+ "RMD", "RNFR", "RNTO", "NLST", "QUIT", "REIN",
+ "NOOP", "ALLO", NULL
+};
+static const ftp_command_handler authenticated_handlers[] = {
+ ftp_USER, ftp_PASS, ftp_LIST, ftp_PWD, ftp_CWD, ftp_CDUP,
+ ftp_SIZE, ftp_PASV, ftp_PORT, ftp_TYPE, ftp_SYST, ftp_MODE,
+ ftp_RETR, ftp_STOR, ftp_APPE, ftp_REST, ftp_DELE, ftp_MKD,
+ ftp_DELE, ftp_RNFR, ftp_RNTO, ftp_NLST, ftp_QUIT, ftp_REIN,
+ ftp_NOOP, ftp_SUPERFLUOUS, ftp_UNKNOWN
+};
+
+/*
+ returns negative to signal an error that requires closing the connection
+*/
+static s32 process_command(client_t *client, char *cmd_line) {
+ if (strlen(cmd_line) == 0) {
+ return 0;
+ }
+
+ gprintf("Got command: %s\n", cmd_line);
+
+ const char **commands = unauthenticated_commands;
+ const ftp_command_handler *handlers = unauthenticated_handlers;
+
+ if (client->authenticated) {
+ commands = authenticated_commands;
+ handlers = authenticated_handlers;
+ }
+
+ return dispatch_to_handler(client, cmd_line, commands, handlers);
+}
+
+static void cleanup_data_resources(client_t *client) {
+ if (client->data_socket >= 0 && client->data_socket != client->passive_socket) {
+ net_close_blocking(client->data_socket);
+ }
+ client->data_socket = -1;
+ client->data_connection_connected = false;
+ client->data_callback = NULL;
+ if (client->data_connection_cleanup) {
+ client->data_connection_cleanup(client->data_connection_callback_arg);
+ }
+ client->data_connection_callback_arg = NULL;
+ client->data_connection_cleanup = NULL;
+ client->data_connection_timer = 0;
+}
+
+static void cleanup_client(client_t *client) {
+ net_close_blocking(client->socket);
+ cleanup_data_resources(client);
+ close_passive_socket(client);
+ int client_index;
+ for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
+ if (clients[client_index] == client) {
+ clients[client_index] = NULL;
+ break;
+ }
+ }
+ free(client);
+ num_clients--;
+ if(num_clients == 0)
+ ftp_init(); /* reinit for new clients */
+ gprintf("Client disconnected.\n");
+}
+
+void cleanup_ftp() {
+ int client_index;
+ for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
+ client_t *client = clients[client_index];
+ if (client) {
+ write_reply(client, 421, "Service not available, closing control connection.");
+ cleanup_client(client);
+ }
+ }
+}
+
+static bool process_accept_events(s32 server) {
+ s32 peer;
+ struct sockaddr_in client_address;
+ socklen_t addrlen = sizeof(client_address);
+ while ((peer = net_accept(server, (struct sockaddr *)&client_address, &addrlen)) != -EAGAIN) {
+ if (peer < 0) {
+ gprintf("Error accepting connection: [%i] %s\n", -peer, strerror(-peer));
+ return false;
+ }
+
+ gprintf("Accepted connection from %s!\n", inet_ntoa(client_address.sin_addr));
+
+ if (num_clients == MAX_CLIENTS) {
+ gprintf("Maximum of %u clients reached, not accepting client.\n", MAX_CLIENTS);
+ net_close(peer);
+ return true;
+ }
+
+ client_t *client = malloc(sizeof(client_t));
+ if (!client) {
+ gprintf("Could not allocate memory for client state, not accepting client.\n");
+ net_close(peer);
+ return true;
+ }
+ client->socket = peer;
+ client->representation_type = 'A';
+ client->passive_socket = -1;
+ client->data_socket = -1;
+ strcpy(client->cwd, "/");
+ *client->pending_rename = '\0';
+ client->restart_marker = 0;
+ client->authenticated = false;
+ client->offset = 0;
+ client->data_connection_connected = false;
+ client->data_callback = NULL;
+ client->data_connection_callback_arg = NULL;
+ client->data_connection_cleanup = NULL;
+ client->data_connection_timer = 0;
+ memcpy(&client->address, &client_address, sizeof(client_address));
+ int client_index;
+ char *welcome = fmt("Welcome to %s (%s-r%s)! This is the ftpii server core.", APP_NAME, APP_VERSION, SVN_REV);
+ if (write_reply(client, 220, welcome) < 0) {
+ gprintf("Error writing greeting.\n");
+ net_close_blocking(peer);
+ free(client);
+ } else {
+ for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
+ if (!clients[client_index]) {
+ clients[client_index] = client;
+ break;
+ }
+ }
+ num_clients++;
+ }
+ }
+ return true;
+}
+
+static void process_data_events(client_t *client) {
+ s32 result;
+ if (!client->data_connection_connected) {
+ if (client->passive_socket >= 0) {
+ struct sockaddr_in data_peer_address;
+ socklen_t addrlen = sizeof(data_peer_address);
+ result = net_accept(client->passive_socket, (struct sockaddr *)&data_peer_address ,&addrlen);
+ if (result >= 0) {
+ client->data_socket = result;
+ client->data_connection_connected = true;
+ }
+ } else {
+ if ((result = net_connect(client->data_socket, (struct sockaddr *)&client->address, sizeof(client->address))) < 0) {
+ if (result == -EINPROGRESS || result == -EALREADY) result = -EAGAIN;
+ if (result != -EAGAIN && result != -EISCONN) gprintf("Unable to connect to client: [%i] %s\n", -result, strerror(-result));
+ }
+ if (result >= 0 || result == -EISCONN) {
+ client->data_connection_connected = true;
+ }
+ }
+ if (client->data_connection_connected) {
+ result = 1;
+ gprintf("Connected to client! Transferring data...\n");
+ } else if (gettime() > client->data_connection_timer) {
+ result = -1;
+ gprintf("Timed out waiting for data connection.\n");
+ }
+ } else {
+ result = client->data_callback(client->data_socket, client->data_connection_callback_arg);
+ }
+
+ if (result <= 0 && result != -EAGAIN) {
+ cleanup_data_resources(client);
+ if (result < 0) {
+ result = write_reply(client, 520, "Closing data connection, error occurred during transfer.");
+ } else {
+ result = write_reply(client, 226, "Closing data connection, transfer successful.");
+ }
+ if (result < 0) {
+ cleanup_client(client);
+ }
+ }
+}
+
+static void process_control_events(client_t *client) {
+ s32 bytes_read;
+ while (client->offset < (FTP_BUFFER_SIZE - 1)) {
+ if (client->data_callback) {
+ return;
+ }
+ char *offset_buf = client->buf + client->offset;
+ if ((bytes_read = net_read(client->socket, offset_buf, FTP_BUFFER_SIZE - 1 - client->offset)) < 0) {
+ if (bytes_read != -EAGAIN) {
+ gprintf("Read error %i occurred, closing client.\n", bytes_read);
+ goto recv_loop_end;
+ }
+ return;
+ } else if (bytes_read == 0) {
+ goto recv_loop_end; // EOF from client
+ }
+ client->offset += bytes_read;
+ client->buf[client->offset] = '\0';
+
+ if (strchr(offset_buf, '\0') != (client->buf + client->offset)) {
+ gprintf("Received a null byte from client, closing connection ;-)\n"); // i have decided this isn't allowed =P
+ goto recv_loop_end;
+ }
+
+ char *next;
+ char *end;
+ for (next = client->buf; (end = strstr(next, CRLF)) && !client->data_callback; next = end + CRLF_LENGTH) {
+ *end = '\0';
+ if (strchr(next, '\n')) {
+ gprintf("Received a line-feed from client without preceding carriage return, closing connection ;-)\n"); // i have decided this isn't allowed =P
+ goto recv_loop_end;
+ }
+
+ if (*next) {
+ s32 result;
+ if ((result = process_command(client, next)) < 0) {
+ if (result != -EQUIT) {
+ gprintf("Closing connection due to error while processing command: %s\n", next);
+ }
+ goto recv_loop_end;
+ }
+ }
+
+ }
+
+ if (next != client->buf) { // some lines were processed
+ client->offset = strlen(next);
+ char tmp_buf[client->offset];
+ memcpy(tmp_buf, next, client->offset);
+ memcpy(client->buf, tmp_buf, client->offset);
+ }
+ }
+ gprintf("Received line longer than %u bytes, closing client.\n", FTP_BUFFER_SIZE - 1);
+
+ recv_loop_end:
+ cleanup_client(client);
+}
+
+bool process_ftp_events(s32 server) {
+ bool network_down = !process_accept_events(server);
+ int client_index;
+ for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
+ client_t *client = clients[client_index];
+ if (client) {
+ if (client->data_callback) {
+ process_data_events(client);
+ } else {
+ process_control_events(client);
+ }
+ }
+ }
+ return network_down;
+}
diff --git a/source/network/ftp.h b/source/network/ftp.h
new file mode 100644
index 00000000..061b2848
--- /dev/null
+++ b/source/network/ftp.h
@@ -0,0 +1,20 @@
+// Copyright 2010 Joseph Jordan
+// This code is licensed to you under the terms of the GNU GPL, version 2;
+// see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+#ifndef _FTP_H_
+#define _FTP_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void accept_ftp_client(s32 server);
+void set_ftp_password(char *new_password);
+bool process_ftp_events(s32 server);
+void cleanup_ftp();
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _FTP_H_ */
diff --git a/source/network/net.c b/source/network/net.c
new file mode 100644
index 00000000..9efdd7c8
--- /dev/null
+++ b/source/network/net.c
@@ -0,0 +1,130 @@
+// Copyright 2010 Joseph Jordan
+// This code is licensed to you under the terms of the GNU GPL, version 2;
+// see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "net.h"
+#include "FTP_Dir.hpp"
+#include "loader/sys.h"
+#include "gecko/gecko.hpp"
+
+#define MAX_NET_BUFFER_SIZE 32768
+#define MIN_NET_BUFFER_SIZE 4096
+#define FREAD_BUFFER_SIZE 32768
+
+static u32 NET_BUFFER_SIZE = MAX_NET_BUFFER_SIZE;
+
+s32 set_blocking(s32 s, bool blocking) {
+ s32 flags;
+ flags = net_fcntl(s, F_GETFL, 0);
+ if (flags >= 0) flags = net_fcntl(s, F_SETFL, blocking ? (flags&~4) : (flags|4));
+ return flags;
+}
+
+s32 net_close_blocking(s32 s) {
+ set_blocking(s, true);
+ return net_close(s);
+}
+
+s32 create_server(u16 port) {
+ s32 server = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
+ if (server < 0) return -1;//die("Error creating socket", -server);
+ set_blocking(server, false);
+
+ struct sockaddr_in bindAddress;
+ memset(&bindAddress, 0, sizeof(bindAddress));
+ bindAddress.sin_family = AF_INET;
+ bindAddress.sin_port = htons(port);
+ bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ s32 ret;
+ if ((ret = net_bind(server, (struct sockaddr *)&bindAddress, sizeof(bindAddress))) < 0) {
+ net_close(server);
+ gprintf("Error binding socket: [%i] %s\n", -ret, strerror(-ret));
+ return ret;
+ }
+ if ((ret = net_listen(server, 3)) < 0) {
+ net_close(server);
+ gprintf("Error listening on socket: [%i] %s\n", -ret, strerror(-ret));
+ return ret;
+ }
+
+ return server;
+}
+
+typedef s32 (*transferrer_type)(s32 s, void *mem, s32 len);
+static s32 transfer_exact(s32 s, char *buf, s32 length, transferrer_type transferrer) {
+ s32 result = 0;
+ s32 remaining = length;
+ s32 bytes_transferred;
+ set_blocking(s, true);
+ while (remaining) {
+ try_again_with_smaller_buffer:
+ bytes_transferred = transferrer(s, buf, MIN(remaining, (int)NET_BUFFER_SIZE));
+ if (bytes_transferred > 0) {
+ remaining -= bytes_transferred;
+ buf += bytes_transferred;
+ } else if (bytes_transferred < 0) {
+ if (bytes_transferred == -EINVAL && NET_BUFFER_SIZE == MAX_NET_BUFFER_SIZE) {
+ NET_BUFFER_SIZE = MIN_NET_BUFFER_SIZE;
+ goto try_again_with_smaller_buffer;
+ }
+ result = bytes_transferred;
+ break;
+ } else {
+ result = -ENODATA;
+ break;
+ }
+ }
+ set_blocking(s, false);
+ return result;
+}
+
+s32 send_exact(s32 s, char *buf, s32 length) {
+ return transfer_exact(s, buf, length, (transferrer_type)net_write);
+}
+
+s32 send_from_file(s32 s, FILE *f) {
+ char buf[FREAD_BUFFER_SIZE];
+ s32 bytes_read;
+ s32 result = 0;
+
+ bytes_read = fread(buf, 1, FREAD_BUFFER_SIZE, f);
+ if (bytes_read > 0) {
+ result = send_exact(s, buf, bytes_read);
+ if (result < 0) goto end;
+ }
+ if (bytes_read < FREAD_BUFFER_SIZE) {
+ result = -!feof(f);
+ goto end;
+ }
+ return -EAGAIN;
+ end:
+ return result;
+}
+
+s32 recv_to_file(s32 s, FILE *f) {
+ char buf[NET_BUFFER_SIZE];
+ s32 bytes_read;
+ while (1) {
+ try_again_with_smaller_buffer:
+ bytes_read = net_read(s, buf, NET_BUFFER_SIZE);
+ if (bytes_read < 0) {
+ if (bytes_read == -EINVAL && NET_BUFFER_SIZE == MAX_NET_BUFFER_SIZE) {
+ NET_BUFFER_SIZE = MIN_NET_BUFFER_SIZE;
+ goto try_again_with_smaller_buffer;
+ }
+ return bytes_read;
+ } else if (bytes_read == 0) {
+ return 0;
+ }
+
+ s32 bytes_written = fwrite(buf, 1, bytes_read, f);
+ if (bytes_written < bytes_read) return -1;
+ }
+}
diff --git a/source/network/net.h b/source/network/net.h
new file mode 100644
index 00000000..191767c4
--- /dev/null
+++ b/source/network/net.h
@@ -0,0 +1,25 @@
+// Copyright 2010 Joseph Jordan
+// This code is licensed to you under the terms of the GNU GPL, version 2;
+// see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+#ifndef _NET_H_
+#define _NET_H_
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void initialise_network();
+s32 set_blocking(s32 s, bool blocking);
+s32 net_close_blocking(s32 s);
+s32 create_server(u16 port);
+s32 send_exact(s32 s, char *buf, s32 length);
+s32 send_from_file(s32 s, FILE *f);
+s32 recv_to_file(s32 s, FILE *f);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _NET_H_ */
diff --git a/wii/wiiflow/Languages/english.ini b/wii/wiiflow/Languages/english.ini
index a4c6229c..1711d49d 100644
--- a/wii/wiiflow/Languages/english.ini
+++ b/wii/wiiflow/Languages/english.ini
@@ -211,6 +211,7 @@ dlmsg24=Extracting...
dlmsg25=Extraction must have failed! Renaming the backup to boot.dol
dlmsg26=Updating cache...
dlmsg27=Not enough memory!
+dlmsg28=Running FTP Server on %s:%u
dlmsg3=Downloading from %s
dlmsg4=Saving %s
dlmsg5=%i/%i files downloaded