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