├── .clang-format ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── pr.yml ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── include ├── IOAbstraction.h ├── fs.h ├── ftpConfig.h ├── ftpServer.h ├── ftpSession.h ├── ioBuffer.h ├── log.h ├── mdns.h ├── platform.h ├── sockAddr.h └── socket.h └── source ├── IOAbstraction.cpp ├── fs.cpp ├── ftpConfig.cpp ├── ftpServer.cpp ├── ftpSession.cpp ├── ioBuffer.cpp ├── log.cpp ├── main.cpp ├── mdns.cpp ├── posix ├── collate.c ├── collate.h ├── collcmp.c └── glob.c ├── sockAddr.cpp ├── socket.cpp └── wiiu ├── logger.c ├── logger.h ├── platform.cpp └── version.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: true 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | #AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterCaseLabel: true 25 | AfterClass: true 26 | AfterControlStatement: true 27 | AfterEnum: true 28 | AfterFunction: true 29 | AfterNamespace: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | # SplitEmptyFunction: true 36 | # SplitEmptyRecord: true 37 | # SplitEmptyNamespace: true 38 | BreakBeforeBinaryOperators: None 39 | BreakBeforeBraces: Custom 40 | BreakBeforeTernaryOperators: true 41 | #BreakConstructorInitializers: AfterColon 42 | BreakConstructorInitializersBeforeComma: false 43 | BreakStringLiterals: true 44 | ColumnLimit: 100 45 | CommentPragmas: '^ IWYU pragma:' 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 47 | ConstructorInitializerIndentWidth: 4 48 | ContinuationIndentWidth: 4 49 | Cpp11BracedListStyle: true 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: false 53 | FixNamespaceComments: false 54 | ForEachMacros: [ foreach ] 55 | IncludeCategories: 56 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 57 | Priority: 2 58 | - Regex: '^(<|"(gtest|isl|json)/)' 59 | Priority: 3 60 | - Regex: '.*' 61 | Priority: 1 62 | IncludeIsMainRegex: '$' 63 | IndentCaseLabels: false 64 | IndentWidth: 4 65 | IndentWrappedFunctionNames: true 66 | KeepEmptyLinesAtTheStartOfBlocks: false 67 | MacroBlockBegin: '' 68 | MacroBlockEnd: '' 69 | MaxEmptyLinesToKeep: 1 70 | NamespaceIndentation: None 71 | PenaltyBreakBeforeFirstCallParameter: 19 72 | PenaltyBreakComment: 300 73 | PenaltyBreakFirstLessLess: 120 74 | PenaltyBreakString: 1000 75 | PenaltyExcessCharacter: 1000000 76 | PenaltyReturnTypeOnItsOwnLine: 60 77 | PointerAlignment: Right 78 | ReflowComments: true 79 | SortIncludes: true 80 | SpaceAfterCStyleCast: false 81 | SpaceAfterTemplateKeyword: true 82 | SpaceBeforeAssignmentOperators: true 83 | SpaceBeforeParens: Always 84 | SpaceInEmptyParentheses: false 85 | SpacesBeforeTrailingComments: 1 86 | SpacesInAngles: false 87 | SpacesInContainerLiterals: true 88 | SpacesInCStyleCastParentheses: false 89 | SpacesInParentheses: false 90 | SpacesInSquareBrackets: false 91 | Standard: Cpp11 92 | TabWidth: 4 93 | UseTab: ForIndentation 94 | ... 95 | 96 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "docker" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI-Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | clang-format: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: clang-format 14 | run: | 15 | docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source ./include 16 | build-binary: 17 | runs-on: ubuntu-22.04 18 | needs: clang-format 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: create version.h 24 | run: | 25 | git_hash=$(git rev-parse --short "$GITHUB_SHA") 26 | cat < ./source/wiiu/version.h 27 | #pragma once 28 | #define VERSION_EXTRA " (nightly-$git_hash)" 29 | EOF 30 | - name: build binary 31 | run: | 32 | docker build . -t builder 33 | docker run --rm -v ${PWD}:/project builder make 34 | - uses: actions/upload-artifact@master 35 | with: 36 | name: binary 37 | path: "*.wps" 38 | deploy-binary: 39 | needs: build-binary 40 | runs-on: ubuntu-22.04 41 | steps: 42 | - name: Get environment variables 43 | id: get_repository_name 44 | run: | 45 | echo REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") >> $GITHUB_ENV 46 | echo DATETIME=$(echo $(date '+%Y%m%d-%H%M%S')) >> $GITHUB_ENV 47 | - uses: actions/download-artifact@master 48 | with: 49 | name: binary 50 | - name: zip artifact 51 | run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.wps 52 | - name: Create Release 53 | uses: "softprops/action-gh-release@v2" 54 | with: 55 | tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} 56 | draft: false 57 | prerelease: true 58 | generate_release_notes: true 59 | name: Nightly-${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} 60 | files: | 61 | ./${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: CI-PR 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | clang-format: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: clang-format 11 | run: | 12 | docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source ./include 13 | build-binary: 14 | runs-on: ubuntu-22.04 15 | needs: clang-format 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - name: create version.h 21 | run: | 22 | git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}") 23 | cat < ./source/wiiu/version.h 24 | #pragma once 25 | #define VERSION_EXTRA " (nightly-$git_hash)" 26 | EOF 27 | - name: build binary 28 | run: | 29 | docker build . -t builder 30 | docker run --rm -v ${PWD}:/project builder make 31 | - uses: actions/upload-artifact@master 32 | with: 33 | name: binary 34 | path: "*.wps" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.nds 2 | *.nds.xz 3 | *.3dsx 4 | *.3dsx.xz 5 | *.cia 6 | *.cia.xz 7 | *.elf 8 | *.nacp 9 | *.nds 10 | *.nro 11 | *.nro.xz 12 | *.nso 13 | *.pfs0 14 | *.smdh 15 | *~ 16 | .gdb_history 17 | 3ds/build 18 | 3ds-classic/build 19 | 3ds/romfs/*.t3x 20 | linux/build 21 | linux/ftpd 22 | nds/build 23 | switch/build 24 | switch-classic/build 25 | switch/romfs/*.zst 26 | switch/romfs/shaders/*.dksh 27 | .idea/ 28 | build*/ 29 | *.rpx 30 | *.wuhb 31 | *.wps 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rd/gls"] 2 | path = 3rd/gls 3 | url = git@github.com:microsoft/GSL.git 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/wiiu-env/devkitppc:20241128 2 | 3 | COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20250208 /artifacts $DEVKITPRO 4 | COPY --from=ghcr.io/wiiu-env/libmocha:20240603 /artifacts $DEVKITPRO 5 | 6 | WORKDIR project -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | 11 | include $(DEVKITPRO)/wups/share/wups_rules 12 | 13 | WUT_ROOT := $(DEVKITPRO)/wut 14 | WUPS_ROOT := $(DEVKITPRO)/wups 15 | 16 | #------------------------------------------------------------------------------- 17 | # TARGET is the name of the output 18 | # BUILD is the directory where object files & intermediate files will be placed 19 | # SOURCES is a list of directories containing source code 20 | # DATA is a list of directories containing data files 21 | # INCLUDES is a list of directories containing header files 22 | #------------------------------------------------------------------------------- 23 | TARGET := ftpiiu 24 | BUILD := build 25 | SOURCES := source source/wiiu source/posix 26 | DATA := data 27 | INCLUDES := source include 3rd/gls/include 28 | 29 | #------------------------------------------------------------------------------- 30 | # options for code generation 31 | #------------------------------------------------------------------------------- 32 | CFLAGS := -Wall -O2 -ffunction-sections \ 33 | $(MACHDEP) 34 | 35 | CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__ -DSTATUS_STRING="\"ftpd v$(VERSION)\"" \ 36 | -DNO_IPV6 -DCLASSIC -DNO_CONSOLE -DFTPDCONFIG="\"/config/ftpd/ftpd.cfg\"" 37 | 38 | CXXFLAGS := $(CFLAGS) -std=gnu++20 39 | 40 | ASFLAGS := -g $(ARCH) 41 | LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) $(WUPSSPECS) 42 | 43 | ifeq ($(DEBUG),1) 44 | CXXFLAGS += -DDEBUG -g 45 | CFLAGS += -DDEBUG -g 46 | endif 47 | 48 | ifeq ($(DEBUG),VERBOSE) 49 | CXXFLAGS += -DDEBUG -DVERBOSE_DEBUG -g 50 | CFLAGS += -DDEBUG -DVERBOSE_DEBUG -g 51 | endif 52 | 53 | LIBS := -lwups -lwut -lmocha 54 | 55 | #------------------------------------------------------------------------------- 56 | # list of directories containing libraries, this must be the top level 57 | # containing include and lib 58 | #------------------------------------------------------------------------------- 59 | LIBDIRS := $(PORTLIBS) $(WUPS_ROOT) $(WUT_ROOT) $(WUT_ROOT)/usr 60 | 61 | #------------------------------------------------------------------------------- 62 | # no real need to edit anything past this point unless you need to add additional 63 | # rules for different file extensions 64 | #------------------------------------------------------------------------------- 65 | ifneq ($(BUILD),$(notdir $(CURDIR))) 66 | #------------------------------------------------------------------------------- 67 | 68 | export OUTPUT := $(CURDIR)/$(TARGET) 69 | export TOPDIR := $(CURDIR) 70 | 71 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 72 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 73 | 74 | export DEPSDIR := $(CURDIR)/$(BUILD) 75 | 76 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 77 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 78 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 79 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 80 | 81 | #------------------------------------------------------------------------------- 82 | # use CXX for linking C++ projects, CC for standard C 83 | #------------------------------------------------------------------------------- 84 | ifeq ($(strip $(CPPFILES)),) 85 | #------------------------------------------------------------------------------- 86 | export LD := $(CC) 87 | #------------------------------------------------------------------------------- 88 | else 89 | #------------------------------------------------------------------------------- 90 | export LD := $(CXX) 91 | #------------------------------------------------------------------------------- 92 | endif 93 | #------------------------------------------------------------------------------- 94 | 95 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 96 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 97 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 98 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 99 | 100 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 101 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 102 | -I$(CURDIR)/$(BUILD) 103 | 104 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 105 | 106 | .PHONY: $(BUILD) clean all 107 | 108 | #------------------------------------------------------------------------------- 109 | all: $(BUILD) 110 | 111 | $(BUILD): 112 | @$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD)) 113 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 114 | 115 | #------------------------------------------------------------------------------- 116 | clean: 117 | @echo clean ... 118 | @rm -fr $(BUILD) $(TARGET).wps $(TARGET).elf 119 | 120 | #------------------------------------------------------------------------------- 121 | else 122 | .PHONY: all 123 | 124 | DEPENDS := $(OFILES:.o=.d) 125 | 126 | #------------------------------------------------------------------------------- 127 | # main targets 128 | #------------------------------------------------------------------------------- 129 | all : $(OUTPUT).wps 130 | 131 | $(OUTPUT).wps : $(OUTPUT).elf 132 | $(OUTPUT).elf : $(OFILES) 133 | 134 | $(OFILES_SRC) : $(HFILES_BIN) 135 | 136 | #------------------------------------------------------------------------------- 137 | # you need a rule like this for each extension you use as binary data 138 | #------------------------------------------------------------------------------- 139 | %.bin.o %_bin.h : %.bin 140 | #------------------------------------------------------------------------------- 141 | @echo $(notdir $<) 142 | @$(bin2o) 143 | 144 | -include $(DEPENDS) 145 | 146 | #------------------------------------------------------------------------------- 147 | endif 148 | #------------------------------------------------------------------------------- 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI-Release](https://github.com/wiiu-env/ftpiiu_plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/ftpiiu_plugin/actions/workflows/ci.yml) 2 | 3 | # ftpiiu - A ftp server plugin for the Wii U based on ftpd 4 | 5 | ## Installation 6 | (`[ENVIRONMENT]` is a placeholder for the actual environment name.) 7 | 8 | 1. Copy the file `ftpiiu.wps` into `sd:/wiiu/environments/[ENVIRONMENT]/plugins`. 9 | 2. Requires the [WiiUPluginLoaderBackend](https://github.com/wiiu-env/WiiUPluginLoaderBackend) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. 10 | 11 | ## Usage information and settings 12 | 13 | - By default, the FTPiiU server is running as long the plugin loaded (file is in the plugin directory of your environment). 14 | - Access to the system files is **disabled by default**, you can enable it in the config menu. 15 | - To connect to the server you can use empty credentials 16 | - The SD card can be accessed via `/fs/vol/external01/` 17 | 18 | Via the plugin config menu (press L, DPAD Down and Minus on the gamepad) you can configure the plugin. The available options are the following: 19 | - **Settings**: 20 | - Enable FTPiiU: 21 | - Starts/Stops the ftp server which is running in the background. Changes take effect when so close the config menu. (Default is true). 22 | - Allow access to system files: 23 | - Allows you to access all system files. If this option is disabled, you can only access `/fs/vol/content`, `/fs/vol/save` and `/fs/vol/external01` (SD card). Changes take effect when so close the config menu, but the server may restart. (Default is false). 24 | - Additionally, the config menu will display the IP of your console and the port the server is running at. 25 | 26 | See the [ftpd repository](https://github.com/mtheall/ftpd?tab=readme-ov-file#supported-commands) for a list of all supported commands. 27 | 28 | ### Logging 29 | Logs will only appear in the system log (OSReport). 30 | 31 | ## Building using the Dockerfile 32 | 33 | It's possible to use a docker image for building. This way you don't need anything installed on your host system. 34 | 35 | ``` 36 | # Build docker image (only needed once) 37 | docker build . -t ftpiiuplugin-builder 38 | 39 | # make 40 | docker run -it --rm -v ${PWD}:/project ftpiiuplugin-builder make 41 | 42 | # make clean 43 | docker run -it --rm -v ${PWD}:/project ftpiiuplugin-builder make clean 44 | ``` 45 | 46 | ## Format the code via docker 47 | 48 | `docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source ./include -i` 49 | 50 | ## Credits 51 | 52 | This plugin is based on [ftpd](https://github.com/mtheall/ftpd) by mtheall -------------------------------------------------------------------------------- /include/IOAbstraction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class IOAbstraction 9 | { 10 | public: 11 | static std::string convertPath (std::string_view inPath); 12 | 13 | static FILE *fopen (const char *_name, const char *_type); 14 | 15 | static int fseek (FILE *f, long pos, int origin); 16 | 17 | static size_t fread (void *buffer, size_t _size, size_t _n, FILE *f); 18 | 19 | static size_t fwrite (const void *buffer, size_t _size, size_t _n, FILE *f); 20 | 21 | static int closedir (DIR *dirp); 22 | 23 | static DIR *opendir (const char *dirname); 24 | 25 | static struct dirent *readdir (DIR *dirp); 26 | 27 | static int stat (const char *path, struct stat *sbuf); 28 | 29 | static int lstat (const char *path, struct stat *buf); 30 | 31 | static int mkdir (const char *path, mode_t mode); 32 | 33 | static int rmdir (const char *path); 34 | 35 | static int rename (const char *path, const char *path2); 36 | 37 | static int unlink (const char *path); 38 | 39 | static void addVirtualPath (const std::string &virtualPath, 40 | const std::vector &subDirectories); 41 | 42 | static void clear (); 43 | }; 44 | -------------------------------------------------------------------------------- /include/fs.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include "ioBuffer.h" 24 | 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace fs 37 | { 38 | /// \brief Print size in human-readable format (KiB, MiB, etc) 39 | /// \param size_ Size to print 40 | std::string printSize (std::uint64_t size_); 41 | 42 | /// \brief File I/O object 43 | class File 44 | { 45 | public: 46 | ~File (); 47 | 48 | File (); 49 | 50 | File (File const &that_) = delete; 51 | 52 | /// \brief Move constructor 53 | /// \param that_ Object to move from 54 | File (File &&that_); 55 | 56 | File &operator= (File const &that_) = delete; 57 | 58 | /// \brief Move assignment 59 | /// \param that_ Object to move from 60 | File &operator= (File &&that_); 61 | 62 | /// \brief bool cast operator 63 | explicit operator bool () const; 64 | 65 | /// \brief std::FILE* cast operator 66 | operator std::FILE * () const; 67 | 68 | /// \brief Set buffer size 69 | /// \param size_ Buffer size 70 | void setBufferSize (std::size_t size_); 71 | 72 | /// \brief Open file 73 | /// \param path_ Path to open 74 | /// \param mode_ Access mode (\sa std::fopen) 75 | bool open (gsl::not_null path_, gsl::not_null mode_ = "rb"); 76 | 77 | /// \brief Close file 78 | void close (); 79 | 80 | /// \brief Seek to file position 81 | /// \param pos_ File position 82 | /// \param origin_ Reference position (\sa std::fseek) 83 | std::make_signed_t seek (std::make_signed_t pos_, int origin_); 84 | 85 | /// \brief Read data 86 | /// \param buffer_ Output buffer 87 | /// \param size_ Size to read 88 | /// \note Can return partial reads 89 | std::make_signed_t read (gsl::not_null buffer_, std::size_t size_); 90 | 91 | /// \brief Read data 92 | /// \param buffer_ Output buffer 93 | /// \note Can return partial reads 94 | std::make_signed_t read (IOBuffer &buffer_); 95 | 96 | /// \brief Read line 97 | std::string_view readLine (); 98 | 99 | /// \brief Read data 100 | /// \param buffer_ Output buffer 101 | /// \param size_ Size to read 102 | /// \note Fails on partial reads and errors 103 | bool readAll (gsl::not_null buffer_, std::size_t size_); 104 | 105 | /// \brief Write data 106 | /// \param buffer_ Input data 107 | /// \param size_ Size to write 108 | /// \note Can return partial writes 109 | std::make_signed_t write (gsl::not_null buffer_, std::size_t size_); 110 | 111 | /// \brief Write data 112 | /// \param buffer_ Input data 113 | /// \note Can return partial writes 114 | std::make_signed_t write (IOBuffer &buffer_); 115 | 116 | /// \brief Write data 117 | /// \param buffer_ Input data 118 | /// \param size_ Size to write 119 | /// \note Fails on partials writes and errors 120 | bool writeAll (gsl::not_null buffer_, std::size_t size_); 121 | 122 | private: 123 | /// \brief Underlying std::FILE* 124 | std::unique_ptr m_fp{nullptr, nullptr}; 125 | 126 | /// \brief Buffer 127 | std::vector m_buffer; 128 | 129 | /// \brief Line buffer 130 | gsl::owner m_lineBuffer = nullptr; 131 | 132 | /// \brief Line buffer size 133 | std::size_t m_lineBufferSize = 0; 134 | }; 135 | 136 | /// Directory object 137 | class Dir 138 | { 139 | public: 140 | ~Dir (); 141 | 142 | Dir (); 143 | 144 | Dir (Dir const &that_) = delete; 145 | 146 | /// \brief Move constructor 147 | /// \param that_ Object to move from 148 | Dir (Dir &&that_); 149 | 150 | Dir &operator= (Dir const &that_) = delete; 151 | 152 | /// \brief Move assignment 153 | /// \param that_ Object to move from 154 | Dir &operator= (Dir &&that_); 155 | 156 | /// \brief bool cast operator 157 | explicit operator bool () const; 158 | 159 | /// \brief DIR* cast operator 160 | operator DIR * () const; 161 | 162 | /// \brief Open directory 163 | /// \param path_ Path to open 164 | bool open (gsl::not_null path_); 165 | 166 | /// \brief Close directory 167 | void close (); 168 | 169 | /// \brief Read a directory entry 170 | /// \note Returns nullptr on end-of-directory or error; check errno 171 | dirent *read (); 172 | 173 | private: 174 | /// \brief Underlying DIR* 175 | std::unique_ptr m_dp{nullptr, nullptr}; 176 | }; 177 | } 178 | -------------------------------------------------------------------------------- /include/ftpConfig.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include "platform.h" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | class FtpConfig; 34 | using UniqueFtpConfig = std::unique_ptr; 35 | 36 | /// \brief FTP config 37 | class FtpConfig 38 | { 39 | public: 40 | ~FtpConfig (); 41 | 42 | /// \brief Create config 43 | static UniqueFtpConfig create (); 44 | 45 | /// \brief Load config 46 | /// \param path_ Path to config file 47 | static UniqueFtpConfig load (gsl::not_null path_); 48 | 49 | #ifndef __NDS__ 50 | std::scoped_lock lockGuard (); 51 | #endif 52 | 53 | /// \brief Save config 54 | /// \param path_ Path to config file 55 | bool save (gsl::not_null path_); 56 | 57 | /// \brief Get user 58 | std::string const &user () const; 59 | 60 | /// \brief Get password 61 | std::string const &pass () const; 62 | 63 | /// \brief Get hostname 64 | std::string const &hostname () const; 65 | 66 | /// \brief Get port 67 | std::uint16_t port () const; 68 | 69 | #ifdef __3DS__ 70 | /// \brief Whether to get mtime 71 | /// \note only effective on 3DS 72 | bool getMTime () const; 73 | #endif 74 | 75 | #ifdef __SWITCH__ 76 | /// \brief Whether to enable access point 77 | bool enableAP () const; 78 | 79 | /// \brief Access point SSID 80 | std::string const &ssid () const; 81 | 82 | /// \brief Access point passphrase 83 | std::string const &passphrase () const; 84 | #endif 85 | 86 | /// \brief Set user 87 | /// \param user_ User 88 | void setUser (std::string user_); 89 | 90 | /// \brief Set password 91 | /// \param pass_ Password 92 | void setPass (std::string pass_); 93 | 94 | /// \brief Set hostname 95 | /// \param hostname_ Hostname 96 | void setHostname (std::string hostname_); 97 | 98 | /// \brief Set listen port 99 | /// \param port_ Listen port 100 | bool setPort (std::string_view port_); 101 | 102 | /// \brief Set listen port 103 | /// \param port_ Listen port 104 | bool setPort (std::uint16_t port_); 105 | 106 | #ifdef __3DS__ 107 | /// \brief Set whether to get mtime 108 | /// \param getMTime_ Whether to get mtime 109 | void setGetMTime (bool getMTime_); 110 | #endif 111 | 112 | #ifdef __SWITCH__ 113 | /// \brief Set whether to enable access point 114 | /// \param enable_ Whether to enable access point 115 | void setEnableAP (bool enable_); 116 | 117 | /// \brief Set access point SSID 118 | /// \param ssid_ SSID 119 | void setSSID (std::string_view ssid_); 120 | 121 | /// \brief Set access point passphrase 122 | /// \param passphrase_ Passphrase 123 | void setPassphrase (std::string_view passphrase_); 124 | #endif 125 | 126 | private: 127 | FtpConfig (); 128 | 129 | #ifndef __NDS__ 130 | /// \brief Mutex 131 | mutable platform::Mutex m_lock; 132 | #endif 133 | 134 | /// \brief Username 135 | std::string m_user; 136 | 137 | /// \brief Password 138 | std::string m_pass; 139 | 140 | /// \brief Hostname 141 | std::string m_hostname; 142 | 143 | /// \brief Listen port 144 | std::uint16_t m_port; 145 | 146 | #ifdef __3DS__ 147 | /// \brief Whether to get mtime 148 | bool m_getMTime = true; 149 | #endif 150 | 151 | #ifdef __SWITCH__ 152 | /// \brief Whether to enable access point 153 | bool m_enableAP = false; 154 | 155 | /// \brief Access point SSID 156 | std::string m_ssid = "ftpd"; 157 | 158 | /// \brief Access point passphrase 159 | std::string m_passphrase; 160 | #endif 161 | }; 162 | -------------------------------------------------------------------------------- /include/ftpServer.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include "ftpConfig.h" 24 | #include "ftpSession.h" 25 | #include "platform.h" 26 | #include "socket.h" 27 | 28 | #ifndef CLASSIC 29 | #include 30 | #endif 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | class FtpServer; 40 | using UniqueFtpServer = std::unique_ptr; 41 | 42 | /// \brief FTP server 43 | class FtpServer 44 | { 45 | public: 46 | ~FtpServer (); 47 | 48 | /// \brief Draw server and all of its sessions 49 | void draw (); 50 | 51 | /// \brief Whether server wants to quit 52 | bool quit (); 53 | 54 | /// \brief Create server 55 | static UniqueFtpServer create (); 56 | 57 | /// \brief Get free space 58 | static std::string getFreeSpace (); 59 | 60 | /// \brief Update free space 61 | static void updateFreeSpace (); 62 | 63 | /// \brief Server start time 64 | static std::time_t startTime (); 65 | 66 | #ifdef __3DS__ 67 | /// \brief Get timezone offset in seconds (only used on 3DS) 68 | static int tzOffset (); 69 | #endif 70 | 71 | private: 72 | /// \brief Paramterized constructor 73 | /// \param config_ FTP config 74 | FtpServer (UniqueFtpConfig config_); 75 | 76 | /// \brief Handle when network is found 77 | void handleNetworkFound (); 78 | 79 | /// \brief Handle when network is lost 80 | void handleNetworkLost (); 81 | 82 | #ifndef CLASSIC 83 | /// \brief Show menu in the current window 84 | void showMenu (); 85 | 86 | /// \brief Show settings menu 87 | void showSettings (); 88 | 89 | /// \brief Show about window 90 | void showAbout (); 91 | #endif 92 | 93 | /// \brief Server loop 94 | void loop (); 95 | 96 | /// \brief Thread entry point 97 | void threadFunc (); 98 | 99 | #ifndef __NDS__ 100 | /// \brief Thread 101 | platform::Thread m_thread; 102 | 103 | /// \brief Mutex 104 | platform::Mutex m_lock; 105 | #endif 106 | 107 | /// \brief Config 108 | UniqueFtpConfig m_config; 109 | 110 | /// \brief Listen socket 111 | UniqueSocket m_socket; 112 | 113 | #ifndef __NDS__ 114 | /// \brief mDNS socket 115 | UniqueSocket m_mdnsSocket; 116 | #endif 117 | 118 | /// \brief ImGui window name 119 | std::string m_name; 120 | 121 | /// \brief Sessions 122 | std::vector m_sessions; 123 | 124 | /// \brief Whether thread should quit 125 | std::atomic_bool m_quit = false; 126 | 127 | #ifndef CLASSIC 128 | /// \brief Log upload cURL context 129 | CURLM *m_uploadLogCurlM = nullptr; 130 | /// \brief Log upload mime context 131 | curl_mime *m_uploadLogMime = nullptr; 132 | /// \brief Log upload cURL context 133 | std::atomic m_uploadLogCurl = nullptr; 134 | 135 | /// \brief Log upload data 136 | std::string m_uploadLogData; 137 | /// \brief Log upload result 138 | std::string m_uploadLogResult; 139 | #endif 140 | 141 | #ifndef CLASSIC 142 | /// \brief Whether to show settings menu 143 | bool m_showSettings = false; 144 | 145 | #ifdef __SWITCH__ 146 | /// \brief Whether to show access point menu 147 | bool m_showAP = false; 148 | #endif 149 | 150 | /// \brief Whether to show about window 151 | bool m_showAbout = false; 152 | 153 | /// \brief User name setting 154 | std::string m_userSetting; 155 | 156 | /// \brief Password setting 157 | std::string m_passSetting; 158 | 159 | /// \brief Hostname setting 160 | std::string m_hostnameSetting; 161 | 162 | /// \brief Port setting 163 | std::uint16_t m_portSetting = 0; 164 | 165 | #ifdef __3DS__ 166 | /// \brief getMTime setting 167 | bool m_getMTimeSetting; 168 | #endif 169 | 170 | #ifdef __SWITCH__ 171 | /// \brief Whether an error occurred enabling access point 172 | std::atomic_bool m_apError = false; 173 | 174 | /// \brief Enable access point setting 175 | bool m_enableAPSetting; 176 | 177 | /// \brief Access point SSID setting 178 | std::string m_ssidSetting; 179 | 180 | /// \brief Access point passphrase setting 181 | std::string m_passphraseSetting; 182 | #endif 183 | #endif 184 | }; 185 | -------------------------------------------------------------------------------- /include/ftpSession.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include "fs.h" 24 | #include "ftpConfig.h" 25 | #include "ioBuffer.h" 26 | #include "platform.h" 27 | #include "socket.h" 28 | 29 | #if __has_include() 30 | #include 31 | #define FTPD_HAS_GLOB 1 32 | #else 33 | #define FTPD_HAS_GLOB 0 34 | #endif 35 | 36 | #include 37 | using stat_t = struct stat; 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | class FtpSession; 48 | using UniqueFtpSession = std::unique_ptr; 49 | 50 | /// \brief FTP session 51 | class FtpSession 52 | { 53 | public: 54 | ~FtpSession (); 55 | 56 | /// \brief Whether session sockets are all inactive 57 | bool dead (); 58 | 59 | /// \brief Draw session status 60 | void draw (); 61 | 62 | /// \brief Draw session connections 63 | void drawConnections (); 64 | 65 | /// \brief Create session 66 | /// \param config_ FTP config 67 | /// \param commandSocket_ Command socket 68 | static UniqueFtpSession create (FtpConfig &config_, UniqueSocket commandSocket_); 69 | 70 | /// \brief Poll for activity 71 | /// \param sessions_ Sessions to poll 72 | static bool poll (std::vector const &sessions_); 73 | 74 | private: 75 | /// \brief Command buffer size 76 | constexpr static auto COMMAND_BUFFERSIZE = 4096; 77 | 78 | #ifdef __NDS__ 79 | /// \brief Response buffer size 80 | constexpr static auto RESPONSE_BUFFERSIZE = 4096; 81 | 82 | /// \brief Transfer buffersize 83 | constexpr static auto XFER_BUFFERSIZE = 8192; 84 | #else 85 | /// \brief Response buffer size 86 | constexpr static auto RESPONSE_BUFFERSIZE = 16 * 1024; 87 | 88 | /// \brief Transfer buffersize 89 | constexpr static auto XFER_BUFFERSIZE = 32 * 1024; 90 | #endif 91 | 92 | /// \brief File buffersize 93 | constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE; 94 | 95 | #if defined(__NDS__) 96 | /// \brief Socket buffer size 97 | constexpr static auto SOCK_BUFFERSIZE = 4096; 98 | 99 | /// \brief Amount of file position history to keep 100 | constexpr static auto POSITION_HISTORY = 60; 101 | #elif defined(__3DS__) 102 | /// \brief Socket buffer size 103 | constexpr static auto SOCK_BUFFERSIZE = 32768; 104 | 105 | /// \brief Amount of file position history to keep 106 | constexpr static auto POSITION_HISTORY = 100; 107 | #else 108 | /// \brief Socket buffer size 109 | constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE; 110 | 111 | /// \brief Amount of file position history to keep 112 | constexpr static auto POSITION_HISTORY = 300; 113 | #endif 114 | 115 | /// \brief Session state 116 | enum class State 117 | { 118 | COMMAND, 119 | DATA_CONNECT, 120 | DATA_TRANSFER, 121 | }; 122 | 123 | /// \brief Transfer file mode 124 | enum class XferFileMode 125 | { 126 | RETR, 127 | STOR, 128 | APPE, 129 | }; 130 | 131 | /// \brief Transfer directory mode 132 | enum class XferDirMode 133 | { 134 | LIST, 135 | MLSD, 136 | MLST, 137 | NLST, 138 | STAT, 139 | }; 140 | 141 | /// \brief Parameterized constructor 142 | /// \param config_ FTP config 143 | /// \param commandSocket_ Command socket 144 | FtpSession (FtpConfig &config_, UniqueSocket commandSocket_); 145 | 146 | /// \brief Whether session is authorized 147 | bool authorized () const; 148 | 149 | /// \brief Set session state 150 | /// \param state_ State to set 151 | /// \param closePasv_ Whether to close listening socket 152 | /// \param closeData_ Whether to close data socket 153 | void setState (State state_, bool closePasv_, bool closeData_); 154 | 155 | /// \brief Close socket 156 | /// \param socket_ Socket to close 157 | void closeSocket (SharedSocket &socket_); 158 | /// \brief Close command socket 159 | void closeCommand (); 160 | /// \brief Close passive socket 161 | void closePasv (); 162 | /// \brief Close data socket 163 | void closeData (); 164 | 165 | /// \brief Change working directory 166 | bool changeDir (char const *args_); 167 | 168 | /// \brief Accept connection as data socket 169 | bool dataAccept (); 170 | 171 | /// \brief Connect data socket 172 | bool dataConnect (); 173 | 174 | /// \brief Perform stat and apply tz offset to mtime 175 | /// \param path_ Path to stat 176 | /// \param st_ Output stat 177 | int tzStat (char const *const path_, stat_t *st_); 178 | 179 | /// \brief Perform lstat and apply tz offset to mtime 180 | /// \param path_ Path to lstat 181 | /// \param st_ Output stat 182 | int tzLStat (char const *const path_, stat_t *st_); 183 | 184 | /// \brief Fill directory entry 185 | /// \param st_ Entry status 186 | /// \param path_ Path name 187 | /// \param type_ MLST type 188 | int fillDirent (stat_t const &st_, std::string_view path_, char const *type_ = nullptr); 189 | 190 | /// \brief Fill directory entry 191 | /// \param path_ Path name 192 | /// \param type_ MLST type 193 | int fillDirent (std::string const &path_, char const *type_ = nullptr); 194 | 195 | /// \brief Transfer file 196 | /// \param args_ Command arguments 197 | /// \param mode_ Transfer file mode 198 | void xferFile (char const *args_, XferFileMode mode_); 199 | 200 | /// \brief Transfer directory 201 | /// \param args_ Command arguments 202 | /// \param mode_ Transfer directory mode 203 | /// \param workaround_ Workaround broken clients who use LIST -a/-l 204 | void xferDir (char const *args_, XferDirMode mode_, bool workaround_); 205 | 206 | /// \brief Read command 207 | /// \param events_ Poll events 208 | void readCommand (int events_); 209 | 210 | /// \brief Write response 211 | void writeResponse (); 212 | 213 | /// \brief Send response 214 | /// \param fmt_ Message format 215 | __attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...); 216 | 217 | /// \brief Send response 218 | /// \param response_ Response message 219 | void sendResponse (std::string_view response_); 220 | 221 | /// \brief Transfer function 222 | bool (FtpSession::*m_transfer) () = nullptr; 223 | 224 | /// \brief Transfer directory list 225 | bool listTransfer (); 226 | 227 | #if FTPD_HAS_GLOB 228 | /// \brief Transfer glob list 229 | bool globTransfer (); 230 | #endif 231 | 232 | /// \brief Transfer download 233 | bool retrieveTransfer (); 234 | 235 | /// \brief Transfer upload 236 | bool storeTransfer (); 237 | 238 | #ifndef __NDS__ 239 | /// \brief Mutex 240 | platform::Mutex m_lock; 241 | #endif 242 | 243 | /// \brief FTP config 244 | FtpConfig &m_config; 245 | 246 | /// \brief Command socket 247 | SharedSocket m_commandSocket; 248 | 249 | /// \brief Data listen socker 250 | UniqueSocket m_pasvSocket; 251 | 252 | /// \brief Data socket 253 | SharedSocket m_dataSocket; 254 | 255 | /// \brief Sockets pending close 256 | std::vector m_pendingCloseSocket; 257 | 258 | /// \brief Command buffer 259 | IOBuffer m_commandBuffer; 260 | 261 | /// \brief Response buffer 262 | IOBuffer m_responseBuffer; 263 | 264 | /// \brief Transfer buffer 265 | IOBuffer m_xferBuffer; 266 | 267 | /// \brief Address from last PORT command 268 | SockAddr m_portAddr; 269 | 270 | /// \brief Current working directory 271 | std::string m_cwd = "/"; 272 | 273 | /// \brief List working directory 274 | std::string m_lwd; 275 | 276 | /// \brief Path from RNFR command 277 | std::string m_rename; 278 | 279 | /// \brief Current work item 280 | std::string m_workItem; 281 | 282 | /// \brief ImGui window name 283 | std::string m_windowName; 284 | 285 | /// \brief ImGui plot widget name 286 | std::string m_plotName; 287 | 288 | /// \brief Position from REST command 289 | std::uint64_t m_restartPosition = 0; 290 | 291 | /// \brief Current file position 292 | std::uint64_t m_filePosition = 0; 293 | 294 | /// \brief File size of current transfer 295 | std::uint64_t m_fileSize = 0; 296 | 297 | /// \brief Last file position update timestamp 298 | platform::steady_clock::time_point m_filePositionTime; 299 | 300 | /// \brief File position history 301 | std::uint64_t m_filePositionHistory[POSITION_HISTORY]; 302 | 303 | /// \brief File position history deltas 304 | float m_filePositionDeltas[POSITION_HISTORY]; 305 | 306 | /// \brief Transfer rate (EWMA low-pass filtered) 307 | float m_xferRate; 308 | 309 | /// \brief Session state 310 | State m_state = State::COMMAND; 311 | 312 | /// \brief File being transferred 313 | fs::File m_file; 314 | 315 | /// \brief Directory being transferred 316 | fs::Dir m_dir; 317 | 318 | #if FTPD_HAS_GLOB 319 | /// \brief Glob wrappre 320 | class Glob 321 | { 322 | public: 323 | ~Glob () noexcept; 324 | Glob () noexcept; 325 | 326 | /// \brief Perform glob 327 | /// \param pattern_ Glob pattern 328 | bool glob (char const *pattern_) noexcept; 329 | 330 | /// \brief Get next glob result 331 | /// \note returns nullptr when no more entries exist 332 | char const *next () noexcept; 333 | 334 | private: 335 | /// \brief Clear glob 336 | void clear () noexcept; 337 | 338 | /// \brief Glob result 339 | std::optional m_glob = std::nullopt; 340 | 341 | /// \brief Result counter 342 | unsigned m_offset = 0; 343 | }; 344 | 345 | /// \brief Glob 346 | Glob m_glob; 347 | #endif 348 | 349 | /// \brief Directory transfer mode 350 | XferDirMode m_xferDirMode; 351 | 352 | /// \brief Last activity timestamp 353 | time_t m_timestamp; 354 | 355 | /// \brief Whether user has been authorized 356 | bool m_authorizedUser : 1; 357 | /// \brief Whether password has been authorized 358 | bool m_authorizedPass : 1; 359 | /// \brief Whether previous command was PASV 360 | bool m_pasv : 1; 361 | /// \brief Whether previous command was PORT 362 | bool m_port : 1; 363 | /// \brief Whether receiving data 364 | bool m_recv : 1; 365 | /// \brief Whether sending data 366 | bool m_send : 1; 367 | /// \brief Whether urgent (out-of-band) data is on the way 368 | bool m_urgent : 1; 369 | 370 | /// \brief Whether MLST type fact is enabled 371 | bool m_mlstType : 1; 372 | /// \brief Whether MLST size fact is enabled 373 | bool m_mlstSize : 1; 374 | /// \brief Whether MLST modify fact is enabled 375 | bool m_mlstModify : 1; 376 | /// \brief Whether MLST perm fact is enabled 377 | bool m_mlstPerm : 1; 378 | /// \brief Whether MLST unix.mode fact is enabled 379 | bool m_mlstUnixMode : 1; 380 | 381 | /// \brief Whether emulating /dev/zero 382 | bool m_devZero : 1; 383 | 384 | /// \brief Abort a transfer 385 | /// \param args_ Command arguments 386 | void ABOR (char const *args_); 387 | 388 | /// \brief Allocate space 389 | /// \param args_ Command arguments 390 | void ALLO (char const *args_); 391 | 392 | /// \brief Append data to a file 393 | /// \param args_ Command arguments 394 | void APPE (char const *args_); 395 | 396 | /// \brief CWD to parent directory 397 | /// \param args_ Command arguments 398 | void CDUP (char const *args_); 399 | 400 | /// \brief Change working directory 401 | /// \param args_ Command arguments 402 | void CWD (char const *args_); 403 | 404 | /// \brief Delete a file 405 | /// \param args_ Command arguments 406 | void DELE (char const *args_); 407 | 408 | /// \brief List server features 409 | /// \param args_ Command arguments 410 | void FEAT (char const *args_); 411 | 412 | /// \brief Print server help 413 | /// \param args_ Command arguments 414 | void HELP (char const *args_); 415 | 416 | /// \brief List directory 417 | /// \param args_ Command arguments 418 | void LIST (char const *args_); 419 | 420 | /// \brief Last modification time 421 | /// \param args_ Command arguments 422 | void MDTM (char const *args_); 423 | 424 | /// \brief Create a directory 425 | /// \param args_ Command arguments 426 | void MKD (char const *args_); 427 | 428 | /// \brief Machine list directory 429 | /// \param args_ Command arguments 430 | void MLSD (char const *args_); 431 | 432 | /// \brief Machine list 433 | /// \param args_ Command arguments 434 | void MLST (char const *args_); 435 | 436 | /// \brief Set transfer mode 437 | /// \param args_ Command arguments 438 | void MODE (char const *args_); 439 | 440 | /// \brief Name list 441 | /// \param args_ Command arguments 442 | void NLST (char const *args_); 443 | 444 | /// \brief No-op 445 | /// \param args_ Command arguments 446 | void NOOP (char const *args_); 447 | 448 | /// \brief Set server options 449 | /// \param args_ Command arguments 450 | void OPTS (char const *args_); 451 | 452 | /// \brief Password 453 | /// \param args_ Command arguments 454 | void PASS (char const *args_); 455 | 456 | /// \brief Request an address to connect to for data transfers 457 | /// \param args_ Command arguments 458 | void PASV (char const *args_); 459 | 460 | /// \brief Provide an address to connect to for data transfers 461 | /// \param args_ Command arguments 462 | void PORT (char const *args_); 463 | 464 | /// \brief Print working directory 465 | /// \param args_ Command arguments 466 | void PWD (char const *args_); 467 | 468 | /// \brief Terminate session 469 | /// \param args_ Command arguments 470 | void QUIT (char const *args_); 471 | 472 | /// \brief Restart a file transfer 473 | /// \param args_ Command arguments 474 | void REST (char const *args_); 475 | 476 | /// \brief Retrieve a file 477 | /// \param args_ Command arguments 478 | /// \note Requires a PASV or PORT connection 479 | void RETR (char const *args_); 480 | 481 | /// \brief Remove a directory 482 | /// \param args_ Command arguments 483 | void RMD (char const *args_); 484 | 485 | /// \brief Rename from 486 | /// \param args_ Command arguments 487 | void RNFR (char const *args_); 488 | 489 | /// \brief Rename to 490 | /// \param args_ Command arguments 491 | void RNTO (char const *args_); 492 | 493 | /// \brief Site command 494 | /// \param args_ Command arguments 495 | void SITE (char const *args_); 496 | 497 | /// \brief Get file size 498 | /// \param args_ Command arguments 499 | void SIZE (char const *args_); 500 | 501 | /// \brief Get status 502 | /// \param args_ Command arguments 503 | /// \note If no argument is supplied, and a transfer is occurring, get the current transfer 504 | /// status. If no argument is supplied, and no transfer is occurring, get the server status. If 505 | /// an argument is supplied, this is equivalent to LIST, except the data is sent over the 506 | /// command socket. 507 | void STAT (char const *args_); 508 | 509 | /// \brief Store a file 510 | /// \param args_ Command arguments 511 | void STOR (char const *args_); 512 | 513 | /// \brief Store a unique file 514 | /// \param args_ Command arguments 515 | void STOU (char const *args_); 516 | 517 | /// \brief Set file structure 518 | /// \param args_ Command arguments 519 | void STRU (char const *args_); 520 | 521 | /// \brief Identify system 522 | /// \param args_ Command arguments 523 | void SYST (char const *args_); 524 | 525 | /// \brief Set representation type 526 | /// \param args_ Command arguments 527 | void TYPE (char const *args_); 528 | 529 | /// \brief User name 530 | /// \param args_ Command arguments 531 | void USER (char const *args_); 532 | 533 | /// \brief Map of command handlers 534 | static std::vector> const 535 | handlers; 536 | }; 537 | -------------------------------------------------------------------------------- /include/ioBuffer.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2020 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | /// \brief I/O buffer 27 | /// [unusable][usedArea][freeArea] 28 | class IOBuffer 29 | { 30 | public: 31 | ~IOBuffer (); 32 | 33 | /// \brief Parameterized constructor 34 | /// \param size_ Buffer size 35 | IOBuffer (std::size_t size_); 36 | 37 | /// \brief Get pointer to writable area 38 | char *freeArea () const; 39 | /// \brief Get size of writable area 40 | std::size_t freeSize () const; 41 | 42 | /// \brief Get pointer to readable area 43 | char *usedArea () const; 44 | /// \brief Get size of readable area 45 | std::size_t usedSize () const; 46 | 47 | /// \brief Consume data from the beginning of usedArea 48 | /// \param size_ Size to consume 49 | /// [unusable][+++++usedArea][freeArea] 50 | /// becomes 51 | /// [unusable+++++][usedArea][freeArea] 52 | void markFree (std::size_t size_); 53 | /// \brief Produce data to the end of usedArea from freeArea 54 | /// [unusable][usedArea][++++++freeArea] 55 | /// becomes 56 | /// [unusable][usedArea++++++][freeArea] 57 | void markUsed (std::size_t size_); 58 | 59 | /// \brief Whether usedArea is empty 60 | bool empty () const; 61 | 62 | /// \brief Get buffer capacity 63 | std::size_t capacity () const; 64 | 65 | /// \brief Clear buffer; usedArea becomes empty 66 | /// [unusable][usedArea][++++++freeArea] 67 | /// becomes 68 | /// [freeArea++++++++++++++++++++++++++] 69 | void clear (); 70 | 71 | /// \brief Move usedArea to the beginning of the buffer 72 | /// [unusable][usedArea][freeArea] 73 | /// becomes 74 | /// [usedArea][freeArea++++++++++] 75 | void coalesce (); 76 | 77 | private: 78 | /// \brief Buffer 79 | std::unique_ptr m_buffer; 80 | 81 | /// \brief Buffer size 82 | std::size_t const m_size; 83 | 84 | /// \brief Start of usedArea 85 | std::size_t m_start = 0; 86 | /// \brief Start of freeArea 87 | std::size_t m_end = 0; 88 | }; 89 | -------------------------------------------------------------------------------- /include/log.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2023 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef DEBUG 28 | #undef DEBUG 29 | #endif 30 | 31 | /// \brief Log level 32 | enum LogLevel 33 | { 34 | DEBUGLOG, 35 | INFO, 36 | ERROR, 37 | COMMAND, 38 | RESPONSE, 39 | }; 40 | 41 | /// \brief Draw log 42 | void drawLog (); 43 | 44 | #ifndef CLASSIC 45 | /// \brief Get log 46 | std::string getLog (); 47 | #endif 48 | 49 | /// \brief Add debug message to bound log 50 | /// \param fmt_ Message format 51 | __attribute__ ((format (printf, 1, 2))) void debug (char const *fmt_, ...); 52 | 53 | /// \brief Add info message to bound log 54 | /// \param fmt_ Message format 55 | __attribute__ ((format (printf, 1, 2))) void info (char const *fmt_, ...); 56 | 57 | /// \brief Add error message to bound log 58 | /// \param fmt_ Message format 59 | __attribute__ ((format (printf, 1, 2))) void error (char const *fmt_, ...); 60 | 61 | /// \brief Add command message to bound log 62 | /// \param fmt_ Message format 63 | __attribute__ ((format (printf, 1, 2))) void command (char const *fmt_, ...); 64 | 65 | /// \brief Add response message to bound log 66 | /// \param fmt_ Message format 67 | __attribute__ ((format (printf, 1, 2))) void response (char const *fmt_, ...); 68 | 69 | /// \brief Add log message to bound log 70 | /// \param level_ Log level 71 | /// \param fmt_ Message format 72 | /// \param ap_ Message arguments 73 | void addLog (LogLevel level_, char const *fmt_, va_list ap_); 74 | 75 | /// \brief Add log message to bound log 76 | /// \param level_ Log level 77 | /// \param message_ Message to log 78 | void addLog (LogLevel level_, std::string_view message_); 79 | -------------------------------------------------------------------------------- /include/mdns.h: -------------------------------------------------------------------------------- 1 | 2 | // ftpd is a server implementation based on the following: 3 | // - RFC 959 (https://datatracker.ietf.org/doc/html/rfc959) 4 | // - RFC 3659 (https://datatracker.ietf.org/doc/html/rfc3659) 5 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 6 | // 7 | // ftpd implements mdns based on the following: 8 | // - RFC 1035 (https://datatracker.ietf.org/doc/html/rfc1035) 9 | // - RFC 6762 (https://datatracker.ietf.org/doc/html/rfc6762) 10 | // 11 | // Copyright (C) 2024 Michael Theall 12 | // 13 | // This program is free software: you can redistribute it and/or modify 14 | // it under the terms of the GNU General Public License as published by 15 | // the Free Software Foundation, either version 3 of the License, or 16 | // (at your option) any later version. 17 | // 18 | // This program is distributed in the hope that it will be useful, 19 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | // GNU General Public License for more details. 22 | // 23 | // You should have received a copy of the GNU General Public License 24 | // along with this program. If not, see . 25 | 26 | #pragma once 27 | 28 | #include "sockAddr.h" 29 | #include "socket.h" 30 | 31 | #include 32 | 33 | namespace mdns 34 | { 35 | void setHostname (std::string hostname_); 36 | 37 | UniqueSocket createSocket (); 38 | 39 | void handleSocket (Socket *socket_, SockAddr const &addr_); 40 | } 41 | -------------------------------------------------------------------------------- /include/platform.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include "sockAddr.h" 24 | 25 | #if defined(__NDS__) 26 | #include 27 | #elif defined(__3DS__) 28 | #include <3ds.h> 29 | #elif defined(__SWITCH__) 30 | #include 31 | #elif defined(__WIIU__) 32 | #include 33 | #include 34 | #endif 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #if defined(CLASSIC) && !defined(__WIIU__) 43 | extern PrintConsole g_statusConsole; 44 | extern PrintConsole g_logConsole; 45 | extern PrintConsole g_sessionConsole; 46 | #endif 47 | 48 | namespace platform 49 | { 50 | /// \brief Initialize platform 51 | bool init (); 52 | 53 | #ifdef __SWITCH__ 54 | /// \brief Enable access point 55 | /// \param enable_ Whether to enable access point 56 | /// \param ssid_ SSID 57 | /// \param passphrase_ Passphrase 58 | bool enableAP (bool enable_, std::string const &ssid_, std::string const &passphrase_); 59 | 60 | /// \brief Check if SSID is valid 61 | /// \param ssid_ SSID to check 62 | /// \returns empty string on success, error message on failure 63 | char const *validateSSID (std::string const &ssid_); 64 | 65 | /// \brief Check if passphrase is valid 66 | /// \param passphrase_ Passphrase to check 67 | /// \returns empty string on success, error message on failure 68 | char const *validatePassphrase (std::string const &passphrase_); 69 | #endif 70 | 71 | /// \brief Whether network is visible 72 | bool networkVisible (); 73 | 74 | /// \brief Get network address 75 | /// \param[out] addr_ Network address 76 | bool networkAddress (SockAddr &addr_); 77 | 78 | /// \brief Get hostname 79 | std::string const &hostname (); 80 | 81 | /// \brief Platform loop 82 | bool loop (); 83 | 84 | /// \brief Platform render 85 | void render (); 86 | 87 | /// \brief Deinitialize platform 88 | void exit (); 89 | 90 | #ifdef __3DS__ 91 | /// \brief Steady clock 92 | struct steady_clock 93 | { 94 | /// \brief Type representing number of ticks 95 | using rep = std::uint64_t; 96 | 97 | /// \brief Type representing ratio of clock period in seconds 98 | using period = std::ratio<1, SYSCLOCK_ARM11>; 99 | 100 | /// \brief Duration type 101 | using duration = std::chrono::duration; 102 | 103 | /// \brief Timestamp type 104 | using time_point = std::chrono::time_point; 105 | 106 | /// \brief Whether clock is steady 107 | constexpr static bool is_steady = true; 108 | 109 | /// \brief Current timestamp 110 | static time_point now () noexcept; 111 | }; 112 | #else 113 | /// \brief Steady clock 114 | using steady_clock = std::chrono::steady_clock; 115 | #endif 116 | 117 | #ifndef __NDS__ 118 | /// \brief Platform thread 119 | class Thread 120 | { 121 | public: 122 | ~Thread (); 123 | Thread (); 124 | 125 | /// \brief Parameterized constructor 126 | /// \param func_ Thread entrypoint 127 | Thread (std::function &&func_); 128 | 129 | Thread (Thread const &that_) = delete; 130 | 131 | /// \brief Move constructor 132 | /// \param that_ Object to move from 133 | Thread (Thread &&that_); 134 | 135 | Thread &operator= (Thread const &that_) = delete; 136 | 137 | /// \brief Move assignment 138 | /// \param that_ Object to move from 139 | Thread &operator= (Thread &&that_); 140 | 141 | /// \brief Join thread 142 | void join (); 143 | 144 | /// \brief Suspend current thread 145 | /// \param timeout_ Minimum time to sleep 146 | static void sleep (std::chrono::milliseconds timeout_); 147 | 148 | private: 149 | class privateData_t; 150 | 151 | /// \brief pimpl 152 | std::unique_ptr m_d; 153 | }; 154 | 155 | /// \brief Platform mutex 156 | class Mutex 157 | { 158 | public: 159 | ~Mutex (); 160 | Mutex (); 161 | 162 | /// \brief Lock mutex 163 | void lock (); 164 | 165 | /// \brief Unlock mutex 166 | void unlock (); 167 | 168 | private: 169 | class privateData_t; 170 | 171 | /// \brief pimpl 172 | std::unique_ptr m_d; 173 | }; 174 | #endif 175 | } 176 | -------------------------------------------------------------------------------- /include/sockAddr.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | /// \brief Socket address 30 | class SockAddr 31 | { 32 | public: 33 | enum class Domain 34 | { 35 | IPv4 = AF_INET, 36 | #ifndef NO_IPV6 37 | IPv6 = AF_INET6, 38 | #endif 39 | }; 40 | 41 | /// \brief 0.0.0.0 42 | static SockAddr const AnyIPv4; 43 | 44 | #ifndef NO_IPV6 45 | /// \brief :: 46 | static SockAddr const AnyIPv6; 47 | #endif 48 | 49 | ~SockAddr (); 50 | 51 | SockAddr (); 52 | 53 | /// \brief Parameterized constructor 54 | /// \param domain_ Socket domain 55 | /// \note Initial address is INADDR_ANY/in6addr_any 56 | SockAddr (Domain domain_); 57 | 58 | /// \brief Parameterized constructor 59 | /// \param addr_ Socket address (network byte order) 60 | /// \param port_ Socket port (host byte order) 61 | SockAddr (in_addr_t addr_, std::uint16_t port_ = 0); 62 | 63 | /// \brief Parameterized constructor 64 | /// \param addr_ Socket address (network byte order) 65 | /// \param port_ Socket port (host byte order) 66 | SockAddr (in_addr const &addr_, std::uint16_t port_ = 0); 67 | 68 | #ifndef NO_IPV6 69 | /// \brief Parameterized constructor 70 | /// \param addr_ Socket address 71 | /// \param port_ Socket port (host byte order) 72 | SockAddr (in6_addr const &addr_, std::uint16_t port_ = 0); 73 | #endif 74 | 75 | /// \brief Copy constructor 76 | /// \param that_ Object to copy 77 | SockAddr (SockAddr const &that_); 78 | 79 | /// \brief Move constructor 80 | /// \param that_ Object to move from 81 | SockAddr (SockAddr &&that_); 82 | 83 | /// \brief Copy assignment 84 | /// \param that_ Object to copy 85 | SockAddr &operator= (SockAddr const &that_); 86 | 87 | /// \brief Move assignment 88 | /// \param that_ Object to move from 89 | SockAddr &operator= (SockAddr &&that_); 90 | 91 | /// \brief Parameterized constructor 92 | /// \param addr_ Address (network byte order) 93 | SockAddr (sockaddr_in const &addr_); 94 | 95 | #ifndef NO_IPV6 96 | /// \brief Parameterized constructor 97 | /// \param addr_ Address (network byte order) 98 | SockAddr (sockaddr_in6 const &addr_); 99 | #endif 100 | 101 | /// \brief Parameterized constructor 102 | /// \param addr_ Address (network byte order) 103 | SockAddr (sockaddr_storage const &addr_); 104 | 105 | /// \brief sockaddr_in cast operator (network byte order) 106 | operator sockaddr_in const & () const; 107 | 108 | #ifndef NO_IPV6 109 | /// \brief sockaddr_in6 cast operator (network byte order) 110 | operator sockaddr_in6 const & () const; 111 | #endif 112 | 113 | /// \brief sockaddr_storage cast operator (network byte order) 114 | operator sockaddr_storage const & () const; 115 | 116 | /// \brief sockaddr* cast operator (network byte order) 117 | operator sockaddr * (); 118 | 119 | /// \brief sockaddr const* cast operator (network byte order) 120 | operator sockaddr const * () const; 121 | 122 | /// \brief Equality operator 123 | bool operator== (SockAddr const &that_) const; 124 | 125 | /// \brief Comparison operator 126 | std::strong_ordering operator<=> (SockAddr const &that_) const; 127 | 128 | /// \brief sockaddr domain 129 | Domain domain () const; 130 | 131 | /// \brief sockaddr size 132 | socklen_t size () const; 133 | 134 | /// \brief Set address 135 | /// \param addr_ Address to set (network byte order) 136 | void setAddr (in_addr_t addr_); 137 | 138 | /// \brief Set address 139 | /// \param addr_ Address to set (network byte order) 140 | void setAddr (in_addr const &addr_); 141 | 142 | #ifndef NO_IPV6 143 | /// \brief Set address 144 | /// \param addr_ Address to set (network byte order) 145 | void setAddr (in6_addr const &addr_); 146 | #endif 147 | 148 | /// \brief Address port (host byte order) 149 | std::uint16_t port () const; 150 | 151 | /// \brief Set address port 152 | /// \param port_ Port to set (host byte order) 153 | void setPort (std::uint16_t port_); 154 | 155 | /// \brief Address name 156 | /// \param buffer_ Buffer to hold name 157 | /// \param size_ Size of buffer_ 158 | /// \retval buffer_ success 159 | /// \retval nullptr failure 160 | char const *name (char *buffer_, std::size_t size_) const; 161 | 162 | /// \brief Address name 163 | /// \retval nullptr failure 164 | /// \note This function is not reentrant 165 | char const *name () const; 166 | 167 | private: 168 | /// \brief Address storage (network byte order) 169 | sockaddr_storage m_addr = {}; 170 | }; 171 | -------------------------------------------------------------------------------- /include/socket.h: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #pragma once 22 | 23 | #include "ioBuffer.h" 24 | #include "sockAddr.h" 25 | 26 | #include 27 | #include 28 | 29 | #ifdef __NDS__ 30 | struct pollfd 31 | { 32 | int fd; 33 | int events; 34 | int revents; 35 | }; 36 | 37 | using nfds_t = unsigned int; 38 | 39 | extern "C" int poll (pollfd *fds_, nfds_t nfds_, int timeout_); 40 | 41 | #define POLLIN (1 << 0) 42 | #define POLLPRI (1 << 1) 43 | #define POLLOUT (1 << 2) 44 | #define POLLERR (1 << 3) 45 | #define POLLHUP (1 << 4) 46 | #else 47 | #include 48 | #endif 49 | 50 | class Socket; 51 | using UniqueSocket = std::unique_ptr; 52 | using SharedSocket = std::shared_ptr; 53 | 54 | /// \brief Socket object 55 | class Socket 56 | { 57 | public: 58 | enum Type 59 | { 60 | eStream = SOCK_STREAM, ///< Stream socket 61 | eDatagram = SOCK_DGRAM, ///< Datagram socket 62 | }; 63 | 64 | /// \brief Poll info 65 | struct PollInfo 66 | { 67 | /// \brief Socket to poll 68 | std::reference_wrapper socket; 69 | 70 | /// \brief Input events 71 | int events; 72 | 73 | /// \brief Output events 74 | int revents; 75 | }; 76 | 77 | ~Socket (); 78 | 79 | /// \brief Accept connection 80 | UniqueSocket accept (); 81 | 82 | /// \brief Whether socket is at out-of-band mark 83 | int atMark (); 84 | 85 | /// \brief Bind socket to address 86 | /// \param addr_ Address to bind 87 | bool bind (SockAddr const &addr_); 88 | 89 | /// \brief Connect to a peer 90 | /// \param addr_ Peer address 91 | bool connect (SockAddr const &addr_); 92 | 93 | /// \brief Listen for connections 94 | /// \param backlog_ Queue size for incoming connections 95 | bool listen (int backlog_); 96 | 97 | /// \brief Shutdown socket 98 | /// \param how_ Type of shutdown (\sa ::shutdown) 99 | bool shutdown (int how_); 100 | 101 | /// \brief Set linger option 102 | /// \param enable_ Whether to enable linger 103 | /// \param time_ Linger timeout 104 | bool setLinger (bool enable_, std::chrono::seconds time_); 105 | 106 | /// \brief Set non-blocking 107 | /// \param nonBlocking_ Whether to set non-blocking 108 | bool setNonBlocking (bool nonBlocking_ = true); 109 | 110 | bool setWinScale (const int val); 111 | 112 | /// \brief Set reuse address in subsequent bind 113 | /// \param reuse_ Whether to reuse address 114 | bool setReuseAddress (bool reuse_ = true); 115 | 116 | /// \brief Set recv buffer size 117 | /// \param size_ Buffer size 118 | bool setRecvBufferSize (std::size_t size_); 119 | 120 | /// \brief Set send buffer size 121 | /// \param size_ Buffer size 122 | bool setSendBufferSize (std::size_t size_); 123 | 124 | #ifndef __NDS__ 125 | /// \brief Join multicast group 126 | /// \param addr_ Multicast group address 127 | /// \param iface_ Interface address 128 | bool joinMulticastGroup (SockAddr const &addr_, SockAddr const &iface_); 129 | 130 | /// \brief Drop multicast group 131 | /// \param addr_ Multicast group address 132 | /// \param iface_ Interface address 133 | bool dropMulticastGroup (SockAddr const &addr_, SockAddr const &iface_); 134 | #endif 135 | 136 | /// \brief Read data 137 | /// \param buffer_ Output buffer 138 | /// \param size_ Size to read 139 | /// \param oob_ Whether to read from out-of-band 140 | std::make_signed_t read (void *buffer_, std::size_t size_, bool oob_ = false); 141 | 142 | /// \brief Read data 143 | /// \param buffer_ Output buffer 144 | /// \param oob_ Whether to read from out-of-band 145 | std::make_signed_t read (IOBuffer &buffer_, bool oob_ = false); 146 | 147 | /// \brief Read data 148 | /// \param buffer_ Output buffer 149 | /// \param size_ Size to read 150 | /// \param[out] addr_ Source address 151 | std::make_signed_t readFrom (void *buffer_, std::size_t size_, SockAddr &addr_); 152 | 153 | /// \brief Write data 154 | /// \param buffer_ Input buffer 155 | /// \param size_ Size to write 156 | std::make_signed_t write (void const *buffer_, std::size_t size_); 157 | 158 | /// \brief Write data 159 | /// \param buffer_ Input buffer 160 | /// \param size_ Size to write 161 | std::make_signed_t write (IOBuffer &buffer_); 162 | 163 | /// \brief Write data 164 | /// \param buffer_ Input buffer 165 | /// \param size_ Size to write 166 | /// \param[out] addr_ Destination address 167 | std::make_signed_t 168 | writeTo (void const *buffer_, std::size_t size_, SockAddr const &addr_); 169 | 170 | /// \brief Local name 171 | SockAddr const &sockName () const; 172 | /// \brief Peer name 173 | SockAddr const &peerName () const; 174 | 175 | /// \brief Create socket 176 | /// \param type_ Socket type 177 | static UniqueSocket create (Type type_); 178 | 179 | /// \brief Poll sockets 180 | /// \param info_ Poll info 181 | /// \param count_ Number of poll entries 182 | /// \param timeout_ Poll timeout 183 | static int poll (PollInfo *info_, std::size_t count_, std::chrono::milliseconds timeout_); 184 | 185 | private: 186 | Socket () = delete; 187 | 188 | /// \brief Parameterized constructor 189 | /// \param fd_ Socket fd 190 | Socket (int fd_); 191 | 192 | /// \brief Parameterized constructor 193 | /// \param fd_ Socket fd 194 | /// \param sockName_ Local name 195 | /// \param peerName_ Peer name 196 | Socket (int fd_, SockAddr const &sockName_, SockAddr const &peerName_); 197 | 198 | Socket (Socket const &that_) = delete; 199 | 200 | Socket (Socket &&that_) = delete; 201 | 202 | Socket &operator= (Socket const &that_) = delete; 203 | 204 | Socket &operator= (Socket &&that_) = delete; 205 | 206 | /// \param Local name 207 | SockAddr m_sockName; 208 | /// \param Peer name 209 | SockAddr m_peerName; 210 | 211 | /// \param Socket fd 212 | int const m_fd; 213 | 214 | /// \param Whether listening 215 | bool m_listening : 1; 216 | 217 | /// \param Whether connected 218 | bool m_connected : 1; 219 | }; 220 | -------------------------------------------------------------------------------- /source/IOAbstraction.cpp: -------------------------------------------------------------------------------- 1 | #include "IOAbstraction.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class VirtualDirectory 10 | { 11 | public: 12 | VirtualDirectory (const std::vector &directories) 13 | { 14 | mDirectories.push_back ("."); 15 | mDirectories.push_back (".."); 16 | mDirectories.insert (mDirectories.end (), directories.begin (), directories.end ()); 17 | 18 | mCurIterator = mDirectories.begin (); 19 | } 20 | 21 | [[nodiscard]] const DIR *getAsDir () const 22 | { 23 | return &mDirPtr; 24 | } 25 | 26 | struct dirent *readdir () 27 | { 28 | if (mCurIterator == mDirectories.end ()) 29 | { 30 | return nullptr; 31 | } 32 | mDir = {}; 33 | snprintf (mDir.d_name, sizeof (mDir.d_name), "%s", mCurIterator->c_str ()); 34 | #ifdef _DIRENT_HAVE_D_STAT 35 | mDir.d_stat.st_mode = _IFDIR; 36 | #endif 37 | mCurIterator++; 38 | mDirPtr.position++; 39 | return &mDir; 40 | } 41 | 42 | private: 43 | DIR mDirPtr = {}; 44 | std::vector mDirectories; 45 | struct dirent mDir = {}; 46 | std::vector::iterator mCurIterator{}; 47 | }; 48 | 49 | std::vector> sOpenVirtualDirectories; 50 | std::mutex sOpenVirtualDirectoriesMutex; 51 | std::map> sVirtualDirs; 52 | 53 | template 54 | typename std::enable_if>::value, 55 | bool>::type 56 | remove_first_if (Container &container, Predicate pred) 57 | { 58 | auto it = container.begin (); 59 | while (it != container.end ()) 60 | { 61 | if (pred (*it)) 62 | { 63 | container.erase (it); 64 | return true; 65 | } 66 | ++it; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | template 73 | bool remove_locked_first_if (std::mutex &mutex, Container &container, Predicate pred) 74 | { 75 | std::lock_guard lock (mutex); 76 | return remove_first_if (container, pred); 77 | } 78 | 79 | static const DIR *getVirtualDir (const std::vector &subDirectories) 80 | { 81 | auto virtDir = std::make_unique (subDirectories); 82 | auto *result = virtDir->getAsDir (); 83 | std::lock_guard lock (sOpenVirtualDirectoriesMutex); 84 | sOpenVirtualDirectories.push_back (std::move (virtDir)); 85 | return result; 86 | } 87 | 88 | std::string IOAbstraction::convertPath (std::string_view inPath) 89 | { 90 | #ifdef __WIIU__ 91 | if (!inPath.starts_with ('/') || inPath.find (':') != std::string::npos) 92 | { 93 | return std::string (inPath); 94 | } 95 | std::string path = std::string (inPath); 96 | size_t secondSlashPos = path.find ('/', 1); 97 | if (secondSlashPos != std::string::npos) 98 | { 99 | // Extract the substring between the first and second slashes 100 | std::string prefix = path.substr (1, secondSlashPos - 1); 101 | std::string suffix = path.substr (secondSlashPos); 102 | 103 | // Concatenate the modified prefix and suffix 104 | path = prefix + ":" + suffix; 105 | } 106 | else 107 | { 108 | path = std::string (inPath.substr (1)) + ":/"; 109 | } 110 | return path; 111 | #else 112 | return std::string (inPath); 113 | #endif 114 | } 115 | 116 | int IOAbstraction::closedir (DIR *dirp) 117 | { 118 | { 119 | std::lock_guard lock (sOpenVirtualDirectoriesMutex); 120 | if (remove_locked_first_if (sOpenVirtualDirectoriesMutex, 121 | sOpenVirtualDirectories, 122 | [dirp] (auto &cur) { return cur->getAsDir () == dirp; })) 123 | { 124 | return 0; 125 | } 126 | } 127 | return ::closedir (dirp); 128 | } 129 | 130 | DIR *IOAbstraction::opendir (const char *dirname) 131 | { 132 | auto convertedPath = convertPath (dirname); 133 | auto *res = ::opendir (convertedPath.c_str ()); 134 | if (res == nullptr) 135 | { 136 | if (sVirtualDirs.count (convertedPath) > 0) 137 | { 138 | return (DIR *)getVirtualDir (sVirtualDirs[convertedPath]); 139 | } 140 | } 141 | return res; 142 | } 143 | 144 | FILE *IOAbstraction::fopen (const char *_name, const char *_type) 145 | { 146 | return std::fopen (convertPath (_name).c_str (), _type); 147 | } 148 | 149 | int IOAbstraction::fseek (FILE *f, long pos, int origin) 150 | { 151 | return std::fseek (f, pos, origin); 152 | } 153 | 154 | size_t IOAbstraction::fread (void *buffer, size_t _size, size_t _n, FILE *f) 155 | { 156 | return std::fread (buffer, _size, _n, f); 157 | } 158 | 159 | size_t IOAbstraction::fwrite (const void *buffer, size_t _size, size_t _n, FILE *f) 160 | { 161 | return std::fwrite (buffer, _size, _n, f); 162 | } 163 | 164 | struct dirent *IOAbstraction::readdir (DIR *dirp) 165 | { 166 | { 167 | std::lock_guard lock (sOpenVirtualDirectoriesMutex); 168 | auto itr = std::find_if (sOpenVirtualDirectories.begin (), 169 | sOpenVirtualDirectories.end (), 170 | [dirp] (auto &cur) { return cur->getAsDir () == dirp; }); 171 | if (itr != sOpenVirtualDirectories.end ()) 172 | { 173 | return (*itr)->readdir (); 174 | } 175 | } 176 | 177 | return ::readdir (dirp); 178 | } 179 | 180 | int IOAbstraction::stat (const char *path, struct stat *sbuf) 181 | { 182 | auto convertedPath = convertPath (path); 183 | auto r = ::stat (convertedPath.c_str (), sbuf); 184 | if (r < 0) 185 | { 186 | if (errno == EPERM) 187 | { 188 | auto *dir = ::opendir (convertedPath.c_str ()); 189 | if (dir) 190 | { 191 | *sbuf = {}; 192 | // TODO: init other values? 193 | sbuf->st_mode = _IFDIR; 194 | ::closedir (dir); 195 | return 0; 196 | } 197 | } 198 | if (sVirtualDirs.contains (convertedPath)) 199 | { 200 | *sbuf = {}; 201 | // TODO: init other values? 202 | sbuf->st_mode = _IFDIR; 203 | return 0; 204 | } 205 | } 206 | return r; 207 | } 208 | 209 | int IOAbstraction::lstat (const char *path, struct stat *buf) 210 | { 211 | return IOAbstraction::stat (path, buf); 212 | } 213 | 214 | void IOAbstraction::addVirtualPath (const std::string &virtualPath, 215 | const std::vector &subDirectories) 216 | { 217 | sVirtualDirs.insert (std::make_pair (virtualPath, subDirectories)); 218 | } 219 | 220 | void IOAbstraction::clear () 221 | { 222 | std::lock_guard lock (sOpenVirtualDirectoriesMutex); 223 | sOpenVirtualDirectories.clear (); 224 | sVirtualDirs.clear (); 225 | } 226 | 227 | int IOAbstraction::mkdir (const char *path, mode_t mode) 228 | { 229 | return ::mkdir (convertPath (path).c_str (), mode); 230 | } 231 | 232 | int IOAbstraction::rmdir (const char *path) 233 | { 234 | return ::rmdir (convertPath (path).c_str ()); 235 | } 236 | 237 | int IOAbstraction::unlink (const char *path) 238 | { 239 | return ::unlink (convertPath (path).c_str ()); 240 | } 241 | 242 | int IOAbstraction::rename (const char *path, const char *path2) 243 | { 244 | return ::rename (convertPath (path).c_str (), convertPath (path2).c_str ()); 245 | } 246 | -------------------------------------------------------------------------------- /source/fs.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "fs.h" 22 | #include "IOAbstraction.h" 23 | #include "ioBuffer.h" 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #if defined(__NDS__) || defined(__3DS__) || defined(__SWITCH__) || defined(__WIIU__) 43 | #define getline __getline 44 | #endif 45 | 46 | std::string fs::printSize (std::uint64_t const size_) 47 | { 48 | constexpr std::uint64_t const KiB = 1024; 49 | constexpr std::uint64_t const MiB = 1024 * KiB; 50 | constexpr std::uint64_t const GiB = 1024 * MiB; 51 | constexpr std::uint64_t const TiB = 1024 * GiB; 52 | constexpr std::uint64_t const PiB = 1024 * TiB; 53 | constexpr std::uint64_t const EiB = 1024 * PiB; 54 | 55 | std::array buffer{}; 56 | 57 | for (auto const &[name, bin] : { 58 | // clang-format off 59 | std::make_pair ("EiB", EiB), 60 | std::make_pair ("PiB", PiB), 61 | std::make_pair ("TiB", TiB), 62 | std::make_pair ("GiB", GiB), 63 | std::make_pair ("MiB", MiB), 64 | std::make_pair ("KiB", KiB) 65 | // clang-format on 66 | }) 67 | { 68 | // get the integral portion of the number 69 | auto const whole = size_ / bin; 70 | if (size_ >= 100 * bin) 71 | { 72 | // >= 100, print xxxXiB 73 | std::size_t const size = std::sprintf (buffer.data (), "%" PRIu64 "%s", whole, name); 74 | return {buffer.data (), size}; 75 | } 76 | 77 | // get the fractional portion of the number 78 | auto const frac = size_ - (whole * bin); 79 | if (size_ >= 10 * bin) 80 | { 81 | // >= 10, print xx.xXiB 82 | std::size_t const size = std::sprintf ( 83 | buffer.data (), "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name); 84 | return {buffer.data (), size}; 85 | } 86 | 87 | if (size_ >= 1000 * (bin / KiB)) 88 | { 89 | // >= 1000 of lesser bin, print x.xxXiB 90 | std::size_t const size = std::sprintf ( 91 | buffer.data (), "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name); 92 | return {buffer.data (), size}; 93 | } 94 | } 95 | 96 | // < 1KiB, just print the number 97 | std::size_t const size = std::sprintf (buffer.data (), "%" PRIu64 "B", size_); 98 | return {buffer.data (), size}; 99 | } 100 | 101 | /////////////////////////////////////////////////////////////////////////// 102 | fs::File::~File () 103 | { 104 | std::free (m_lineBuffer); 105 | } 106 | 107 | fs::File::File () = default; 108 | 109 | fs::File::File (File &&that_) = default; 110 | 111 | fs::File &fs::File::operator= (File &&that_) = default; 112 | 113 | fs::File::operator bool () const 114 | { 115 | return static_cast (m_fp); 116 | } 117 | 118 | fs::File::operator FILE * () const 119 | { 120 | return m_fp.get (); 121 | } 122 | 123 | void fs::File::setBufferSize (std::size_t const size_) 124 | { 125 | if (m_buffer.size () != size_) 126 | m_buffer.resize (size_); 127 | 128 | if (m_fp) 129 | (void)std::setvbuf (m_fp.get (), m_buffer.data (), _IOFBF, m_buffer.size ()); 130 | } 131 | 132 | bool fs::File::open (gsl::not_null const path_, 133 | gsl::not_null const mode_) 134 | { 135 | gsl::owner fp = IOAbstraction::fopen (path_, mode_); 136 | if (!fp) 137 | return false; 138 | 139 | m_fp = std::unique_ptr (fp, &std::fclose); 140 | 141 | if (!m_buffer.empty ()) 142 | (void)std::setvbuf (m_fp.get (), m_buffer.data (), _IOFBF, m_buffer.size ()); 143 | 144 | return true; 145 | } 146 | 147 | void fs::File::close () 148 | { 149 | m_fp.reset (); 150 | } 151 | 152 | std::make_signed_t fs::File::seek (std::make_signed_t const pos_, 153 | int const origin_) 154 | { 155 | return IOAbstraction::fseek (m_fp.get (), pos_, origin_); 156 | } 157 | 158 | std::make_signed_t fs::File::read (gsl::not_null const buffer_, 159 | std::size_t const size_) 160 | { 161 | assert (buffer_); 162 | assert (size_ > 0); 163 | 164 | auto const rc = IOAbstraction::fread (buffer_, 1, size_, m_fp.get ()); 165 | if (rc == 0) 166 | { 167 | if (std::feof (m_fp.get ())) 168 | return 0; 169 | return -1; 170 | } 171 | 172 | return gsl::narrow_cast> (rc); 173 | } 174 | 175 | std::make_signed_t fs::File::read (IOBuffer &buffer_) 176 | { 177 | assert (buffer_.freeSize () > 0); 178 | 179 | auto const rc = read (buffer_.freeArea (), buffer_.freeSize ()); 180 | if (rc > 0) 181 | buffer_.markUsed (rc); 182 | 183 | return rc; 184 | } 185 | 186 | std::string_view fs::File::readLine () 187 | { 188 | while (true) 189 | { 190 | auto rc = ::getline (&m_lineBuffer, &m_lineBufferSize, m_fp.get ()); 191 | if (rc < 0) 192 | return {}; 193 | 194 | while (rc > 0) 195 | { 196 | if (m_lineBuffer[rc - 1] != '\r' && m_lineBuffer[rc - 1] != '\n') 197 | break; 198 | 199 | m_lineBuffer[--rc] = 0; 200 | } 201 | 202 | if (rc > 0) 203 | return {m_lineBuffer, gsl::narrow_cast (rc)}; 204 | } 205 | } 206 | 207 | bool fs::File::readAll (gsl::not_null const buffer_, std::size_t const size_) 208 | { 209 | assert (buffer_); 210 | assert (size_ > 0); 211 | 212 | auto const p = static_cast (buffer_.get ()); 213 | 214 | std::size_t bytes = 0; 215 | while (bytes < size_) 216 | { 217 | auto const rc = read (p + bytes, size_ - bytes); 218 | if (rc <= 0) 219 | return false; 220 | 221 | bytes += rc; 222 | } 223 | 224 | return true; 225 | } 226 | 227 | std::make_signed_t fs::File::write (gsl::not_null const buffer_, 228 | std::size_t const size_) 229 | { 230 | assert (buffer_); 231 | assert (size_ > 0); 232 | 233 | auto const rc = IOAbstraction::fwrite (buffer_, 1, size_, m_fp.get ()); 234 | if (rc == 0) 235 | return -1; 236 | 237 | return gsl::narrow_cast> (rc); 238 | } 239 | 240 | std::make_signed_t fs::File::write (IOBuffer &buffer_) 241 | { 242 | assert (buffer_.usedSize () > 0); 243 | 244 | auto const rc = write (buffer_.usedArea (), buffer_.usedSize ()); 245 | if (rc > 0) 246 | buffer_.markFree (rc); 247 | 248 | return rc; 249 | } 250 | 251 | bool fs::File::writeAll (gsl::not_null const buffer_, std::size_t const size_) 252 | { 253 | assert (buffer_); 254 | assert (size_ > 0); 255 | 256 | auto const p = static_cast (buffer_.get ()); 257 | 258 | std::size_t bytes = 0; 259 | while (bytes < size_) 260 | { 261 | auto const rc = write (p + bytes, size_ - bytes); 262 | if (rc <= 0) 263 | return false; 264 | 265 | bytes += rc; 266 | } 267 | 268 | return true; 269 | } 270 | 271 | /////////////////////////////////////////////////////////////////////////// 272 | fs::Dir::~Dir () = default; 273 | 274 | fs::Dir::Dir () = default; 275 | 276 | fs::Dir::Dir (Dir &&that_) = default; 277 | 278 | fs::Dir &fs::Dir::operator= (Dir &&that_) = default; 279 | 280 | fs::Dir::operator bool () const 281 | { 282 | return static_cast (m_dp); 283 | } 284 | 285 | fs::Dir::operator DIR * () const 286 | { 287 | return m_dp.get (); 288 | } 289 | 290 | bool fs::Dir::open (gsl::not_null const path_) 291 | { 292 | auto const dp = IOAbstraction::opendir (path_); 293 | if (!dp) 294 | return false; 295 | 296 | m_dp = std::unique_ptr (dp, &IOAbstraction::closedir); 297 | return true; 298 | } 299 | 300 | void fs::Dir::close () 301 | { 302 | m_dp.reset (); 303 | } 304 | 305 | dirent *fs::Dir::read () 306 | { 307 | errno = 0; 308 | return IOAbstraction::readdir (m_dp.get ()); 309 | } 310 | -------------------------------------------------------------------------------- /source/ftpConfig.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "ftpConfig.h" 22 | 23 | #include "fs.h" 24 | #include "log.h" 25 | #include "platform.h" 26 | 27 | #include 28 | 29 | #include 30 | using stat_t = struct stat; 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | namespace 44 | { 45 | #if defined(__WIIU__) && defined(__WUPS__) 46 | constexpr std::uint16_t DEFAULT_PORT = 21; 47 | #else 48 | constexpr std::uint16_t DEFAULT_PORT = 5000; 49 | #endif 50 | 51 | bool mkdirParent (std::string_view const path_) 52 | { 53 | auto pos = path_.find_first_of ('/'); 54 | while (pos != std::string::npos) 55 | { 56 | auto const dir = std::string (path_.substr (0, pos)); 57 | 58 | stat_t st{}; 59 | auto const rc = ::stat (dir.c_str (), &st); 60 | if (rc < 0 && errno != ENOENT) 61 | return false; 62 | 63 | if (rc < 0 && errno == ENOENT) 64 | { 65 | auto const rc = ::mkdir (dir.c_str (), 0755); 66 | if (rc < 0) 67 | return false; 68 | } 69 | 70 | pos = path_.find_first_of ('/', pos + 1); 71 | } 72 | 73 | return true; 74 | } 75 | 76 | std::string_view strip (std::string_view const str_) 77 | { 78 | auto const start = str_.find_first_not_of (" \t"); 79 | if (start == std::string::npos) 80 | return {}; 81 | 82 | auto const end = str_.find_last_not_of (" \t"); 83 | if (end == std::string::npos) 84 | return str_.substr (start); 85 | 86 | return str_.substr (start, end + 1 - start); 87 | } 88 | 89 | template 90 | bool parseInt (T &out_, std::string_view const val_) 91 | { 92 | auto const rc = std::from_chars (val_.data (), val_.data () + val_.size (), out_); 93 | if (rc.ec != std::errc{}) 94 | { 95 | errno = static_cast (rc.ec); 96 | return false; 97 | } 98 | 99 | if (rc.ptr != val_.data () + val_.size ()) 100 | { 101 | errno = EINVAL; 102 | return false; 103 | } 104 | 105 | return true; 106 | } 107 | } 108 | 109 | /////////////////////////////////////////////////////////////////////////// 110 | FtpConfig::~FtpConfig () = default; 111 | 112 | FtpConfig::FtpConfig () : m_port (DEFAULT_PORT) 113 | { 114 | } 115 | 116 | UniqueFtpConfig FtpConfig::create () 117 | { 118 | return UniqueFtpConfig (new FtpConfig ()); 119 | } 120 | 121 | UniqueFtpConfig FtpConfig::load (gsl::not_null const path_) 122 | { 123 | auto config = create (); 124 | 125 | auto fp = fs::File (); 126 | if (!fp.open (path_)) 127 | return config; 128 | 129 | std::uint16_t port = DEFAULT_PORT; 130 | 131 | std::string line; 132 | while (!(line = fp.readLine ()).empty ()) 133 | { 134 | auto const pos = line.find_first_of ('='); 135 | if (pos == std::string::npos) 136 | { 137 | error ("Ignoring '%s'\n", line.c_str ()); 138 | continue; 139 | } 140 | 141 | auto const key = strip (std::string_view (line).substr (0, pos)); 142 | auto const val = strip (std::string_view (line).substr (pos + 1)); 143 | if (key.empty () || val.empty ()) 144 | { 145 | error ("Ignoring '%s'\n", line.c_str ()); 146 | continue; 147 | } 148 | 149 | if (key == "user") 150 | config->m_user = val; 151 | else if (key == "pass") 152 | config->m_pass = val; 153 | else if (key == "port") 154 | parseInt (port, val); 155 | #ifdef __3DS__ 156 | else if (key == "mtime") 157 | { 158 | if (val == "0") 159 | config->m_getMTime = false; 160 | else if (val == "1") 161 | config->m_getMTime = true; 162 | else 163 | error ("Invalid value for mtime: %.*s\n", 164 | gsl::narrow_cast (val.size ()), 165 | val.data ()); 166 | } 167 | #endif 168 | #ifdef __SWITCH__ 169 | else if (key == "ap") 170 | { 171 | if (val == "0") 172 | config->m_enableAP = false; 173 | else if (val == "1") 174 | config->m_enableAP = true; 175 | else 176 | error ("Invalid value for ap: %.*s\n", 177 | gsl::narrow_cast (val.size ()), 178 | val.data ()); 179 | } 180 | else if (key == "ssid") 181 | config->m_ssid = val; 182 | else if (key == "passphrase") 183 | config->m_passphrase = val; 184 | #endif 185 | } 186 | 187 | config->setPort (port); 188 | 189 | return config; 190 | } 191 | 192 | #ifndef __NDS__ 193 | std::scoped_lock FtpConfig::lockGuard () 194 | { 195 | return std::scoped_lock (m_lock); 196 | } 197 | #endif 198 | 199 | bool FtpConfig::save (gsl::not_null const path_) 200 | { 201 | if (!mkdirParent (path_.get ())) 202 | return false; 203 | 204 | auto fp = fs::File (); 205 | if (!fp.open (path_, "wb")) 206 | return false; 207 | 208 | if (!m_user.empty ()) 209 | (void)std::fprintf (fp, "user=%s\n", m_user.c_str ()); 210 | if (!m_pass.empty ()) 211 | (void)std::fprintf (fp, "pass=%s\n", m_pass.c_str ()); 212 | (void)std::fprintf (fp, "port=%u\n", m_port); 213 | 214 | #ifdef __3DS__ 215 | (void)std::fprintf (fp, "mtime=%u\n", m_getMTime); 216 | #endif 217 | 218 | #ifdef __SWITCH__ 219 | (void)std::fprintf (fp, "ap=%u\n", m_enableAP); 220 | if (!m_ssid.empty ()) 221 | (void)std::fprintf (fp, "ssid=%s\n", m_ssid.c_str ()); 222 | if (!m_passphrase.empty ()) 223 | (void)std::fprintf (fp, "passphrase=%s\n", m_passphrase.c_str ()); 224 | #endif 225 | 226 | return true; 227 | } 228 | 229 | std::string const &FtpConfig::user () const 230 | { 231 | return m_user; 232 | } 233 | 234 | std::string const &FtpConfig::pass () const 235 | { 236 | return m_pass; 237 | } 238 | 239 | std::string const &FtpConfig::hostname () const 240 | { 241 | return m_hostname; 242 | } 243 | 244 | std::uint16_t FtpConfig::port () const 245 | { 246 | return m_port; 247 | } 248 | 249 | #ifdef __3DS__ 250 | bool FtpConfig::getMTime () const 251 | { 252 | return m_getMTime; 253 | } 254 | #endif 255 | 256 | #ifdef __SWITCH__ 257 | bool FtpConfig::enableAP () const 258 | { 259 | return m_enableAP; 260 | } 261 | 262 | std::string const &FtpConfig::ssid () const 263 | { 264 | return m_ssid; 265 | } 266 | 267 | std::string const &FtpConfig::passphrase () const 268 | { 269 | return m_passphrase; 270 | } 271 | #endif 272 | 273 | void FtpConfig::setUser (std::string user_) 274 | { 275 | m_user = std::move (user_); 276 | } 277 | 278 | void FtpConfig::setPass (std::string pass_) 279 | { 280 | m_pass = std::move (pass_); 281 | } 282 | 283 | void FtpConfig::setHostname (std::string hostname_) 284 | { 285 | m_hostname = std::move (hostname_); 286 | } 287 | 288 | bool FtpConfig::setPort (std::string_view const port_) 289 | { 290 | std::uint16_t parsed{}; 291 | if (!parseInt (parsed, port_)) 292 | return false; 293 | 294 | return setPort (parsed); 295 | } 296 | 297 | bool FtpConfig::setPort (std::uint16_t const port_) 298 | { 299 | #ifdef __SWITCH__ 300 | // Switch is not allowed < 1024, except 0 301 | if (port_ < 1024 && port_ != 0) 302 | { 303 | errno = EPERM; 304 | return false; 305 | } 306 | #elif defined(__NDS__) || defined(__3DS__) 307 | // 3DS is allowed < 1024, but not 0 308 | // NDS is allowed < 1024, but 0 crashes the app 309 | if (port_ == 0) 310 | { 311 | errno = EPERM; 312 | return false; 313 | } 314 | #endif 315 | 316 | m_port = port_; 317 | return true; 318 | } 319 | 320 | #ifdef __3DS__ 321 | void FtpConfig::setGetMTime (bool const getMTime_) 322 | { 323 | m_getMTime = getMTime_; 324 | } 325 | #endif 326 | 327 | #ifdef __SWITCH__ 328 | void FtpConfig::setEnableAP (bool const enable_) 329 | { 330 | m_enableAP = enable_; 331 | } 332 | 333 | void FtpConfig::setSSID (std::string_view const ssid_) 334 | { 335 | m_ssid = ssid_.substr (0, ssid_.find_first_of ('\0')); 336 | } 337 | 338 | void FtpConfig::setPassphrase (std::string_view const passphrase_) 339 | { 340 | m_passphrase = passphrase_.substr (0, passphrase_.find_first_of ('\0')); 341 | } 342 | #endif 343 | -------------------------------------------------------------------------------- /source/ioBuffer.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2020 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "ioBuffer.h" 22 | 23 | #include 24 | #include 25 | 26 | /////////////////////////////////////////////////////////////////////////// 27 | IOBuffer::~IOBuffer () = default; 28 | 29 | IOBuffer::IOBuffer (std::size_t const size_) 30 | : m_buffer (std::make_unique (size_)), m_size (size_) 31 | { 32 | assert (size_ > 0); 33 | } 34 | 35 | char *IOBuffer::freeArea () const 36 | { 37 | assert (m_end < m_size); 38 | return &m_buffer[m_end]; 39 | } 40 | 41 | std::size_t IOBuffer::freeSize () const 42 | { 43 | assert (m_size >= m_end); 44 | return m_size - m_end; 45 | } 46 | 47 | void IOBuffer::markFree (std::size_t size_) 48 | { 49 | assert (m_end >= m_start); 50 | assert (m_end - m_start >= size_); 51 | m_start += size_; 52 | 53 | // reset back to beginning 54 | if (m_start == m_end) 55 | { 56 | m_start = 0; 57 | m_end = 0; 58 | } 59 | } 60 | 61 | char *IOBuffer::usedArea () const 62 | { 63 | assert (m_start < m_size); 64 | return &m_buffer[m_start]; 65 | } 66 | 67 | std::size_t IOBuffer::usedSize () const 68 | { 69 | assert (m_end >= m_start); 70 | return m_end - m_start; 71 | } 72 | 73 | void IOBuffer::markUsed (std::size_t size_) 74 | { 75 | assert (m_size >= m_end); 76 | assert (m_size - m_end >= size_); 77 | m_end += size_; 78 | } 79 | 80 | bool IOBuffer::empty () const 81 | { 82 | assert (m_end >= m_start); 83 | return (m_end - m_start) == 0; 84 | } 85 | 86 | std::size_t IOBuffer::capacity () const 87 | { 88 | return m_size; 89 | } 90 | 91 | void IOBuffer::clear () 92 | { 93 | m_start = 0; 94 | m_end = 0; 95 | } 96 | 97 | void IOBuffer::coalesce () 98 | { 99 | assert (m_size >= m_end); 100 | assert (m_end >= m_start); 101 | 102 | auto const size = m_end - m_start; 103 | if (size != 0) 104 | std::memmove (&m_buffer[0], &m_buffer[m_start], size); 105 | 106 | m_end -= m_start; 107 | m_start = 0; 108 | } 109 | -------------------------------------------------------------------------------- /source/log.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "log.h" 22 | 23 | #include "platform.h" 24 | 25 | #if !defined(__WIIU__) && !defined(CLASSIC) 26 | #include "imgui.h" 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #ifdef __WIIU__ 34 | #include 35 | #endif 36 | 37 | namespace 38 | { 39 | #ifdef __3DS__ 40 | /// \brief Maximum number of log messages to keep 41 | constexpr auto MAX_LOGS = 250; 42 | #else 43 | /// \brief Maximum number of log messages to keep 44 | constexpr auto MAX_LOGS = 100; 45 | #endif 46 | 47 | #ifdef CLASSIC 48 | bool s_logUpdated = true; 49 | #endif 50 | 51 | /// \brief Message prefix 52 | static char const *const s_prefix[] = { 53 | [DEBUGLOG] = "[DEBUG]", 54 | [INFO] = "[INFO]", 55 | [ERROR] = "[ERROR]", 56 | [COMMAND] = "[COMMAND]", 57 | [RESPONSE] = "[RESPONSE]", 58 | }; 59 | 60 | /// \brief Log message 61 | struct Message 62 | { 63 | /// \brief Parameterized constructor 64 | /// \param level_ Log level 65 | /// \param message_ Log message 66 | Message (LogLevel const level_, std::string message_) 67 | : level (level_), message (std::move (message_)) 68 | { 69 | } 70 | 71 | /// \brief Log level 72 | LogLevel level; 73 | /// \brief Log message 74 | std::string message; 75 | }; 76 | 77 | /// \brief Log messages 78 | std::vector s_messages; 79 | 80 | #ifndef __NDS__ 81 | /// \brief Log lock 82 | platform::Mutex s_lock; 83 | #endif 84 | } 85 | 86 | void drawLog () 87 | { 88 | #ifndef __NDS__ 89 | auto const lock = std::scoped_lock (s_lock); 90 | #endif 91 | 92 | #ifdef CLASSIC 93 | if (!s_logUpdated) 94 | return; 95 | 96 | s_logUpdated = false; 97 | #endif 98 | 99 | auto const maxLogs = 100 | #if defined(CLASSIC) && !defined(__WIIU__) 101 | g_logConsole.windowHeight; 102 | #else 103 | MAX_LOGS; 104 | #endif 105 | 106 | if (s_messages.size () > static_cast (maxLogs)) 107 | { 108 | auto const begin = std::begin (s_messages); 109 | auto const end = std::next (begin, s_messages.size () - maxLogs); 110 | s_messages.erase (begin, end); 111 | } 112 | 113 | #ifdef CLASSIC 114 | char const *const s_colors[] = { 115 | [DEBUGLOG] = "\x1b[33;1m", // yellow 116 | [INFO] = "\x1b[37;1m", // white 117 | [ERROR] = "\x1b[31;1m", // red 118 | [COMMAND] = "\x1b[32;1m", // green 119 | [RESPONSE] = "\x1b[36;1m", // cyan 120 | }; 121 | 122 | #ifdef __WIIU__ 123 | for (auto &cur : s_messages) 124 | { 125 | OSReport ("ftpiiu plugin: %s %s\x1b[0m", s_colors[cur.level], cur.message.c_str ()); 126 | } 127 | #else 128 | auto it = std::begin (s_messages); 129 | if (s_messages.size () > static_cast (g_logConsole.windowHeight)) 130 | it = std::next (it, s_messages.size () - g_logConsole.windowHeight); 131 | 132 | consoleSelect (&g_logConsole); 133 | while (it != std::end (s_messages)) 134 | { 135 | std::fputs (s_colors[it->level], stdout); 136 | std::fputs (it->message.c_str (), stdout); 137 | ++it; 138 | } 139 | std::fflush (stdout); 140 | #endif 141 | s_messages.clear (); 142 | #else 143 | ImVec4 const s_colors[] = { 144 | [DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f), // yellow 145 | [INFO] = ImGui::GetStyleColorVec4 (ImGuiCol_Text), // normal 146 | [ERROR] = ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), // red 147 | [COMMAND] = ImVec4 (0.4f, 1.0f, 0.4f, 1.0f), // green 148 | [RESPONSE] = ImVec4 (0.4f, 1.0f, 1.0f, 1.0f), // cyan 149 | }; 150 | 151 | for (auto const &message : s_messages) 152 | { 153 | ImGui::PushStyleColor (ImGuiCol_Text, s_colors[message.level]); 154 | ImGui::TextUnformatted (s_prefix[message.level]); 155 | ImGui::SameLine (); 156 | ImGui::TextUnformatted (message.message.c_str ()); 157 | ImGui::PopStyleColor (); 158 | } 159 | 160 | // auto-scroll if scroll bar is at end 161 | if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ()) 162 | ImGui::SetScrollHereY (1.0f); 163 | #endif 164 | } 165 | 166 | #ifndef CLASSIC 167 | std::string getLog () 168 | { 169 | #ifndef __NDS__ 170 | auto const lock = std::scoped_lock (s_lock); 171 | #endif 172 | 173 | if (s_messages.empty ()) 174 | return {}; 175 | 176 | std::vector stack; 177 | stack.reserve (s_messages.size ()); 178 | 179 | std::size_t size = 0; 180 | for (auto const &msg : s_messages | std::views::reverse) 181 | { 182 | if (size + msg.message.size () > 1024 * 1024) 183 | break; 184 | 185 | size += msg.message.size (); 186 | stack.emplace_back (&msg); 187 | } 188 | 189 | std::string log; 190 | log.reserve (size); 191 | 192 | for (auto const &msg : stack | std::views::reverse) 193 | log += msg->message; 194 | 195 | return log; 196 | } 197 | #endif 198 | 199 | void debug (char const *const fmt_, ...) 200 | { 201 | #ifndef NDEBUG 202 | va_list ap; 203 | 204 | va_start (ap, fmt_); 205 | addLog (DEBUGLOG, fmt_, ap); 206 | va_end (ap); 207 | #else 208 | (void)fmt_; 209 | #endif 210 | } 211 | 212 | void info (char const *const fmt_, ...) 213 | { 214 | va_list ap; 215 | 216 | va_start (ap, fmt_); 217 | addLog (INFO, fmt_, ap); 218 | va_end (ap); 219 | } 220 | 221 | void error (char const *const fmt_, ...) 222 | { 223 | va_list ap; 224 | 225 | va_start (ap, fmt_); 226 | addLog (ERROR, fmt_, ap); 227 | va_end (ap); 228 | } 229 | 230 | void command (char const *const fmt_, ...) 231 | { 232 | va_list ap; 233 | 234 | va_start (ap, fmt_); 235 | addLog (COMMAND, fmt_, ap); 236 | va_end (ap); 237 | } 238 | 239 | void response (char const *const fmt_, ...) 240 | { 241 | va_list ap; 242 | 243 | va_start (ap, fmt_); 244 | addLog (RESPONSE, fmt_, ap); 245 | va_end (ap); 246 | } 247 | 248 | void addLog (LogLevel const level_, char const *const fmt_, va_list ap_) 249 | { 250 | #ifdef __WIIU__ 251 | // the plugin is currently never calling drawLogs 252 | return; 253 | #endif 254 | #ifdef NDEBUG 255 | if (level_ == DEBUGLOG) 256 | return; 257 | #endif 258 | 259 | #ifndef __NDS__ 260 | auto const lock = std::scoped_lock (s_lock); 261 | #endif 262 | 263 | #if !defined(NDS) && !defined(__WIIU__) 264 | thread_local 265 | #endif 266 | #if !defined(__WIIU__) 267 | static 268 | #endif 269 | char buffer[1024]; 270 | 271 | std::vsnprintf (buffer, sizeof (buffer), fmt_, ap_); 272 | 273 | #ifndef NDEBUG 274 | // std::fprintf (stderr, "%s", s_prefix[level_]); 275 | // std::fputs (buffer, stderr); 276 | #endif 277 | s_messages.emplace_back (level_, buffer); 278 | #ifdef CLASSIC 279 | s_logUpdated = true; 280 | #endif 281 | } 282 | 283 | void addLog (LogLevel const level_, std::string_view const message_) 284 | { 285 | #ifdef __WIIU__ 286 | // the plugin is currently never calling drawLogs 287 | return; 288 | #endif 289 | #ifdef NDEBUG 290 | if (level_ == DEBUGLOG) 291 | return; 292 | #endif 293 | 294 | auto msg = std::string (message_); 295 | for (auto &c : msg) 296 | { 297 | // replace nul-characters with ? to avoid truncation 298 | if (c == '\0') 299 | c = '?'; 300 | } 301 | 302 | #ifndef __NDS__ 303 | auto const lock = std::scoped_lock (s_lock); 304 | #endif 305 | #ifndef NDEBUG 306 | // std::fprintf (stderr, "%s", s_prefix[level_]); 307 | // std::fwrite (msg.data (), 1, msg.size (), stderr); 308 | #endif 309 | s_messages.emplace_back (level_, msg); 310 | #ifdef CLASSIC 311 | s_logUpdated = true; 312 | #endif 313 | } 314 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "platform.h" 22 | 23 | #include "ftpServer.h" 24 | #include "log.h" 25 | #ifndef __WIIU__ 26 | #include "imgui.h" 27 | #endif 28 | 29 | #ifndef CLASSIC 30 | #include 31 | 32 | #include 33 | #endif 34 | 35 | #include 36 | #include 37 | 38 | int main () 39 | { 40 | #ifndef CLASSIC 41 | curl_global_init (CURL_GLOBAL_ALL); 42 | IMGUI_CHECKVERSION (); 43 | ImGui::CreateContext (); 44 | #endif 45 | 46 | if (!platform::init ()) 47 | { 48 | #ifndef CLASSIC 49 | ImGui::DestroyContext (); 50 | #endif 51 | return EXIT_FAILURE; 52 | } 53 | 54 | #ifndef CLASSIC 55 | auto &style = ImGui::GetStyle (); 56 | 57 | // turn off window rounding 58 | style.WindowRounding = 0.0f; 59 | #endif 60 | 61 | auto server = FtpServer::create (); 62 | 63 | while (!server->quit () && platform::loop ()) 64 | { 65 | #ifndef NO_CONSOLE 66 | server->draw (); 67 | platform::render (); 68 | #else 69 | drawLog (); 70 | #endif 71 | } 72 | 73 | // clean up resources before exiting switch/3ds services 74 | server.reset (); 75 | 76 | platform::exit (); 77 | 78 | #ifndef CLASSIC 79 | ImGui::DestroyContext (); 80 | curl_global_cleanup (); 81 | #endif 82 | } 83 | -------------------------------------------------------------------------------- /source/mdns.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://datatracker.ietf.org/doc/html/rfc959) 3 | // - RFC 3659 (https://datatracker.ietf.org/doc/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // ftpd implements mdns based on the following: 7 | // - RFC 1035 (https://datatracker.ietf.org/doc/html/rfc1035) 8 | // - RFC 6762 (https://datatracker.ietf.org/doc/html/rfc6762) 9 | // 10 | // Copyright (C) 2024 Michael Theall 11 | // 12 | // This program is free software: you can redistribute it and/or modify 13 | // it under the terms of the GNU General Public License as published by 14 | // the Free Software Foundation, either version 3 of the License, or 15 | // (at your option) any later version. 16 | // 17 | // This program is distributed in the hope that it will be useful, 18 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | // GNU General Public License for more details. 21 | // 22 | // You should have received a copy of the GNU General Public License 23 | // along with this program. If not, see . 24 | 25 | #include "mdns.h" 26 | 27 | #include "log.h" 28 | #include "platform.h" 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | using namespace std::chrono_literals; 43 | 44 | static_assert ( 45 | std::endian::native == std::endian::big || std::endian::native == std::endian::little); 46 | 47 | static_assert (sizeof (in_addr_t) == 4); 48 | 49 | namespace 50 | { 51 | constexpr auto MDNS_TTL = 120; 52 | 53 | SockAddr const s_multicastAddress{inet_addr ("224.0.0.251"), 5353}; 54 | 55 | platform::steady_clock::time_point s_lastAnnounce{}; 56 | platform::steady_clock::time_point s_lastProbe{}; 57 | 58 | std::string s_hostname = platform::hostname (); 59 | std::string s_hostnameLocal = s_hostname + ".local"; 60 | 61 | enum class State 62 | { 63 | Probe1, 64 | Probe2, 65 | Probe3, 66 | Announce1, 67 | Announce2, 68 | Complete, 69 | Conflict, 70 | }; 71 | 72 | auto s_state = State::Probe1; 73 | 74 | #if __has_cpp_attribute(__cpp_lib_byteswap) 75 | template 76 | using byteswap = std::byteswap; 77 | #else 78 | template 79 | constexpr T byteswap (T const value_) noexcept 80 | { 81 | static_assert (std::has_unique_object_representations_v, "T may not have padding bits"); 82 | auto buffer = std::bit_cast> (value_); 83 | std::ranges::reverse (buffer); 84 | return std::bit_cast (buffer); 85 | } 86 | #endif 87 | 88 | template 89 | constexpr T hton (T const value_) noexcept 90 | { 91 | if constexpr (std::endian::native == std::endian::big) 92 | return value_; 93 | else 94 | return byteswap (value_); 95 | } 96 | 97 | template 98 | constexpr T ntoh (T const value_) noexcept 99 | { 100 | if constexpr (std::endian::native == std::endian::big) 101 | return value_; 102 | else 103 | return byteswap (value_); 104 | } 105 | 106 | template 107 | void const *decode (void const *const buffer_, U &size_, T &out_, bool networkToHost_ = true) 108 | { 109 | if (!buffer_) 110 | return nullptr; 111 | 112 | if (size_ < 0 || static_cast> (size_) < sizeof (T)) 113 | return nullptr; 114 | 115 | std::memcpy (&out_, buffer_, sizeof (T)); 116 | 117 | if (networkToHost_) 118 | out_ = ntoh (out_); 119 | 120 | size_ -= sizeof (T); 121 | return static_cast (buffer_) + sizeof (T); 122 | } 123 | 124 | template 125 | void const *decode (void const *buffer_, T &size_, std::string &out_) 126 | { 127 | auto p = static_cast (buffer_); 128 | auto const end = p + size_; 129 | 130 | std::string result; 131 | result.reserve (size_); 132 | 133 | while (p < end && *p) 134 | { 135 | auto const len = *p++; 136 | 137 | // punt on compressed labels 138 | if (len & 0xC0) 139 | return nullptr; 140 | 141 | if (p + len >= end) 142 | return nullptr; 143 | 144 | if (!result.empty ()) 145 | result.push_back ('.'); 146 | 147 | result.insert (std::end (result), p, p + len); 148 | p += len; 149 | } 150 | 151 | ++p; 152 | 153 | out_ = std::move (result); 154 | 155 | size_ = end - p; 156 | return p; 157 | } 158 | 159 | template 160 | void *encode (void *const buffer_, U &size_, T in_, bool hostToNetwork_ = true) 161 | { 162 | if (!buffer_) 163 | return nullptr; 164 | 165 | if (size_ < sizeof (T)) 166 | return nullptr; 167 | 168 | if (hostToNetwork_) 169 | in_ = hton (in_); 170 | 171 | std::memcpy (buffer_, &in_, sizeof (T)); 172 | 173 | size_ -= sizeof (T); 174 | return static_cast (buffer_) + sizeof (T); 175 | } 176 | 177 | template 178 | void *encode (void *const buffer_, T &size_, std::string const &in_) 179 | { 180 | // names are limited to 255 bytes 181 | if (in_.size () > 0xFF) 182 | return nullptr; 183 | 184 | auto p = static_cast (buffer_); 185 | auto const end = p + size_; 186 | 187 | std::string::size_type prev = 0; 188 | std::string::size_type pos = 0; 189 | while (p < end && pos != std::string::npos) 190 | { 191 | pos = in_.find ('.', prev); 192 | 193 | auto const label = std::string_view (in_).substr (prev, pos); 194 | 195 | // labels are limited to 63 bytes 196 | if (label.size () >= size_ || label.size () > 0x3F) 197 | return nullptr; 198 | 199 | p = static_cast (encode (p, size_, label.size ())); 200 | if (!p) 201 | return nullptr; 202 | 203 | std::memcpy (p, label.data (), label.size ()); 204 | 205 | p += label.size (); 206 | 207 | if (pos != std::string::npos) 208 | prev = pos + 1; 209 | } 210 | 211 | if (p == end) 212 | return nullptr; 213 | 214 | *p++ = 0; 215 | 216 | size_ = end - p; 217 | return p; 218 | } 219 | 220 | struct DNSHeader 221 | { 222 | std::uint16_t id{}; 223 | std::uint16_t flags{}; 224 | std::uint16_t qdCount{}; 225 | std::uint16_t anCount{}; 226 | std::uint16_t nsCount{}; 227 | std::uint16_t arCount{}; 228 | 229 | template 230 | void const *decode (void const *const buffer_, T &size_) 231 | { 232 | auto in = ::decode (buffer_, size_, id); 233 | in = ::decode (buffer_, size_, flags); 234 | in = ::decode (buffer_, size_, qdCount); 235 | in = ::decode (buffer_, size_, anCount); 236 | in = ::decode (buffer_, size_, nsCount); 237 | in = ::decode (buffer_, size_, arCount); 238 | 239 | return buffer_; 240 | } 241 | 242 | template 243 | void *encode (void *buffer_, T &size_) 244 | { 245 | buffer_ = ::encode (buffer_, size_, id); 246 | buffer_ = ::encode (buffer_, size_, flags); 247 | buffer_ = ::encode (buffer_, size_, qdCount); 248 | buffer_ = ::encode (buffer_, size_, anCount); 249 | buffer_ = ::encode (buffer_, size_, nsCount); 250 | buffer_ = ::encode (buffer_, size_, arCount); 251 | 252 | return buffer_; 253 | } 254 | }; 255 | 256 | struct QueryRecord 257 | { 258 | std::string qname{}; 259 | std::uint16_t qtype{}; 260 | std::uint16_t qclass{}; 261 | 262 | template 263 | void const *decode (void const *buffer_, T &size_) 264 | { 265 | buffer_ = ::decode (buffer_, size_, qname); 266 | buffer_ = ::decode (buffer_, size_, qtype); 267 | buffer_ = ::decode (buffer_, size_, qclass); 268 | 269 | return buffer_; 270 | } 271 | 272 | template 273 | void *encode (void *buffer_, T &size_) 274 | { 275 | buffer_ = ::encode (buffer_, size_, qname); 276 | buffer_ = ::encode (buffer_, size_, qtype); 277 | buffer_ = ::encode (buffer_, size_, qclass); 278 | 279 | return buffer_; 280 | } 281 | }; 282 | 283 | struct ResourceRecord 284 | { 285 | std::string rname{}; 286 | std::uint16_t rtype{}; 287 | std::uint16_t rclass{}; 288 | std::uint32_t rttl{}; 289 | std::uint16_t rlen{}; 290 | std::vector rdata{}; 291 | 292 | template 293 | void const *decode (void const *buffer_, T &size_) 294 | { 295 | buffer_ = ::decode (buffer_, size_, rname); 296 | buffer_ = ::decode (buffer_, size_, rtype); 297 | buffer_ = ::decode (buffer_, size_, rclass); 298 | buffer_ = ::decode (buffer_, size_, rttl); 299 | buffer_ = ::decode (buffer_, size_, rlen); 300 | 301 | return buffer_; 302 | } 303 | 304 | template 305 | void *encode (void *buffer_, T &size_) 306 | { 307 | if (rttl > std::numeric_limits::max ()) 308 | return nullptr; 309 | 310 | buffer_ = ::encode (buffer_, size_, rname); 311 | buffer_ = ::encode (buffer_, size_, rtype); 312 | buffer_ = ::encode (buffer_, size_, rclass); 313 | buffer_ = ::encode (buffer_, size_, rttl); 314 | buffer_ = ::encode (buffer_, size_, rlen); 315 | 316 | if (rlen > size_) 317 | return nullptr; 318 | 319 | rdata.resize (rlen); 320 | std::memcpy (rdata.data (), buffer_, rlen); 321 | 322 | size_ -= rlen; 323 | return static_cast (buffer_) + rlen; 324 | } 325 | }; 326 | 327 | void probe (Socket *const socket_, std::string const &qname_) 328 | { 329 | std::vector response (65536); 330 | auto available = response.size (); 331 | 332 | auto out = DNSHeader{.qdCount = 1}.encode (response.data (), available); 333 | out = QueryRecord{.qname = qname_, .qtype = 255, .qclass = 1}.encode (out, available); 334 | 335 | if (!out) 336 | return; 337 | 338 | info ("Probe mDNS %s\n", qname_.c_str ()); 339 | 340 | socket_->writeTo (response.data (), response.size () - available, s_multicastAddress); 341 | s_lastProbe = platform::steady_clock::now (); 342 | } 343 | 344 | void announce (Socket *const socket_, 345 | SockAddr const *srcAddr_, 346 | std::uint16_t const id_, 347 | std::uint16_t const flags_, 348 | QueryRecord const &record_, 349 | SockAddr const &addr_) 350 | { 351 | std::vector response (65536); 352 | auto available = response.size (); 353 | 354 | // header 355 | auto out = encode (response.data (), available, id_); 356 | out = 357 | encode (out, available, flags_ | (1 << 15) | (1 << 10)); // mark response/AA 358 | out = encode (out, available, 0); 359 | out = encode (out, available, 1); 360 | out = encode (out, available, 0); 361 | out = encode (out, available, 0); 362 | 363 | // answer section 364 | out = encode (out, available, record_.qname); 365 | out = encode (out, available, record_.qtype); 366 | out = encode (out, available, record_.qclass | (1 << 15)); // mark unique/flush 367 | out = encode (out, available, MDNS_TTL); 368 | out = encode (out, available, sizeof (in_addr_t)); 369 | out = encode ( 370 | out, available, static_cast (addr_).sin_addr.s_addr, false); 371 | 372 | if (!out) 373 | return; 374 | 375 | auto const preferUnicast = srcAddr_ && ((record_.qclass >> 15) & 0x1); 376 | 377 | if (preferUnicast) 378 | { 379 | auto const name = std::string (addr_.name ()); 380 | info ( 381 | "Respond mDNS %s %s to %s\n", record_.qname.c_str (), name.c_str (), srcAddr_->name ()); 382 | socket_->writeTo (response.data (), response.size () - available, *srcAddr_); 383 | } 384 | 385 | auto const now = platform::steady_clock::now (); 386 | if (!preferUnicast || now - s_lastAnnounce > std::chrono::seconds (MDNS_TTL / 4)) 387 | { 388 | info ("Announce mDNS %s %s\n", record_.qname.c_str (), addr_.name ()); 389 | socket_->writeTo (response.data (), response.size () - available, s_multicastAddress); 390 | s_lastAnnounce = now; 391 | } 392 | } 393 | } 394 | 395 | void mdns::setHostname (std::string hostname_) 396 | { 397 | if (hostname_.empty ()) 398 | hostname_ = platform::hostname (); 399 | 400 | if (s_hostname == hostname_) 401 | return; 402 | 403 | s_hostname = std::move (hostname_); 404 | s_hostnameLocal = s_hostname + ".local"; 405 | 406 | s_state = State::Probe1; 407 | s_lastProbe = platform::steady_clock::now (); 408 | } 409 | 410 | UniqueSocket mdns::createSocket () 411 | { 412 | auto socket = Socket::create (Socket::eDatagram); 413 | if (!socket) 414 | return nullptr; 415 | 416 | if (!socket->setReuseAddress ()) 417 | return nullptr; 418 | 419 | auto iface = SockAddr::AnyIPv4; 420 | iface.setPort (s_multicastAddress.port ()); 421 | if (!socket->bind (iface)) 422 | return nullptr; 423 | 424 | if (!socket->joinMulticastGroup (s_multicastAddress, iface)) 425 | return nullptr; 426 | 427 | s_state = State::Probe1; 428 | s_lastProbe = platform::steady_clock::now (); 429 | 430 | return socket; 431 | } 432 | 433 | void mdns::handleSocket (Socket *socket_, SockAddr const &addr_) 434 | { 435 | if (!socket_) 436 | return; 437 | 438 | // only support IPv4 for now 439 | if (addr_.domain () != SockAddr::Domain::IPv4) 440 | return; 441 | 442 | auto const now = platform::steady_clock::now (); 443 | 444 | switch (s_state) 445 | { 446 | case State::Probe1: 447 | case State::Probe2: 448 | case State::Probe3: 449 | if (now - s_lastProbe > 250ms) 450 | { 451 | probe (socket_, s_hostname); 452 | s_state = static_cast (static_cast (s_state) + 1); 453 | } 454 | break; 455 | 456 | case State::Announce1: 457 | case State::Announce2: 458 | if (now - s_lastAnnounce > 1s) 459 | { 460 | announce (socket_, 461 | nullptr, 462 | 0, 463 | 0, 464 | QueryRecord{.qname = s_hostname, .qtype = 1, .qclass = 1}, 465 | addr_); 466 | s_state = static_cast (static_cast (s_state) + 1); 467 | } 468 | 469 | default: 470 | break; 471 | } 472 | 473 | Socket::PollInfo pollInfo{*socket_, POLLIN, 0}; 474 | auto const rc = Socket::poll (&pollInfo, 1, 0ms); 475 | if (rc <= 0 || !(pollInfo.revents & POLLIN)) 476 | return; 477 | 478 | SockAddr srcAddr; 479 | std::vector buffer (65536); 480 | auto bytes = socket_->readFrom (buffer.data (), buffer.size (), srcAddr); 481 | if (bytes <= 0) 482 | return; 483 | 484 | // only support IPv4 for now 485 | if (srcAddr.domain () != SockAddr::Domain::IPv4) 486 | return; 487 | 488 | // ignore loopback 489 | if (std::memcmp (&reinterpret_cast (srcAddr).sin_addr.s_addr, 490 | &reinterpret_cast (addr_).sin_addr.s_addr, 491 | sizeof (in_addr_t)) == 0) 492 | return; 493 | 494 | std::uint16_t id; 495 | std::uint16_t flags; 496 | std::uint16_t qdCount; 497 | std::uint16_t anCount; 498 | std::uint16_t nsCount; 499 | std::uint16_t arCount; 500 | 501 | // parse header 502 | auto in = decode (buffer.data (), bytes, id); 503 | in = decode (in, bytes, flags); 504 | in = decode (in, bytes, qdCount); 505 | in = decode (in, bytes, anCount); 506 | in = decode (in, bytes, nsCount); 507 | in = decode (in, bytes, arCount); 508 | 509 | if (!in) 510 | return; 511 | 512 | auto const qr = (flags >> 15) & 0x1; 513 | 514 | // ill-formed on queries and responses 515 | auto const opcode = (flags >> 11) & 0xF; 516 | if (opcode != 0) 517 | return; 518 | 519 | // ill-formed on queries 520 | if (!qr && ((flags >> 10) & 0x1)) 521 | return; 522 | 523 | // punt on truncated messages 524 | if ((flags >> 9) & 0x1) 525 | return; 526 | 527 | // ill-formed on queries 528 | if (!qr && ((flags >> 7) & 0x1)) 529 | return; 530 | 531 | // must be zero 532 | if ((flags >> 4) & 0x7) 533 | return; 534 | 535 | // ill-formed on queries and responses 536 | if ((flags >> 0) & 0xF) 537 | return; 538 | 539 | // std::vector response (65536); 540 | // void *out = response.data (); 541 | // auto available = response.size (); 542 | 543 | std::vector answers; 544 | 545 | bool announced = false; 546 | for (unsigned i = 0; i < qdCount; ++i) 547 | { 548 | QueryRecord record; 549 | in = record.decode (in, bytes); 550 | 551 | if (!in) 552 | return; 553 | 554 | // only respond to queries 555 | if (qr) 556 | continue; 557 | 558 | // only accept A or ANY type 559 | if (record.qtype != 1 && record.qtype != 255) 560 | continue; 561 | 562 | // only accept IN or ANY class 563 | if ((record.qclass & 0x7FFF) != 1 && (record.qclass & 0x7FFF) != 255) 564 | continue; 565 | 566 | if (record.qname != s_hostname && record.qname != s_hostnameLocal) 567 | continue; 568 | 569 | if (!announced) 570 | { 571 | std::vector data (sizeof (in_addr_t)); 572 | auto n = data.size (); 573 | encode ( 574 | data.data (), n, static_cast (addr_).sin_addr.s_addr, false); 575 | 576 | answers.emplace_back (ResourceRecord{// answer 577 | .rname = record.qname, 578 | .rtype = 1, 579 | .rclass = static_cast (1 | (1 << 15)), 580 | .rttl = MDNS_TTL, 581 | .rlen = sizeof (in_addr_t), 582 | .rdata = std::move (data)}); 583 | 584 | announce (socket_, &srcAddr, id, flags, record, addr_); 585 | announced = true; 586 | } 587 | } 588 | 589 | for (unsigned i = 0; i < anCount; ++i) 590 | { 591 | ResourceRecord record; 592 | in = record.decode (in, bytes); 593 | 594 | if (!in) 595 | return; 596 | } 597 | } 598 | -------------------------------------------------------------------------------- /source/posix/collate.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 1995 Alex Tatmanjants 3 | * at Electronni Visti IA, Kiev, Ukraine. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. 26 | */ 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "collate.h" 37 | 38 | extern char *_PathLocale; 39 | int __collate_load_error = 1; 40 | int __collate_substitute_nontrivial; 41 | char __collate_version[STR_LEN]; 42 | u_char __collate_substitute_table[UCHAR_MAX + 1][STR_LEN]; 43 | struct __collate_st_char_pri __collate_char_pri_table[UCHAR_MAX + 1]; 44 | struct __collate_st_chain_pri __collate_chain_pri_table[TABLE_SIZE]; 45 | 46 | #define FREAD(a, b, c, d) \ 47 | do \ 48 | { \ 49 | if (fread (a, b, c, d) != c) \ 50 | { \ 51 | fclose (d); \ 52 | return -1; \ 53 | } \ 54 | } while (0) 55 | 56 | void __collate_err (int ex, const char *f); 57 | 58 | int __collate_load_tables (encoding) 59 | char *encoding; 60 | { 61 | char buf[PATH_MAX]; 62 | FILE *fp; 63 | int i, save_load_error; 64 | 65 | save_load_error = __collate_load_error; 66 | __collate_load_error = 1; 67 | if (!encoding) 68 | { 69 | __collate_load_error = save_load_error; 70 | return -1; 71 | } 72 | if (!strcmp (encoding, "C") || !strcmp (encoding, "POSIX")) 73 | return 0; 74 | if (!_PathLocale) 75 | { 76 | __collate_load_error = save_load_error; 77 | return -1; 78 | } 79 | /* Range checking not needed, encoding has fixed size */ 80 | (void)strcpy (buf, _PathLocale); 81 | (void)strcat (buf, "/"); 82 | (void)strcat (buf, encoding); 83 | (void)strcat (buf, "/LC_COLLATE"); 84 | if ((fp = fopen (buf, "r")) == NULL) 85 | { 86 | __collate_load_error = save_load_error; 87 | return -1; 88 | } 89 | FREAD (__collate_version, sizeof (__collate_version), 1, fp); 90 | if (strcmp (__collate_version, COLLATE_VERSION) != 0) 91 | { 92 | fclose (fp); 93 | return -1; 94 | } 95 | FREAD (__collate_substitute_table, sizeof (__collate_substitute_table), 1, fp); 96 | FREAD (__collate_char_pri_table, sizeof (__collate_char_pri_table), 1, fp); 97 | FREAD (__collate_chain_pri_table, sizeof (__collate_chain_pri_table), 1, fp); 98 | fclose (fp); 99 | __collate_load_error = 0; 100 | 101 | __collate_substitute_nontrivial = 0; 102 | for (i = 0; i < UCHAR_MAX + 1; i++) 103 | { 104 | if (__collate_substitute_table[i][0] != i || __collate_substitute_table[i][1] != 0) 105 | { 106 | __collate_substitute_nontrivial = 1; 107 | break; 108 | } 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | u_char *__collate_substitute (s) const u_char *s; 115 | { 116 | int dest_len, len, nlen; 117 | int delta = strlen ((const char *)s); 118 | u_char *dest_str = NULL; 119 | 120 | if (s == NULL || *s == '\0') 121 | return __collate_strdup ((u_char *)""); 122 | delta += delta / 8; 123 | dest_str = (u_char *)malloc (dest_len = delta); 124 | if (dest_str == NULL) 125 | __collate_err (EXIT_FAILURE, __FUNCTION__); 126 | len = 0; 127 | while (*s) 128 | { 129 | nlen = len + strlen ((const char *)__collate_substitute_table[*s]); 130 | if (dest_len <= nlen) 131 | { 132 | dest_str = reallocf (dest_str, dest_len = nlen + delta); 133 | if (dest_str == NULL) 134 | __collate_err (EXIT_FAILURE, __FUNCTION__); 135 | } 136 | strcpy ((char *)dest_str + len, (const char *)__collate_substitute_table[*s++]); 137 | len = nlen; 138 | } 139 | return dest_str; 140 | } 141 | 142 | void __collate_lookup (t, len, prim, sec) const u_char *t; 143 | int *len, *prim, *sec; 144 | { 145 | struct __collate_st_chain_pri *p2; 146 | 147 | *len = 1; 148 | *prim = *sec = 0; 149 | for (p2 = __collate_chain_pri_table; p2->str[0]; p2++) 150 | { 151 | if (strncmp ((const char *)t, (const char *)p2->str, strlen ((const char *)p2->str)) == 0) 152 | { 153 | *len = strlen ((const char *)p2->str); 154 | *prim = p2->prim; 155 | *sec = p2->sec; 156 | return; 157 | } 158 | } 159 | *prim = __collate_char_pri_table[*t].prim; 160 | *sec = __collate_char_pri_table[*t].sec; 161 | } 162 | 163 | u_char *__collate_strdup (s) 164 | u_char *s; 165 | { 166 | u_char *t = (u_char *)strdup ((const char *)s); 167 | 168 | if (t == NULL) 169 | __collate_err (EXIT_FAILURE, __FUNCTION__); 170 | return t; 171 | } 172 | 173 | void __collate_err (int ex, const char *f) 174 | { 175 | const char *s; 176 | int serrno = errno; 177 | int dummy; 178 | 179 | /* Be careful to change write counts if you change the strings */ 180 | write (STDERR_FILENO, "collate_error: ", 15); 181 | write (STDERR_FILENO, f, strlen (f)); 182 | write (STDERR_FILENO, ": ", 2); 183 | s = _strerror_r (_REENT, serrno, 1, &dummy); 184 | write (STDERR_FILENO, s, strlen (s)); 185 | write (STDERR_FILENO, "\n", 1); 186 | exit (ex); 187 | } 188 | 189 | #ifdef COLLATE_DEBUG 190 | void __collate_print_tables () 191 | { 192 | int i; 193 | struct __collate_st_chain_pri *p2; 194 | 195 | printf ("Substitute table:\n"); 196 | for (i = 0; i < UCHAR_MAX + 1; i++) 197 | if (i != *__collate_substitute_table[i]) 198 | printf ("\t'%c' --> \"%s\"\n", i, __collate_substitute_table[i]); 199 | printf ("Chain priority table:\n"); 200 | for (p2 = __collate_chain_pri_table; p2->str[0]; p2++) 201 | printf ("\t\"%s\" : %d %d\n\n", p2->str, p2->prim, p2->sec); 202 | printf ("Char priority table:\n"); 203 | for (i = 0; i < UCHAR_MAX + 1; i++) 204 | printf ("\t'%c' : %d %d\n", 205 | i, 206 | __collate_char_pri_table[i].prim, 207 | __collate_char_pri_table[i].sec); 208 | } 209 | #endif 210 | -------------------------------------------------------------------------------- /source/posix/collate.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 1995 Alex Tatmanjants 3 | * at Electronni Visti IA, Kiev, Ukraine. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. 26 | * 27 | * $FreeBSD: src/lib/libc/locale/collate.h,v 1.11 2002/03/21 22:46:54 obrien Exp $ 28 | */ 29 | 30 | #ifndef _COLLATE_H_ 31 | #define _COLLATE_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #define STR_LEN 10 38 | #define TABLE_SIZE 100 39 | #define COLLATE_VERSION "1.0\n" 40 | 41 | struct __collate_st_char_pri 42 | { 43 | int prim, sec; 44 | }; 45 | struct __collate_st_chain_pri 46 | { 47 | u_char str[STR_LEN]; 48 | int prim, sec; 49 | }; 50 | 51 | extern int __collate_load_error; 52 | extern int __collate_substitute_nontrivial; 53 | extern char __collate_version[STR_LEN]; 54 | extern u_char __collate_substitute_table[UCHAR_MAX + 1][STR_LEN]; 55 | extern struct __collate_st_char_pri __collate_char_pri_table[UCHAR_MAX + 1]; 56 | extern struct __collate_st_chain_pri __collate_chain_pri_table[TABLE_SIZE]; 57 | 58 | __BEGIN_DECLS 59 | u_char *__collate_strdup (u_char *); 60 | u_char *__collate_substitute (const u_char *); 61 | int __collate_load_tables (char *); 62 | void __collate_lookup (const u_char *, int *, int *, int *); 63 | int __collate_range_cmp (int, int); 64 | #ifdef COLLATE_DEBUG 65 | void __collate_print_tables (void); 66 | #endif 67 | __END_DECLS 68 | 69 | #endif /* !_COLLATE_H_ */ 70 | -------------------------------------------------------------------------------- /source/posix/collcmp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 1996 by Andrey A. Chernov, Moscow, Russia. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #define ASCII_COMPATIBLE_COLLATE /* see share/colldef */ 30 | 31 | #include "collate.h" 32 | #include 33 | #ifndef ASCII_COMPATIBLE_COLLATE 34 | #include 35 | #endif 36 | 37 | /* 38 | * Compare two characters converting collate information 39 | * into ASCII-compatible range, it allows to handle 40 | * "[a-z]"-type ranges with national characters. 41 | */ 42 | 43 | int __collate_range_cmp (c1, c2) 44 | int c1, c2; 45 | { 46 | static char s1[2], s2[2]; 47 | int ret; 48 | #ifndef ASCII_COMPATIBLE_COLLATE 49 | int as1, as2, al1, al2; 50 | #endif 51 | 52 | c1 &= UCHAR_MAX; 53 | c2 &= UCHAR_MAX; 54 | if (c1 == c2) 55 | return (0); 56 | 57 | #ifndef ASCII_COMPATIBLE_COLLATE 58 | as1 = isascii (c1); 59 | as2 = isascii (c2); 60 | al1 = isalpha (c1); 61 | al2 = isalpha (c2); 62 | 63 | if (as1 || as2 || al1 || al2) 64 | { 65 | if ((as1 && as2) || (!al1 && !al2)) 66 | return (c1 - c2); 67 | if (al1 && !al2) 68 | { 69 | if (isupper (c1)) 70 | return ('A' - c2); 71 | else 72 | return ('a' - c2); 73 | } 74 | else if (al2 && !al1) 75 | { 76 | if (isupper (c2)) 77 | return (c1 - 'A'); 78 | else 79 | return (c1 - 'a'); 80 | } 81 | } 82 | #endif 83 | s1[0] = c1; 84 | s2[0] = c2; 85 | if ((ret = strcoll (s1, s2)) != 0) 86 | return (ret); 87 | return (c1 - c2); 88 | } 89 | -------------------------------------------------------------------------------- /source/posix/glob.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1989, 1993 3 | * The Regents of the University of California. All rights reserved. 4 | * 5 | * This code is derived from software contributed to Berkeley by 6 | * Guido van Rossum. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 4. Neither the name of the University nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifdef __CYGWIN__ 34 | #define _NO_GLOB /* Cygwin provides its own glob. */ 35 | #endif 36 | 37 | #ifndef _NO_GLOB 38 | 39 | #if defined(LIBC_SCCS) && !defined(lint) 40 | static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93"; 41 | #endif /* LIBC_SCCS and not lint */ 42 | #include 43 | 44 | /* 45 | * glob(3) -- a superset of the one defined in POSIX 1003.2. 46 | * 47 | * The [!...] convention to negate a range is supported (SysV, Posix, ksh). 48 | * 49 | * Optional extra services, controlled by flags not defined by POSIX: 50 | * 51 | * GLOB_QUOTE: 52 | * Escaping convention: \ inhibits any special meaning the following 53 | * character might have (except \ at end of string is retained). 54 | * GLOB_MAGCHAR: 55 | * Set in gl_flags if pattern contained a globbing character. 56 | * GLOB_NOMAGIC: 57 | * Same as GLOB_NOCHECK, but it will only append pattern if it did 58 | * not contain any magic characters. [Used in csh style globbing] 59 | * GLOB_ALTDIRFUNC: 60 | * Use alternately specified directory access functions. 61 | * GLOB_TILDE: 62 | * expand ~user/foo to the /home/dir/of/user/foo 63 | * GLOB_BRACE: 64 | * expand {1,2}{a,b} to 1a 1b 2a 2b 65 | * gl_matchc: 66 | * Number of matches in the current invocation of glob. 67 | */ 68 | 69 | #include 70 | #include 71 | #include 72 | 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | 84 | #include "collate.h" 85 | 86 | #define DOLLAR '$' 87 | #define DOT '.' 88 | #define EOS '\0' 89 | #define LBRACKET '[' 90 | #define NOT '!' 91 | #define QUESTION '?' 92 | #define QUOTE '\\' 93 | #define RANGE '-' 94 | #define RBRACKET ']' 95 | #define SEP '/' 96 | #define STAR '*' 97 | #define TILDE '~' 98 | #define UNDERSCORE '_' 99 | #define LBRACE '{' 100 | #define RBRACE '}' 101 | #define SLASH '/' 102 | #define COMMA ',' 103 | 104 | #ifndef DEBUG 105 | 106 | #define M_QUOTE 0x8000 107 | #define M_PROTECT 0x4000 108 | #define M_MASK 0xffff 109 | #define M_ASCII 0x00ff 110 | 111 | typedef u_short Char; 112 | 113 | #else 114 | 115 | #define M_QUOTE 0x80 116 | #define M_PROTECT 0x40 117 | #define M_MASK 0xff 118 | #define M_ASCII 0x7f 119 | 120 | typedef char Char; 121 | 122 | #endif 123 | 124 | #define CHAR(c) ((Char)((c)&M_ASCII)) 125 | #define META(c) ((Char)((c) | M_QUOTE)) 126 | #define M_ALL META ('*') 127 | #define M_END META (']') 128 | #define M_NOT META ('!') 129 | #define M_ONE META ('?') 130 | #define M_RNG META ('-') 131 | #define M_SET META ('[') 132 | #define ismeta(c) (((c)&M_QUOTE) != 0) 133 | 134 | static int compare (const void *, const void *); 135 | static int g_Ctoc (const Char *, char *, u_int); 136 | static int g_lstat (Char *, struct stat *, glob_t *); 137 | static DIR *g_opendir (Char *, glob_t *); 138 | static Char *g_strchr (Char *, int); 139 | #ifdef notdef 140 | static Char *g_strcat (Char *, const Char *); 141 | #endif 142 | static int g_stat (Char *, struct stat *, glob_t *); 143 | static int glob0 (const Char *, glob_t *, int *); 144 | static int glob1 (Char *, glob_t *, int *); 145 | static int glob2 (Char *, Char *, Char *, Char *, glob_t *, int *); 146 | static int glob3 (Char *, Char *, Char *, Char *, Char *, glob_t *, int *); 147 | static int globextend (const Char *, glob_t *, int *); 148 | static int globexp1 (const Char *, glob_t *, int *); 149 | static int globexp2 (const Char *, const Char *, glob_t *, int *, int *); 150 | static int match (Char *, Char *, Char *); 151 | #ifdef DEBUG 152 | static void qprintf (const char *, Char *); 153 | #endif 154 | 155 | int glob (pattern, flags, errfunc, pglob) const char *__restrict pattern; 156 | int flags, (*errfunc) (const char *, int); 157 | glob_t *__restrict pglob; 158 | { 159 | const u_char *patnext; 160 | int c, limit; 161 | Char *bufnext, *bufend, patbuf[MAXPATHLEN]; 162 | 163 | patnext = (u_char *)pattern; 164 | if (!(flags & GLOB_APPEND)) 165 | { 166 | pglob->gl_pathc = 0; 167 | pglob->gl_pathv = NULL; 168 | if (!(flags & GLOB_DOOFFS)) 169 | pglob->gl_offs = 0; 170 | } 171 | if (flags & GLOB_LIMIT) 172 | { 173 | limit = pglob->gl_matchc; 174 | if (limit == 0) 175 | limit = ARG_MAX; 176 | } 177 | else 178 | limit = 0; 179 | pglob->gl_flags = flags & ~GLOB_MAGCHAR; 180 | pglob->gl_errfunc = errfunc; 181 | pglob->gl_matchc = 0; 182 | 183 | bufnext = patbuf; 184 | bufend = bufnext + MAXPATHLEN - 1; 185 | if (flags & GLOB_QUOTE) 186 | { 187 | /* Protect the quoted characters. */ 188 | while (bufnext < bufend && (c = *patnext++) != EOS) 189 | if (c == QUOTE) 190 | { 191 | if ((c = *patnext++) == EOS) 192 | { 193 | c = QUOTE; 194 | --patnext; 195 | } 196 | *bufnext++ = c | M_PROTECT; 197 | } 198 | else 199 | *bufnext++ = c; 200 | } 201 | else 202 | while (bufnext < bufend && (c = *patnext++) != EOS) 203 | *bufnext++ = c; 204 | *bufnext = EOS; 205 | 206 | if (flags & GLOB_BRACE) 207 | return globexp1 (patbuf, pglob, &limit); 208 | else 209 | return glob0 (patbuf, pglob, &limit); 210 | } 211 | 212 | /* 213 | * Expand recursively a glob {} pattern. When there is no more expansion 214 | * invoke the standard globbing routine to glob the rest of the magic 215 | * characters 216 | */ 217 | static int globexp1 (pattern, pglob, limit) const Char *pattern; 218 | glob_t *pglob; 219 | int *limit; 220 | { 221 | const Char *ptr = pattern; 222 | int rv; 223 | 224 | /* Protect a single {}, for find(1), like csh */ 225 | if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) 226 | return glob0 (pattern, pglob, limit); 227 | 228 | while ((ptr = (const Char *)g_strchr ((Char *)ptr, LBRACE)) != NULL) 229 | if (!globexp2 (ptr, pattern, pglob, &rv, limit)) 230 | return rv; 231 | 232 | return glob0 (pattern, pglob, limit); 233 | } 234 | 235 | /* 236 | * Recursive brace globbing helper. Tries to expand a single brace. 237 | * If it succeeds then it invokes globexp1 with the new pattern. 238 | * If it fails then it tries to glob the rest of the pattern and returns. 239 | */ 240 | static int globexp2 (ptr, pattern, pglob, rv, limit) const Char *ptr, *pattern; 241 | glob_t *pglob; 242 | int *rv, *limit; 243 | { 244 | int i; 245 | Char *lm, *ls; 246 | const Char *pe, *pm, *pl; 247 | Char patbuf[MAXPATHLEN]; 248 | 249 | /* copy part up to the brace */ 250 | for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++) 251 | continue; 252 | *lm = EOS; 253 | ls = lm; 254 | 255 | /* Find the balanced brace */ 256 | for (i = 0, pe = ++ptr; *pe; pe++) 257 | if (*pe == LBRACKET) 258 | { 259 | /* Ignore everything between [] */ 260 | for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++) 261 | continue; 262 | if (*pe == EOS) 263 | { 264 | /* 265 | * We could not find a matching RBRACKET. 266 | * Ignore and just look for RBRACE 267 | */ 268 | pe = pm; 269 | } 270 | } 271 | else if (*pe == LBRACE) 272 | i++; 273 | else if (*pe == RBRACE) 274 | { 275 | if (i == 0) 276 | break; 277 | i--; 278 | } 279 | 280 | /* Non matching braces; just glob the pattern */ 281 | if (i != 0 || *pe == EOS) 282 | { 283 | *rv = glob0 (patbuf, pglob, limit); 284 | return 0; 285 | } 286 | 287 | for (i = 0, pl = pm = ptr; pm <= pe; pm++) 288 | switch (*pm) 289 | { 290 | case LBRACKET: 291 | /* Ignore everything between [] */ 292 | for (pl = pm++; *pm != RBRACKET && *pm != EOS; pm++) 293 | continue; 294 | if (*pm == EOS) 295 | { 296 | /* 297 | * We could not find a matching RBRACKET. 298 | * Ignore and just look for RBRACE 299 | */ 300 | pm = pl; 301 | } 302 | break; 303 | 304 | case LBRACE: 305 | i++; 306 | break; 307 | 308 | case RBRACE: 309 | if (i) 310 | { 311 | i--; 312 | break; 313 | } 314 | /* FALLTHROUGH */ 315 | case COMMA: 316 | if (i && *pm == COMMA) 317 | break; 318 | else 319 | { 320 | /* Append the current string */ 321 | for (lm = ls; (pl < pm); *lm++ = *pl++) 322 | continue; 323 | /* 324 | * Append the rest of the pattern after the 325 | * closing brace 326 | */ 327 | for (pl = pe + 1; (*lm++ = *pl++) != EOS;) 328 | continue; 329 | 330 | /* Expand the current pattern */ 331 | #ifdef DEBUG 332 | qprintf ("globexp2:", patbuf); 333 | #endif 334 | *rv = globexp1 (patbuf, pglob, limit); 335 | 336 | /* move after the comma, to the next string */ 337 | pl = pm + 1; 338 | } 339 | break; 340 | 341 | default: 342 | break; 343 | } 344 | *rv = 0; 345 | return 0; 346 | } 347 | 348 | /* 349 | * The main glob() routine: compiles the pattern (optionally processing 350 | * quotes), calls glob1() to do the real pattern matching, and finally 351 | * sorts the list (unless unsorted operation is requested). Returns 0 352 | * if things went well, nonzero if errors occurred. It is not an error 353 | * to find no matches. 354 | */ 355 | static int glob0 (pattern, pglob, limit) const Char *pattern; 356 | glob_t *pglob; 357 | int *limit; 358 | { 359 | const Char *qpatnext; 360 | int c, err, oldpathc; 361 | Char *bufnext, patbuf[MAXPATHLEN]; 362 | 363 | qpatnext = pattern; 364 | oldpathc = pglob->gl_pathc; 365 | bufnext = patbuf; 366 | 367 | /* We don't need to check for buffer overflow any more. */ 368 | while ((c = *qpatnext++) != EOS) 369 | { 370 | switch (c) 371 | { 372 | case LBRACKET: 373 | c = *qpatnext; 374 | if (c == NOT) 375 | ++qpatnext; 376 | if (*qpatnext == EOS || g_strchr ((Char *)qpatnext + 1, RBRACKET) == NULL) 377 | { 378 | *bufnext++ = LBRACKET; 379 | if (c == NOT) 380 | --qpatnext; 381 | break; 382 | } 383 | *bufnext++ = M_SET; 384 | if (c == NOT) 385 | *bufnext++ = M_NOT; 386 | c = *qpatnext++; 387 | do 388 | { 389 | *bufnext++ = CHAR (c); 390 | if (*qpatnext == RANGE && (c = qpatnext[1]) != RBRACKET) 391 | { 392 | *bufnext++ = M_RNG; 393 | *bufnext++ = CHAR (c); 394 | qpatnext += 2; 395 | } 396 | } while ((c = *qpatnext++) != RBRACKET); 397 | pglob->gl_flags |= GLOB_MAGCHAR; 398 | *bufnext++ = M_END; 399 | break; 400 | case QUESTION: 401 | pglob->gl_flags |= GLOB_MAGCHAR; 402 | *bufnext++ = M_ONE; 403 | break; 404 | case STAR: 405 | pglob->gl_flags |= GLOB_MAGCHAR; 406 | /* collapse adjacent stars to one, 407 | * to avoid exponential behavior 408 | */ 409 | if (bufnext == patbuf || bufnext[-1] != M_ALL) 410 | *bufnext++ = M_ALL; 411 | break; 412 | default: 413 | *bufnext++ = CHAR (c); 414 | break; 415 | } 416 | } 417 | *bufnext = EOS; 418 | #ifdef DEBUG 419 | qprintf ("glob0:", patbuf); 420 | #endif 421 | 422 | if ((err = glob1 (patbuf, pglob, limit)) != 0) 423 | return (err); 424 | 425 | /* 426 | * If there was no match we are going to append the pattern 427 | * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified 428 | * and the pattern did not contain any magic characters 429 | * GLOB_NOMAGIC is there just for compatibility with csh. 430 | */ 431 | if (pglob->gl_pathc == oldpathc && 432 | ((pglob->gl_flags & GLOB_NOCHECK) || 433 | ((pglob->gl_flags & GLOB_NOMAGIC) && !(pglob->gl_flags & GLOB_MAGCHAR)))) 434 | return (globextend (pattern, pglob, limit)); 435 | else if (!(pglob->gl_flags & GLOB_NOSORT)) 436 | qsort (pglob->gl_pathv + pglob->gl_offs + oldpathc, 437 | pglob->gl_pathc - oldpathc, 438 | sizeof (char *), 439 | compare); 440 | return (0); 441 | } 442 | 443 | static int compare (p, q) const void *p, *q; 444 | { 445 | return (strcmp (*(char **)p, *(char **)q)); 446 | } 447 | 448 | static int glob1 (pattern, pglob, limit) 449 | Char *pattern; 450 | glob_t *pglob; 451 | int *limit; 452 | { 453 | Char pathbuf[MAXPATHLEN]; 454 | 455 | /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */ 456 | if (*pattern == EOS) 457 | return (0); 458 | return (glob2 (pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, pattern, pglob, limit)); 459 | } 460 | 461 | /* 462 | * The functions glob2 and glob3 are mutually recursive; there is one level 463 | * of recursion for each segment in the pattern that contains one or more 464 | * meta characters. 465 | */ 466 | static int glob2 (pathbuf, pathend, pathend_last, pattern, pglob, limit) 467 | Char *pathbuf, *pathend, *pathend_last, *pattern; 468 | glob_t *pglob; 469 | int *limit; 470 | { 471 | struct stat sb; 472 | Char *p, *q; 473 | int anymeta; 474 | 475 | /* 476 | * Loop over pattern segments until end of pattern or until 477 | * segment with meta character found. 478 | */ 479 | for (anymeta = 0;;) 480 | { 481 | if (*pattern == EOS) 482 | { /* End of pattern? */ 483 | *pathend = EOS; 484 | if (g_lstat (pathbuf, &sb, pglob)) 485 | return (0); 486 | 487 | if (((pglob->gl_flags & GLOB_MARK) && pathend[-1] != SEP) && 488 | (S_ISDIR (sb.st_mode) || 489 | (S_ISLNK (sb.st_mode) && (g_stat (pathbuf, &sb, pglob) == 0) && 490 | S_ISDIR (sb.st_mode)))) 491 | { 492 | if (pathend + 1 > pathend_last) 493 | return (1); 494 | *pathend++ = SEP; 495 | *pathend = EOS; 496 | } 497 | ++pglob->gl_matchc; 498 | return (globextend (pathbuf, pglob, limit)); 499 | } 500 | 501 | /* Find end of next segment, copy tentatively to pathend. */ 502 | q = pathend; 503 | p = pattern; 504 | while (*p != EOS && *p != SEP) 505 | { 506 | if (ismeta (*p)) 507 | anymeta = 1; 508 | if (q + 1 > pathend_last) 509 | return (1); 510 | *q++ = *p++; 511 | } 512 | 513 | if (!anymeta) 514 | { /* No expansion, do next segment. */ 515 | pathend = q; 516 | pattern = p; 517 | while (*pattern == SEP) 518 | { 519 | if (pathend + 1 > pathend_last) 520 | return (1); 521 | *pathend++ = *pattern++; 522 | } 523 | } 524 | else /* Need expansion, recurse. */ 525 | return (glob3 (pathbuf, pathend, pathend_last, pattern, p, pglob, limit)); 526 | } 527 | /* NOTREACHED */ 528 | } 529 | 530 | static int glob3 (pathbuf, pathend, pathend_last, pattern, restpattern, pglob, limit) 531 | Char *pathbuf, *pathend, *pathend_last, *pattern, *restpattern; 532 | glob_t *pglob; 533 | int *limit; 534 | { 535 | struct dirent *dp; 536 | DIR *dirp; 537 | int err; 538 | char buf[MAXPATHLEN]; 539 | 540 | /* 541 | * The readdirfunc declaration can't be prototyped, because it is 542 | * assigned, below, to two functions which are prototyped in glob.h 543 | * and dirent.h as taking pointers to differently typed opaque 544 | * structures. 545 | */ 546 | struct dirent *(*readdirfunc) (); 547 | 548 | if (pathend > pathend_last) 549 | return (1); 550 | *pathend = EOS; 551 | errno = 0; 552 | 553 | if ((dirp = g_opendir (pathbuf, pglob)) == NULL) 554 | { 555 | /* TODO: don't call for ENOENT or ENOTDIR? */ 556 | if (pglob->gl_errfunc) 557 | { 558 | if (g_Ctoc (pathbuf, buf, sizeof (buf))) 559 | return (GLOB_ABEND); 560 | if (pglob->gl_errfunc (buf, errno) || pglob->gl_flags & GLOB_ERR) 561 | return (GLOB_ABEND); 562 | } 563 | return (0); 564 | } 565 | 566 | err = 0; 567 | 568 | /* Search directory for matching names. */ 569 | if (pglob->gl_flags & GLOB_ALTDIRFUNC) 570 | readdirfunc = pglob->gl_readdir; 571 | else 572 | readdirfunc = readdir; 573 | while ((dp = (*readdirfunc) (dirp))) 574 | { 575 | u_char *sc; 576 | Char *dc; 577 | 578 | /* Initial DOT must be matched literally. */ 579 | if (dp->d_name[0] == DOT && *pattern != DOT) 580 | continue; 581 | dc = pathend; 582 | sc = (u_char *)dp->d_name; 583 | while (dc < pathend_last && (*dc++ = *sc++) != EOS) 584 | ; 585 | if (!match (pathend, pattern, restpattern)) 586 | { 587 | *pathend = EOS; 588 | continue; 589 | } 590 | err = glob2 (pathbuf, --dc, pathend_last, restpattern, pglob, limit); 591 | if (err) 592 | break; 593 | } 594 | 595 | if (pglob->gl_flags & GLOB_ALTDIRFUNC) 596 | (*pglob->gl_closedir) (dirp); 597 | else 598 | closedir (dirp); 599 | return (err); 600 | } 601 | 602 | /* 603 | * Extend the gl_pathv member of a glob_t structure to accomodate a new item, 604 | * add the new item, and update gl_pathc. 605 | * 606 | * This assumes the BSD realloc, which only copies the block when its size 607 | * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic 608 | * behavior. 609 | * 610 | * Return 0 if new item added, error code if memory couldn't be allocated. 611 | * 612 | * Invariant of the glob_t structure: 613 | * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and 614 | * gl_pathv points to (gl_offs + gl_pathc + 1) items. 615 | */ 616 | static int globextend (path, pglob, limit) const Char *path; 617 | glob_t *pglob; 618 | int *limit; 619 | { 620 | char **pathv; 621 | int i; 622 | u_int newsize, len; 623 | char *copy; 624 | const Char *p; 625 | 626 | if (*limit && pglob->gl_pathc > *limit) 627 | { 628 | errno = 0; 629 | return (GLOB_NOSPACE); 630 | } 631 | 632 | newsize = sizeof (*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); 633 | pathv = pglob->gl_pathv ? realloc ((char *)pglob->gl_pathv, newsize) : malloc (newsize); 634 | if (pathv == NULL) 635 | { 636 | if (pglob->gl_pathv) 637 | { 638 | free (pglob->gl_pathv); 639 | pglob->gl_pathv = NULL; 640 | } 641 | return (GLOB_NOSPACE); 642 | } 643 | 644 | if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) 645 | { 646 | /* first time around -- clear initial gl_offs items */ 647 | pathv += pglob->gl_offs; 648 | for (i = pglob->gl_offs; --i >= 0;) 649 | *--pathv = NULL; 650 | } 651 | pglob->gl_pathv = pathv; 652 | 653 | for (p = path; *p++;) 654 | continue; 655 | len = (size_t)(p - path); 656 | if ((copy = malloc (len)) != NULL) 657 | { 658 | if (g_Ctoc (path, copy, len)) 659 | { 660 | free (copy); 661 | return (GLOB_NOSPACE); 662 | } 663 | pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; 664 | } 665 | pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; 666 | return (copy == NULL ? GLOB_NOSPACE : 0); 667 | } 668 | 669 | /* 670 | * pattern matching function for filenames. Each occurrence of the * 671 | * pattern causes a recursion level. 672 | */ 673 | static int match (name, pat, patend) 674 | Char *name, *pat, *patend; 675 | { 676 | int ok, negate_range; 677 | Char c, k; 678 | 679 | while (pat < patend) 680 | { 681 | c = *pat++; 682 | switch (c & M_MASK) 683 | { 684 | case M_ALL: 685 | if (pat == patend) 686 | return (1); 687 | do 688 | if (match (name, pat, patend)) 689 | return (1); 690 | while (*name++ != EOS); 691 | return (0); 692 | case M_ONE: 693 | if (*name++ == EOS) 694 | return (0); 695 | break; 696 | case M_SET: 697 | ok = 0; 698 | if ((k = *name++) == EOS) 699 | return (0); 700 | if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) 701 | ++pat; 702 | while (((c = *pat++) & M_MASK) != M_END) 703 | if ((*pat & M_MASK) == M_RNG) 704 | { 705 | if (__collate_load_error 706 | ? CHAR (c) <= CHAR (k) && CHAR (k) <= CHAR (pat[1]) 707 | : __collate_range_cmp (CHAR (c), CHAR (k)) <= 0 && 708 | __collate_range_cmp (CHAR (k), CHAR (pat[1])) <= 0) 709 | ok = 1; 710 | pat += 2; 711 | } 712 | else if (c == k) 713 | ok = 1; 714 | if (ok == negate_range) 715 | return (0); 716 | break; 717 | default: 718 | if (*name++ != c) 719 | return (0); 720 | break; 721 | } 722 | } 723 | return (*name == EOS); 724 | } 725 | 726 | /* Free allocated data belonging to a glob_t structure. */ 727 | void globfree (pglob) glob_t *pglob; 728 | { 729 | int i; 730 | char **pp; 731 | 732 | if (pglob->gl_pathv != NULL) 733 | { 734 | pp = pglob->gl_pathv + pglob->gl_offs; 735 | for (i = pglob->gl_pathc; i--; ++pp) 736 | if (*pp) 737 | free (*pp); 738 | free (pglob->gl_pathv); 739 | pglob->gl_pathv = NULL; 740 | } 741 | } 742 | 743 | static DIR *g_opendir (str, pglob) 744 | Char *str; 745 | glob_t *pglob; 746 | { 747 | char buf[MAXPATHLEN]; 748 | 749 | if (!*str) 750 | strcpy (buf, "."); 751 | else 752 | { 753 | if (g_Ctoc (str, buf, sizeof (buf))) 754 | return (NULL); 755 | } 756 | 757 | if (pglob->gl_flags & GLOB_ALTDIRFUNC) 758 | return ((*pglob->gl_opendir) (buf)); 759 | 760 | return (opendir (buf)); 761 | } 762 | 763 | static int g_lstat (fn, sb, pglob) 764 | Char *fn; 765 | struct stat *sb; 766 | glob_t *pglob; 767 | { 768 | char buf[MAXPATHLEN]; 769 | 770 | if (g_Ctoc (fn, buf, sizeof (buf))) 771 | { 772 | errno = ENAMETOOLONG; 773 | return (-1); 774 | } 775 | if (pglob->gl_flags & GLOB_ALTDIRFUNC) 776 | return ((*pglob->gl_lstat) (buf, sb)); 777 | return (lstat (buf, sb)); 778 | } 779 | 780 | static int g_stat (fn, sb, pglob) 781 | Char *fn; 782 | struct stat *sb; 783 | glob_t *pglob; 784 | { 785 | char buf[MAXPATHLEN]; 786 | 787 | if (g_Ctoc (fn, buf, sizeof (buf))) 788 | { 789 | errno = ENAMETOOLONG; 790 | return (-1); 791 | } 792 | if (pglob->gl_flags & GLOB_ALTDIRFUNC) 793 | return ((*pglob->gl_stat) (buf, sb)); 794 | return (stat (buf, sb)); 795 | } 796 | 797 | static Char *g_strchr (str, ch) 798 | Char *str; 799 | int ch; 800 | { 801 | do 802 | { 803 | if (*str == ch) 804 | return (str); 805 | } while (*str++); 806 | return (NULL); 807 | } 808 | 809 | static int g_Ctoc (str, buf, len) const Char *str; 810 | char *buf; 811 | u_int len; 812 | { 813 | while (len--) 814 | { 815 | if ((*buf++ = *str++) == '\0') 816 | return (0); 817 | } 818 | return (1); 819 | } 820 | 821 | #ifdef DEBUG 822 | static void qprintf (str, s) const char *str; 823 | Char *s; 824 | { 825 | Char *p; 826 | 827 | (void)printf ("%s:\n", str); 828 | for (p = s; *p; p++) 829 | (void)printf ("%c", CHAR (*p)); 830 | (void)printf ("\n"); 831 | for (p = s; *p; p++) 832 | (void)printf ("%c", *p & M_PROTECT ? '"' : ' '); 833 | (void)printf ("\n"); 834 | for (p = s; *p; p++) 835 | (void)printf ("%c", ismeta (*p) ? '_' : ' '); 836 | (void)printf ("\n"); 837 | } 838 | #endif 839 | #endif /* !_NO_GLOB */ 840 | -------------------------------------------------------------------------------- /source/sockAddr.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "sockAddr.h" 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef __3DS__ 30 | static_assert (sizeof (sockaddr_storage) == 0x1c); 31 | #endif 32 | 33 | namespace 34 | { 35 | in_addr inaddr_any = {.s_addr = htonl (INADDR_ANY)}; 36 | 37 | std::strong_ordering 38 | strongMemCompare (void const *const a_, void const *const b_, std::size_t const size_) 39 | { 40 | auto const cmp = std::memcmp (a_, b_, size_); 41 | if (cmp < 0) 42 | return std::strong_ordering::less; 43 | if (cmp > 0) 44 | return std::strong_ordering::greater; 45 | return std::strong_ordering::equal; 46 | } 47 | } 48 | 49 | /////////////////////////////////////////////////////////////////////////// 50 | SockAddr const SockAddr::AnyIPv4{inaddr_any}; 51 | 52 | #ifndef NO_IPV6 53 | SockAddr const SockAddr::AnyIPv6{in6addr_any}; 54 | #endif 55 | 56 | SockAddr::~SockAddr () = default; 57 | 58 | SockAddr::SockAddr () = default; 59 | 60 | SockAddr::SockAddr (Domain const domain_) 61 | { 62 | switch (domain_) 63 | { 64 | case Domain::IPv4: 65 | *this = AnyIPv4; 66 | break; 67 | 68 | #ifndef NO_IPV6 69 | case Domain::IPv6: 70 | *this = AnyIPv6; 71 | break; 72 | #endif 73 | 74 | default: 75 | std::abort (); 76 | } 77 | } 78 | 79 | SockAddr::SockAddr (in_addr_t const addr_, std::uint16_t const port_) 80 | : SockAddr (in_addr{.s_addr = addr_}, port_) 81 | { 82 | } 83 | 84 | SockAddr::SockAddr (in_addr const &addr_, std::uint16_t const port_) 85 | { 86 | std::memset (&m_addr, 0, sizeof (m_addr)); 87 | m_addr.ss_family = AF_INET; 88 | setAddr (addr_); 89 | setPort (port_); 90 | } 91 | 92 | #ifndef NO_IPV6 93 | SockAddr::SockAddr (in6_addr const &addr_, std::uint16_t const port_) 94 | { 95 | std::memset (&m_addr, 0, sizeof (m_addr)); 96 | m_addr.ss_family = AF_INET6; 97 | setAddr (addr_); 98 | setPort (port_); 99 | } 100 | #endif 101 | 102 | SockAddr::SockAddr (SockAddr const &that_) = default; 103 | 104 | SockAddr::SockAddr (SockAddr &&that_) = default; 105 | 106 | SockAddr &SockAddr::operator= (SockAddr const &that_) = default; 107 | 108 | SockAddr &SockAddr::operator= (SockAddr &&that_) = default; 109 | 110 | SockAddr::SockAddr (sockaddr_in const &addr_) 111 | { 112 | assert (addr_.sin_family == AF_INET); 113 | std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in)); 114 | } 115 | 116 | #ifndef NO_IPV6 117 | SockAddr::SockAddr (sockaddr_in6 const &addr_) 118 | { 119 | assert (addr_.sin6_family == AF_INET6); 120 | std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in6)); 121 | } 122 | #endif 123 | 124 | SockAddr::SockAddr (sockaddr_storage const &addr_) 125 | { 126 | switch (addr_.ss_family) 127 | { 128 | case AF_INET: 129 | std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in)); 130 | break; 131 | 132 | #ifndef NO_IPV6 133 | case AF_INET6: 134 | std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in6)); 135 | break; 136 | #endif 137 | 138 | default: 139 | std::abort (); 140 | } 141 | } 142 | 143 | SockAddr::operator sockaddr_in const & () const 144 | { 145 | assert (m_addr.ss_family == AF_INET); 146 | return reinterpret_cast (m_addr); 147 | } 148 | 149 | #ifndef NO_IPV6 150 | SockAddr::operator sockaddr_in6 const & () const 151 | { 152 | assert (m_addr.ss_family == AF_INET6); 153 | return reinterpret_cast (m_addr); 154 | } 155 | #endif 156 | 157 | SockAddr::operator sockaddr_storage const & () const 158 | { 159 | return m_addr; 160 | } 161 | 162 | SockAddr::operator sockaddr * () 163 | { 164 | return reinterpret_cast (&m_addr); 165 | } 166 | 167 | SockAddr::operator sockaddr const * () const 168 | { 169 | return reinterpret_cast (&m_addr); 170 | } 171 | 172 | bool SockAddr::operator== (SockAddr const &that_) const 173 | { 174 | if (m_addr.ss_family != that_.m_addr.ss_family) 175 | return false; 176 | 177 | switch (m_addr.ss_family) 178 | { 179 | case AF_INET: 180 | if (port () != that_.port ()) 181 | return false; 182 | 183 | // ignore sin_zero 184 | return static_cast (*this).sin_addr.s_addr == 185 | static_cast (that_).sin_addr.s_addr; 186 | 187 | #ifndef NO_IPV6 188 | case AF_INET6: 189 | return std::memcmp (&m_addr, &that_.m_addr, sizeof (sockaddr_in6)) == 0; 190 | #endif 191 | 192 | default: 193 | std::abort (); 194 | } 195 | } 196 | 197 | std::strong_ordering SockAddr::operator<=> (SockAddr const &that_) const 198 | { 199 | if (m_addr.ss_family != that_.m_addr.ss_family) 200 | return m_addr.ss_family <=> that_.m_addr.ss_family; 201 | 202 | switch (m_addr.ss_family) 203 | { 204 | case AF_INET: 205 | { 206 | auto const cmp = 207 | strongMemCompare (&static_cast (*this).sin_addr.s_addr, 208 | &static_cast (that_).sin_addr.s_addr, 209 | sizeof (in_addr_t)); 210 | 211 | if (cmp != std::strong_ordering::equal) 212 | return cmp; 213 | 214 | return port () <=> that_.port (); 215 | } 216 | 217 | #ifndef NO_IPV6 218 | case AF_INET6: 219 | { 220 | auto const &addr1 = static_cast (*this); 221 | auto const &addr2 = static_cast (that_); 222 | 223 | if (auto const cmp = 224 | strongMemCompare (&addr1.sin6_addr, &addr2.sin6_addr, sizeof (in6_addr)); 225 | cmp != std::strong_ordering::equal) 226 | return cmp; 227 | 228 | auto const p1 = port (); 229 | auto const p2 = that_.port (); 230 | 231 | if (p1 < p2) 232 | return std::strong_ordering::less; 233 | else if (p1 > p2) 234 | return std::strong_ordering::greater; 235 | 236 | if (auto const cmp = strongMemCompare ( 237 | &addr1.sin6_flowinfo, &addr2.sin6_flowinfo, sizeof (std::uint32_t)); 238 | cmp != std::strong_ordering::equal) 239 | return cmp; 240 | 241 | return strongMemCompare ( 242 | &addr1.sin6_flowinfo, &addr2.sin6_flowinfo, sizeof (std::uint32_t)); 243 | } 244 | #endif 245 | 246 | default: 247 | std::abort (); 248 | } 249 | } 250 | 251 | void SockAddr::setAddr (in_addr_t const addr_) 252 | { 253 | setAddr (in_addr{.s_addr = addr_}); 254 | } 255 | 256 | void SockAddr::setAddr (in_addr const &addr_) 257 | { 258 | if (m_addr.ss_family != AF_INET) 259 | std::abort (); 260 | 261 | std::memcpy (&reinterpret_cast (m_addr).sin_addr, &addr_, sizeof (addr_)); 262 | ; 263 | } 264 | 265 | #ifndef NO_IPV6 266 | void SockAddr::setAddr (in6_addr const &addr_) 267 | { 268 | if (m_addr.ss_family != AF_INET6) 269 | std::abort (); 270 | 271 | std::memcpy (&reinterpret_cast (m_addr).sin6_addr, &addr_, sizeof (addr_)); 272 | ; 273 | } 274 | #endif 275 | 276 | std::uint16_t SockAddr::port () const 277 | { 278 | switch (m_addr.ss_family) 279 | { 280 | case AF_INET: 281 | return ntohs (reinterpret_cast (&m_addr)->sin_port); 282 | 283 | #ifndef NO_IPV6 284 | case AF_INET6: 285 | return ntohs (reinterpret_cast (&m_addr)->sin6_port); 286 | #endif 287 | 288 | default: 289 | std::abort (); 290 | } 291 | } 292 | 293 | void SockAddr::setPort (std::uint16_t const port_) 294 | { 295 | switch (m_addr.ss_family) 296 | { 297 | case AF_INET: 298 | reinterpret_cast (&m_addr)->sin_port = htons (port_); 299 | break; 300 | 301 | #ifndef NO_IPV6 302 | case AF_INET6: 303 | reinterpret_cast (&m_addr)->sin6_port = htons (port_); 304 | break; 305 | #endif 306 | 307 | default: 308 | std::abort (); 309 | } 310 | } 311 | 312 | SockAddr::Domain SockAddr::domain () const 313 | { 314 | switch (m_addr.ss_family) 315 | { 316 | case AF_INET: 317 | #ifndef NO_IPV6 318 | case AF_INET6: 319 | #endif 320 | return static_cast (m_addr.ss_family); 321 | 322 | default: 323 | std::abort (); 324 | } 325 | } 326 | 327 | socklen_t SockAddr::size () const 328 | { 329 | switch (m_addr.ss_family) 330 | { 331 | case AF_INET: 332 | return sizeof (sockaddr_in); 333 | 334 | #ifndef NO_IPV6 335 | case AF_INET6: 336 | return sizeof (sockaddr_in6); 337 | #endif 338 | 339 | default: 340 | std::abort (); 341 | } 342 | } 343 | 344 | char const *SockAddr::name (char *buffer_, std::size_t size_) const 345 | { 346 | switch (m_addr.ss_family) 347 | { 348 | case AF_INET: 349 | #ifdef __NDS__ 350 | (void)buffer_; 351 | (void)size_; 352 | return inet_ntoa (reinterpret_cast (&m_addr)->sin_addr); 353 | #else 354 | return inet_ntop ( 355 | AF_INET, &reinterpret_cast (&m_addr)->sin_addr, buffer_, size_); 356 | #endif 357 | 358 | #ifndef NO_IPV6 359 | case AF_INET6: 360 | return inet_ntop ( 361 | AF_INET6, &reinterpret_cast (&m_addr)->sin6_addr, buffer_, size_); 362 | #endif 363 | 364 | default: 365 | std::abort (); 366 | } 367 | } 368 | 369 | char const *SockAddr::name () const 370 | { 371 | #if defined(__NDS__) || defined(__WIIU__) 372 | return inet_ntoa (reinterpret_cast (&m_addr)->sin_addr); 373 | #else 374 | #ifdef NO_IPV6 375 | thread_local static char buffer[INET_ADDRSTRLEN]; 376 | #else 377 | thread_local static char buffer[INET6_ADDRSTRLEN]; 378 | #endif 379 | 380 | return name (buffer, sizeof (buffer)); 381 | #endif 382 | } 383 | -------------------------------------------------------------------------------- /source/socket.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2024 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "socket.h" 22 | #include "log.h" 23 | #include "platform.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | using namespace std::chrono_literals; 37 | 38 | /////////////////////////////////////////////////////////////////////////// 39 | Socket::~Socket () 40 | { 41 | if (m_listening) 42 | info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ()); 43 | 44 | if (m_connected) 45 | info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ()); 46 | 47 | #ifdef __NDS__ 48 | if (::closesocket (m_fd) != 0) 49 | error ("closesocket: %s\n", std::strerror (errno)); 50 | #else 51 | if (::close (m_fd) != 0) 52 | error ("close: %s\n", std::strerror (errno)); 53 | #endif 54 | } 55 | 56 | Socket::Socket (int const fd_) : m_fd (fd_), m_listening (false), m_connected (false) 57 | { 58 | } 59 | 60 | Socket::Socket (int const fd_, SockAddr const &sockName_, SockAddr const &peerName_) 61 | : m_sockName (sockName_), 62 | m_peerName (peerName_), 63 | m_fd (fd_), 64 | m_listening (false), 65 | m_connected (true) 66 | { 67 | } 68 | 69 | UniqueSocket Socket::accept () 70 | { 71 | SockAddr addr; 72 | socklen_t addrLen = sizeof (sockaddr_storage); 73 | 74 | auto const fd = ::accept (m_fd, addr, &addrLen); 75 | if (fd < 0) 76 | { 77 | error ("accept: %s\n", std::strerror (errno)); 78 | return nullptr; 79 | } 80 | 81 | info ("Accepted connection from [%s]:%u\n", addr.name (), addr.port ()); 82 | return UniqueSocket (new Socket (fd, m_sockName, addr)); 83 | } 84 | 85 | int Socket::atMark () 86 | { 87 | #ifdef __NDS__ 88 | errno = ENOSYS; 89 | return -1; 90 | #else 91 | auto const rc = ::sockatmark (m_fd); 92 | 93 | if (rc < 0) 94 | error ("sockatmark: %s\n", std::strerror (errno)); 95 | 96 | return rc; 97 | #endif 98 | } 99 | 100 | bool Socket::bind (SockAddr const &addr_) 101 | { 102 | if (::bind (m_fd, addr_, addr_.size ()) != 0) 103 | { 104 | error ("bind: %s\n", std::strerror (errno)); 105 | return false; 106 | } 107 | 108 | if (addr_.port () == 0) 109 | { 110 | // get socket name due to request for ephemeral port 111 | socklen_t addrLen = sizeof (sockaddr_storage); 112 | if (::getsockname (m_fd, m_sockName, &addrLen) != 0) 113 | error ("getsockname: %s\n", std::strerror (errno)); 114 | } 115 | else 116 | m_sockName = addr_; 117 | 118 | return true; 119 | } 120 | 121 | bool Socket::connect (SockAddr const &addr_) 122 | { 123 | if (::connect (m_fd, addr_, addr_.size ()) != 0) 124 | { 125 | if (errno != EINPROGRESS) 126 | error ("connect: %s\n", std::strerror (errno)); 127 | else 128 | { 129 | m_peerName = addr_; 130 | m_connected = true; 131 | info ("Connecting to [%s]:%u\n", addr_.name (), addr_.port ()); 132 | } 133 | return false; 134 | } 135 | 136 | m_peerName = addr_; 137 | m_connected = true; 138 | info ("Connected to [%s]:%u\n", addr_.name (), addr_.port ()); 139 | return true; 140 | } 141 | 142 | bool Socket::listen (int const backlog_) 143 | { 144 | if (::listen (m_fd, backlog_) != 0) 145 | { 146 | error ("listen: %s\n", std::strerror (errno)); 147 | return false; 148 | } 149 | 150 | m_listening = true; 151 | return true; 152 | } 153 | 154 | bool Socket::shutdown (int const how_) 155 | { 156 | if (::shutdown (m_fd, how_) != 0) 157 | { 158 | error ("shutdown: %s\n", std::strerror (errno)); 159 | return false; 160 | } 161 | 162 | return true; 163 | } 164 | 165 | bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_) 166 | { 167 | #ifdef __NDS__ 168 | (void)enable_; 169 | (void)time_; 170 | errno = ENOSYS; 171 | return -1; 172 | #else 173 | linger linger; 174 | linger.l_onoff = enable_; 175 | linger.l_linger = time_.count (); 176 | 177 | auto const rc = ::setsockopt (m_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof (linger)); 178 | if (rc != 0) 179 | { 180 | error ("setsockopt(SO_LINGER, %s, %lus): %s\n", 181 | enable_ ? "on" : "off", 182 | static_cast (time_.count ()), 183 | std::strerror (errno)); 184 | return false; 185 | } 186 | 187 | return true; 188 | #endif 189 | } 190 | 191 | bool Socket::setNonBlocking (bool const nonBlocking_) 192 | { 193 | #ifdef __NDS__ 194 | unsigned long enable = nonBlocking_; 195 | 196 | auto const rc = ::ioctl (m_fd, FIONBIO, &enable); 197 | if (rc != 0) 198 | { 199 | error ("fcntl(FIONBIO, %d): %s\n", nonBlocking_, std::strerror (errno)); 200 | return false; 201 | } 202 | #else 203 | auto flags = ::fcntl (m_fd, F_GETFL, 0); 204 | if (flags == -1) 205 | { 206 | error ("fcntl(F_GETFL): %s\n", std::strerror (errno)); 207 | return false; 208 | } 209 | 210 | if (nonBlocking_) 211 | flags |= O_NONBLOCK; 212 | else 213 | flags &= ~O_NONBLOCK; 214 | 215 | if (::fcntl (m_fd, F_SETFL, flags) != 0) 216 | { 217 | error ("fcntl(F_SETFL, %d): %s\n", flags, std::strerror (errno)); 218 | return false; 219 | } 220 | #endif 221 | 222 | return true; 223 | } 224 | 225 | bool Socket::setWinScale (const int val) 226 | { 227 | int const o = val; 228 | 229 | if (::setsockopt (m_fd, SOL_SOCKET, SO_WINSCALE, &o, sizeof (o)) < 0) 230 | { 231 | error ("setsockopt(SO_WINSCALE, %d): %s\n", val, std::strerror (errno)); 232 | return false; 233 | } 234 | 235 | return true; 236 | } 237 | 238 | bool Socket::setReuseAddress (bool const reuse_) 239 | { 240 | int const reuse = reuse_; 241 | if (::setsockopt (m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) != 0) 242 | { 243 | error ("setsockopt(SO_REUSEADDR, %s): %s\n", reuse_ ? "yes" : "no", std::strerror (errno)); 244 | return false; 245 | } 246 | 247 | return true; 248 | } 249 | 250 | bool Socket::setRecvBufferSize (std::size_t const size_) 251 | { 252 | int const size = size_; 253 | if (::setsockopt (m_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size)) != 0) 254 | { 255 | error ("setsockopt(SO_RCVBUF, %zu): %s\n", size_, std::strerror (errno)); 256 | return false; 257 | } 258 | 259 | return true; 260 | } 261 | 262 | bool Socket::setSendBufferSize (std::size_t const size_) 263 | { 264 | int const size = size_; 265 | if (::setsockopt (m_fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof (size)) != 0) 266 | { 267 | error ("setsockopt(SO_SNDBUF, %zu): %s\n", size_, std::strerror (errno)); 268 | return false; 269 | } 270 | 271 | return true; 272 | } 273 | 274 | #ifndef __NDS__ 275 | bool Socket::joinMulticastGroup (SockAddr const &addr_, SockAddr const &iface_) 276 | { 277 | ip_mreq group; 278 | group.imr_multiaddr = static_cast (addr_).sin_addr; 279 | group.imr_interface = static_cast (iface_).sin_addr; 280 | 281 | if (::setsockopt (m_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof (group)) != 0) 282 | { 283 | error ("setsockopt(IP_ADD_MEMBERSHIP, %s): %s\n", addr_.name (), std::strerror (errno)); 284 | return false; 285 | } 286 | 287 | return true; 288 | } 289 | 290 | bool Socket::dropMulticastGroup (SockAddr const &addr_, SockAddr const &iface_) 291 | { 292 | ip_mreq group; 293 | group.imr_multiaddr = static_cast (addr_).sin_addr; 294 | group.imr_interface = static_cast (iface_).sin_addr; 295 | 296 | if (::setsockopt (m_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, sizeof (group)) != 0) 297 | { 298 | error ("setsockopt(IP_DROP_MEMBERSHIP, %s): %s\n", addr_.name (), std::strerror (errno)); 299 | return false; 300 | } 301 | 302 | return true; 303 | } 304 | #endif 305 | 306 | std::make_signed_t 307 | Socket::read (void *const buffer_, std::size_t const size_, bool const oob_) 308 | { 309 | assert (buffer_); 310 | assert (size_); 311 | 312 | auto const rc = ::recv (m_fd, buffer_, size_, oob_ ? MSG_OOB : 0); 313 | if (rc < 0 && errno != EWOULDBLOCK) 314 | error ("recv: %s\n", std::strerror (errno)); 315 | 316 | return rc; 317 | } 318 | 319 | std::make_signed_t Socket::read (IOBuffer &buffer_, bool const oob_) 320 | { 321 | assert (buffer_.freeSize () > 0); 322 | 323 | auto const rc = read (buffer_.freeArea (), buffer_.freeSize (), oob_); 324 | if (rc > 0) 325 | buffer_.markUsed (rc); 326 | 327 | return rc; 328 | } 329 | 330 | std::make_signed_t 331 | Socket::readFrom (void *const buffer_, std::size_t const size_, SockAddr &addr_) 332 | { 333 | assert (buffer_); 334 | assert (size_); 335 | 336 | socklen_t addrLen = sizeof (sockaddr_storage); 337 | 338 | auto const rc = ::recvfrom (m_fd, buffer_, size_, 0, addr_, &addrLen); 339 | if (rc < 0 && errno != EWOULDBLOCK) 340 | error ("recvfrom: %s\n", std::strerror (errno)); 341 | 342 | return rc; 343 | } 344 | 345 | std::make_signed_t Socket::write (void const *const buffer_, std::size_t const size_) 346 | { 347 | assert (buffer_); 348 | assert (size_ > 0); 349 | 350 | auto const rc = ::send (m_fd, buffer_, size_, 0); 351 | if (rc < 0 && errno != EWOULDBLOCK) 352 | error ("send: %s\n", std::strerror (errno)); 353 | 354 | return rc; 355 | } 356 | 357 | std::make_signed_t Socket::write (IOBuffer &buffer_) 358 | { 359 | assert (buffer_.usedSize () > 0); 360 | 361 | auto const rc = write (buffer_.usedArea (), buffer_.usedSize ()); 362 | if (rc > 0) 363 | buffer_.markFree (rc); 364 | 365 | return rc; 366 | } 367 | 368 | std::make_signed_t 369 | Socket::writeTo (void const *buffer_, std::size_t size_, SockAddr const &addr_) 370 | { 371 | assert (buffer_); 372 | assert (size_ > 0); 373 | 374 | auto const rc = ::sendto (m_fd, buffer_, size_, 0, addr_, addr_.size ()); 375 | if (rc < 0 && errno != EWOULDBLOCK) 376 | error ("sendto: %s\n", std::strerror (errno)); 377 | 378 | return rc; 379 | } 380 | 381 | SockAddr const &Socket::sockName () const 382 | { 383 | return m_sockName; 384 | } 385 | 386 | SockAddr const &Socket::peerName () const 387 | { 388 | return m_peerName; 389 | } 390 | 391 | UniqueSocket Socket::create (Type const type_) 392 | { 393 | auto const fd = ::socket (AF_INET, static_cast (type_), 0); 394 | if (fd < 0) 395 | { 396 | error ("socket: %s\n", std::strerror (errno)); 397 | return nullptr; 398 | } 399 | 400 | return UniqueSocket (new Socket (fd)); 401 | } 402 | 403 | int Socket::poll (PollInfo *const info_, 404 | std::size_t const count_, 405 | std::chrono::milliseconds const timeout_) 406 | { 407 | if (count_ == 0) 408 | return 0; 409 | 410 | auto const pfd = std::make_unique (count_); 411 | for (std::size_t i = 0; i < count_; ++i) 412 | { 413 | pfd[i].fd = info_[i].socket.get ().m_fd; 414 | pfd[i].events = info_[i].events; 415 | pfd[i].revents = 0; 416 | } 417 | 418 | auto const rc = ::poll (pfd.get (), count_, timeout_.count ()); 419 | if (rc < 0) 420 | { 421 | error ("poll: %s\n", std::strerror (errno)); 422 | return rc; 423 | } 424 | 425 | for (std::size_t i = 0; i < count_; ++i) 426 | info_[i].revents = pfd[i].revents; 427 | 428 | return rc; 429 | } 430 | 431 | #ifdef __NDS__ 432 | extern "C" int poll (pollfd *const fds_, nfds_t const nfds_, int const timeout_) 433 | { 434 | fd_set readFds; 435 | fd_set writeFds; 436 | fd_set exceptFds; 437 | 438 | FD_ZERO (&readFds); 439 | FD_ZERO (&writeFds); 440 | FD_ZERO (&exceptFds); 441 | 442 | for (nfds_t i = 0; i < nfds_; ++i) 443 | { 444 | if (fds_[i].events & POLLIN) 445 | FD_SET (fds_[i].fd, &readFds); 446 | if (fds_[i].events & POLLOUT) 447 | FD_SET (fds_[i].fd, &writeFds); 448 | } 449 | 450 | timeval tv; 451 | tv.tv_sec = timeout_ / 1000; 452 | tv.tv_usec = (timeout_ % 1000) * 1000; 453 | auto const rc = ::select (nfds_, &readFds, &writeFds, &exceptFds, &tv); 454 | if (rc < 0) 455 | return rc; 456 | 457 | int count = 0; 458 | for (nfds_t i = 0; i < nfds_; ++i) 459 | { 460 | bool counted = false; 461 | fds_[i].revents = 0; 462 | 463 | if (FD_ISSET (fds_[i].fd, &readFds)) 464 | { 465 | counted = true; 466 | fds_[i].revents |= POLLIN; 467 | } 468 | 469 | if (FD_ISSET (fds_[i].fd, &writeFds)) 470 | { 471 | counted = true; 472 | fds_[i].revents |= POLLOUT; 473 | } 474 | 475 | if (FD_ISSET (fds_[i].fd, &exceptFds)) 476 | { 477 | counted = true; 478 | fds_[i].revents |= POLLERR; 479 | } 480 | 481 | if (counted) 482 | ++count; 483 | } 484 | 485 | return count; 486 | } 487 | #endif 488 | -------------------------------------------------------------------------------- /source/wiiu/logger.c: -------------------------------------------------------------------------------- 1 | #ifdef DEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | uint32_t moduleLogInit = false; 8 | uint32_t cafeLogInit = false; 9 | uint32_t udpLogInit = false; 10 | #endif // DEBUG 11 | 12 | void initLogging () 13 | { 14 | #ifdef DEBUG 15 | if (!(moduleLogInit = WHBLogModuleInit ())) 16 | { 17 | cafeLogInit = WHBLogCafeInit (); 18 | udpLogInit = WHBLogUdpInit (); 19 | } 20 | #endif // DEBUG 21 | } 22 | 23 | void deinitLogging () 24 | { 25 | #ifdef DEBUG 26 | if (moduleLogInit) 27 | { 28 | WHBLogModuleDeinit (); 29 | moduleLogInit = false; 30 | } 31 | if (cafeLogInit) 32 | { 33 | WHBLogCafeDeinit (); 34 | cafeLogInit = false; 35 | } 36 | if (udpLogInit) 37 | { 38 | WHBLogUdpDeinit (); 39 | udpLogInit = false; 40 | } 41 | #endif // DEBUG 42 | } -------------------------------------------------------------------------------- /source/wiiu/logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define LOG_APP_TYPE "P" 12 | #define LOG_APP_NAME "ftpiiu_plugin" 13 | 14 | #define __FILENAME_X__ (strrchr (__FILE__, '\\') ? strrchr (__FILE__, '\\') + 1 : __FILE__) 15 | #define __FILENAME__ (strrchr (__FILE__, '/') ? strrchr (__FILE__, '/') + 1 : __FILENAME_X__) 16 | 17 | #define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX (LOG_FUNC, "", "", FMT, ##ARGS) 18 | 19 | #define LOG_EX(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \ 20 | do \ 21 | { \ 22 | LOG_FUNC ("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, \ 23 | LOG_APP_TYPE, \ 24 | LOG_APP_NAME, \ 25 | __FILENAME__, \ 26 | __FUNCTION__, \ 27 | __LINE__, \ 28 | ##ARGS); \ 29 | } while (0) 30 | 31 | #ifdef DEBUG 32 | 33 | #ifdef VERBOSE_DEBUG 34 | #define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) LOG (WHBLogPrintf, FMT, ##ARGS) 35 | #else 36 | #define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) 37 | #endif 38 | 39 | #define DEBUG_FUNCTION_LINE(FMT, ARGS...) LOG (WHBLogPrintf, FMT, ##ARGS) 40 | 41 | #define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG (WHBLogWritef, FMT, ##ARGS) 42 | 43 | #define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX (WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS) 44 | #define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX (WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS) 45 | #define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX (WHBLogPrintf, "##INFO ## ", "", FMT, ##ARGS) 46 | 47 | #else 48 | 49 | #define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) 50 | 51 | #define DEBUG_FUNCTION_LINE(FMT, ARGS...) while (0) 52 | 53 | #define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0) 54 | 55 | #define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX (OSReport, "##ERROR## ", "\n", FMT, ##ARGS) 56 | #define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX (OSReport, "##WARN ## ", "\n", FMT, ##ARGS) 57 | #define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX (OSReport, "##INFO ## ", "\n", FMT, ##ARGS) 58 | 59 | #endif 60 | 61 | void initLogging (); 62 | 63 | void deinitLogging (); 64 | 65 | #ifdef __cplusplus 66 | } 67 | #endif 68 | -------------------------------------------------------------------------------- /source/wiiu/platform.cpp: -------------------------------------------------------------------------------- 1 | // ftpd is a server implementation based on the following: 2 | // - RFC 959 (https://tools.ietf.org/html/rfc959) 3 | // - RFC 3659 (https://tools.ietf.org/html/rfc3659) 4 | // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html 5 | // 6 | // Copyright (C) 2020 Michael Theall 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | 21 | #include "platform.h" 22 | #include "version.h" 23 | 24 | #include "IOAbstraction.h" 25 | #include "ftpServer.h" 26 | #include "log.h" 27 | #include "logger.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #ifndef CLASSIC 43 | #error "Wii U must be built in classic mode" 44 | #endif 45 | #define VERSION "v0.4.4" 46 | #define VERSION_FULL VERSION VERSION_EXTRA 47 | 48 | WUPS_PLUGIN_NAME ("ftpiiu"); 49 | WUPS_PLUGIN_DESCRIPTION ("FTP Server based on ftpd"); 50 | WUPS_PLUGIN_VERSION (VERSION_FULL); 51 | WUPS_PLUGIN_AUTHOR ("mtheall, Maschell"); 52 | WUPS_PLUGIN_LICENSE ("GPL3"); 53 | 54 | WUPS_USE_WUT_DEVOPTAB (); 55 | WUPS_USE_STORAGE ("ftpiiu"); // Unique id for the storage api 56 | 57 | #define DEFAULT_FTPIIU_ENABLED_VALUE true 58 | #define DEFAULT_SYSTEM_FILES_ALLOWED_VALUE false 59 | 60 | #define FTPIIU_ENABLED_STRING "enabled" 61 | #define SYSTEM_FILES_ALLOWED_STRING "systemFilesAllowed" 62 | 63 | bool platform::networkVisible () 64 | { 65 | return true; 66 | } 67 | 68 | bool platform::networkAddress (SockAddr &addr_) 69 | { 70 | struct sockaddr_in addr = {}; 71 | addr.sin_family = AF_INET; 72 | nn::ac::GetAssignedAddress (&addr.sin_addr.s_addr); 73 | addr_ = addr; 74 | return true; 75 | } 76 | 77 | MochaUtilsStatus MountWrapper (const char *mount, const char *dev, const char *mountTo) 78 | { 79 | auto res = Mocha_MountFS (mount, dev, mountTo); 80 | if (res == MOCHA_RESULT_ALREADY_EXISTS) 81 | { 82 | res = Mocha_MountFS (mount, nullptr, mountTo); 83 | } 84 | if (res == MOCHA_RESULT_SUCCESS) 85 | { 86 | std::string mountPath = std::string (mount) + ":/"; 87 | debug ("Mounted %s", mountPath.c_str ()); 88 | } 89 | else 90 | { 91 | DEBUG_FUNCTION_LINE_ERR ( 92 | "Failed to mount %s: %s [%d]", mount, Mocha_GetStatusStr (res), res); 93 | } 94 | return res; 95 | } 96 | 97 | UniqueFtpServer server = nullptr; 98 | static bool sSystemFilesAllowed = DEFAULT_SYSTEM_FILES_ALLOWED_VALUE; 99 | static bool sMochaPathsWereMounted = false; 100 | static bool sFTPServerEnabled = DEFAULT_FTPIIU_ENABLED_VALUE; 101 | 102 | void start_server () 103 | { 104 | if (server != nullptr) 105 | { 106 | return; 107 | } 108 | 109 | MochaUtilsStatus res; 110 | if ((res = Mocha_InitLibrary ()) == MOCHA_RESULT_SUCCESS) 111 | { 112 | std::vector virtualDirsInRoot; 113 | if (sSystemFilesAllowed) 114 | { 115 | if (MountWrapper ("slccmpt01", "/dev/slccmpt01", "/vol/storage_slccmpt01") == 116 | MOCHA_RESULT_SUCCESS) 117 | { 118 | virtualDirsInRoot.emplace_back ("slccmpt01"); 119 | IOAbstraction::addVirtualPath ("slccmpt01:/", {}); 120 | } 121 | if (MountWrapper ("storage_odd_tickets", nullptr, "/vol/storage_odd01") == 122 | MOCHA_RESULT_SUCCESS) 123 | { 124 | virtualDirsInRoot.emplace_back ("storage_odd_tickets"); 125 | IOAbstraction::addVirtualPath ("storage_odd_tickets:/", {}); 126 | } 127 | if (MountWrapper ("storage_odd_updates", nullptr, "/vol/storage_odd02") == 128 | MOCHA_RESULT_SUCCESS) 129 | { 130 | virtualDirsInRoot.emplace_back ("storage_odd_updates"); 131 | IOAbstraction::addVirtualPath ("storage_odd_updates:/", {}); 132 | } 133 | if (MountWrapper ("storage_odd_content", nullptr, "/vol/storage_odd03") == 134 | MOCHA_RESULT_SUCCESS) 135 | { 136 | virtualDirsInRoot.emplace_back ("storage_odd_content"); 137 | IOAbstraction::addVirtualPath ("storage_odd_content:/", {}); 138 | } 139 | if (MountWrapper ("storage_odd_content2", nullptr, "/vol/storage_odd04") == 140 | MOCHA_RESULT_SUCCESS) 141 | { 142 | virtualDirsInRoot.emplace_back ("storage_odd_content2"); 143 | IOAbstraction::addVirtualPath ("storage_odd_content2:/", {}); 144 | } 145 | if (MountWrapper ("storage_slc", "/dev/slc01", "/vol/storage_slc01") == 146 | MOCHA_RESULT_SUCCESS) 147 | { 148 | virtualDirsInRoot.emplace_back ("storage_slc"); 149 | IOAbstraction::addVirtualPath ("storage_slc:/", {}); 150 | } 151 | if (Mocha_MountFS ("storage_mlc", nullptr, "/vol/storage_mlc01") == 152 | MOCHA_RESULT_SUCCESS) 153 | { 154 | virtualDirsInRoot.emplace_back ("storage_mlc"); 155 | IOAbstraction::addVirtualPath ("storage_mlc:/", {}); 156 | } 157 | if (Mocha_MountFS ("storage_usb", nullptr, "/vol/storage_usb01") == 158 | MOCHA_RESULT_SUCCESS) 159 | { 160 | virtualDirsInRoot.emplace_back ("storage_usb"); 161 | IOAbstraction::addVirtualPath ("storage_usb:/", {}); 162 | } 163 | 164 | sMochaPathsWereMounted = true; 165 | } 166 | virtualDirsInRoot.emplace_back ("fs"); 167 | IOAbstraction::addVirtualPath (":/", virtualDirsInRoot); 168 | IOAbstraction::addVirtualPath ("fs:/", std::vector{"vol"}); 169 | IOAbstraction::addVirtualPath ( 170 | "fs:/vol", std::vector{"external01", "content", "save"}); 171 | 172 | IOAbstraction::addVirtualPath ("fs:/vol/content", {}); 173 | 174 | std::vector virtualDirsInSave; 175 | virtualDirsInSave.emplace_back ("common"); 176 | nn::act::Initialize (); 177 | for (int32_t i = 0; i < 13; i++) 178 | { 179 | if (!nn::act::IsSlotOccupied (i)) 180 | { 181 | continue; 182 | } 183 | char buffer[9]; 184 | snprintf (buffer, sizeof (buffer), "%08X", nn::act::GetPersistentIdEx (i)); 185 | virtualDirsInSave.emplace_back (buffer); 186 | } 187 | nn::act::Finalize (); 188 | IOAbstraction::addVirtualPath ("fs:/vol/save", virtualDirsInSave); 189 | } 190 | else 191 | { 192 | DEBUG_FUNCTION_LINE_ERR ( 193 | "Failed to init libmocha: %s [%d]\n", Mocha_GetStatusStr (res), res); 194 | } 195 | 196 | server = FtpServer::create (); 197 | } 198 | 199 | void stop_server () 200 | { 201 | server.reset (); 202 | if (sMochaPathsWereMounted) 203 | { 204 | Mocha_UnmountFS ("slccmpt01"); 205 | Mocha_UnmountFS ("storage_odd_tickets"); 206 | Mocha_UnmountFS ("storage_odd_updates"); 207 | Mocha_UnmountFS ("storage_odd_content"); 208 | Mocha_UnmountFS ("storage_odd_content2"); 209 | Mocha_UnmountFS ("storage_slc"); 210 | Mocha_UnmountFS ("storage_mlc"); 211 | Mocha_UnmountFS ("storage_usb"); 212 | sMochaPathsWereMounted = false; 213 | } 214 | 215 | IOAbstraction::clear (); 216 | } 217 | 218 | static void gFTPServerRunningChanged (ConfigItemBoolean *item, bool newValue) 219 | { 220 | sFTPServerEnabled = newValue; 221 | if (!sFTPServerEnabled) 222 | { 223 | stop_server (); 224 | } 225 | else 226 | { 227 | start_server (); 228 | } 229 | // If the value has changed, we store it in the storage. 230 | auto res = WUPSStorageAPI::Store (FTPIIU_ENABLED_STRING, sFTPServerEnabled); 231 | if (res != WUPS_STORAGE_ERROR_SUCCESS) 232 | { 233 | DEBUG_FUNCTION_LINE_ERR ("Failed to store gFTPServerEnabled: %s (%d)\n", 234 | WUPSStorageAPI::GetStatusStr (res).data (), 235 | res); 236 | } 237 | } 238 | 239 | static void gSystemFilesAllowedChanged (ConfigItemBoolean *item, bool newValue) 240 | { 241 | // DEBUG_FUNCTION_LINE("New value in gFTPServerEnabled: %d", newValue); 242 | if (server != nullptr) 243 | { // If the server is already running we need to restart it. 244 | stop_server (); 245 | sSystemFilesAllowed = newValue; 246 | start_server (); 247 | } 248 | else 249 | { 250 | sSystemFilesAllowed = newValue; 251 | } 252 | // If the value has changed, we store it in the storage. 253 | auto res = WUPSStorageAPI::Store (SYSTEM_FILES_ALLOWED_STRING, sSystemFilesAllowed); 254 | if (res != WUPS_STORAGE_ERROR_SUCCESS) 255 | { 256 | DEBUG_FUNCTION_LINE_ERR ("Failed to store gSystemFilesAllowed: %s (%d)\n", 257 | WUPSStorageAPI::GetStatusStr (res).data (), 258 | res); 259 | } 260 | } 261 | 262 | WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback (WUPSConfigCategoryHandle rootHandle) 263 | { 264 | uint32_t hostIpAddress = 0; 265 | nn::ac::GetAssignedAddress (&hostIpAddress); 266 | try 267 | { 268 | WUPSConfigCategory root = WUPSConfigCategory (rootHandle); 269 | root.add (WUPSConfigItemBoolean::Create (FTPIIU_ENABLED_STRING, 270 | "Enable ftpd", 271 | true, 272 | sFTPServerEnabled, 273 | &gFTPServerRunningChanged)); 274 | 275 | root.add (WUPSConfigItemBoolean::Create (SYSTEM_FILES_ALLOWED_STRING, 276 | "Allow access to system files", 277 | false, 278 | sSystemFilesAllowed, 279 | &gSystemFilesAllowedChanged)); 280 | 281 | root.add (WUPSConfigItemStub::Create ("===")); 282 | 283 | char ipSettings[50]; 284 | if (hostIpAddress != 0) 285 | { 286 | snprintf (ipSettings, 287 | 50, 288 | "IP of your console is %u.%u.%u.%u. Port %i", 289 | (hostIpAddress >> 24) & 0xFF, 290 | (hostIpAddress >> 16) & 0xFF, 291 | (hostIpAddress >> 8) & 0xFF, 292 | (hostIpAddress >> 0) & 0xFF, 293 | 21); 294 | } 295 | else 296 | { 297 | snprintf ( 298 | ipSettings, sizeof (ipSettings), "The console is not connected to a network."); 299 | } 300 | 301 | root.add (WUPSConfigItemStub::Create (ipSettings)); 302 | root.add (WUPSConfigItemStub::Create ("You can connect with empty credentials")); 303 | } 304 | catch (std::exception &e) 305 | { 306 | OSReport ("fptiiu plugin: Exception: %s\n", e.what ()); 307 | return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; 308 | } 309 | 310 | return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS; 311 | } 312 | 313 | void ConfigMenuClosedCallback () 314 | { 315 | WUPSStorageAPI::SaveStorage (); 316 | } 317 | 318 | INITIALIZE_PLUGIN () 319 | { 320 | WUPSConfigAPIOptionsV1 configOptions = {.name = "ftpiiu"}; 321 | if (WUPSConfigAPI_Init (configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) != 322 | WUPSCONFIG_API_RESULT_SUCCESS) 323 | { 324 | DEBUG_FUNCTION_LINE_ERR ("Failed to init config api"); 325 | OSFatal ("ftpiiu plugin: Failed to init config api"); 326 | } 327 | 328 | WUPSStorageError err; 329 | if ((err = WUPSStorageAPI::GetOrStoreDefault ( 330 | FTPIIU_ENABLED_STRING, sFTPServerEnabled, DEFAULT_FTPIIU_ENABLED_VALUE)) != 331 | WUPS_STORAGE_ERROR_SUCCESS) 332 | { 333 | DEBUG_FUNCTION_LINE_ERR ("Failed to get or create item \"%s\": %s (%d)\n", 334 | FTPIIU_ENABLED_STRING, 335 | WUPSStorageAPI_GetStatusStr (err), 336 | err); 337 | } 338 | if ((err = WUPSStorageAPI::GetOrStoreDefault (SYSTEM_FILES_ALLOWED_STRING, 339 | sSystemFilesAllowed, 340 | DEFAULT_SYSTEM_FILES_ALLOWED_VALUE)) != WUPS_STORAGE_ERROR_SUCCESS) 341 | { 342 | DEBUG_FUNCTION_LINE_ERR ("Failed to get or create item \"%s\": %s (%d)\n", 343 | SYSTEM_FILES_ALLOWED_STRING, 344 | WUPSStorageAPI_GetStatusStr (err), 345 | err); 346 | } 347 | 348 | if ((err = WUPSStorageAPI::SaveStorage ()) != WUPS_STORAGE_ERROR_SUCCESS) 349 | { 350 | DEBUG_FUNCTION_LINE_ERR ( 351 | "Failed to save storage: %s (%d)\n", WUPSStorageAPI_GetStatusStr (err), err); 352 | } 353 | } 354 | 355 | void wiiu_init () 356 | { 357 | nn::ac::Initialize (); 358 | nn::ac::ConnectAsync (); 359 | if (sFTPServerEnabled) 360 | { 361 | start_server (); 362 | } 363 | } 364 | 365 | ON_APPLICATION_START () 366 | { 367 | initLogging (); 368 | nn::ac::Initialize (); 369 | nn::ac::ConnectAsync (); 370 | 371 | wiiu_init (); 372 | } 373 | 374 | ON_APPLICATION_ENDS () 375 | { 376 | stop_server (); 377 | deinitLogging (); 378 | } 379 | 380 | bool platform::init () 381 | { 382 | WHBProcInit (); 383 | wiiu_init (); 384 | return true; 385 | } 386 | 387 | bool platform::loop () 388 | { 389 | return WHBProcIsRunning (); 390 | } 391 | 392 | void platform::render () 393 | { 394 | } 395 | 396 | void platform::exit () 397 | { 398 | IOAbstraction::clear (); 399 | WHBProcShutdown (); 400 | } 401 | 402 | /////////////////////////////////////////////////////////////////////////// 403 | /// \brief Platform thread pimpl 404 | class platform::Thread::privateData_t 405 | { 406 | public: 407 | privateData_t () = default; 408 | 409 | /// \brief Parameterized constructor 410 | /// \param func_ Thread entry point 411 | explicit privateData_t (std::function &&func_) : thread (std::move (func_)) 412 | { 413 | auto nativeHandle = (OSThread *)thread.native_handle (); 414 | OSSetThreadName (nativeHandle, "ftpiiu"); 415 | while (!OSSetThreadAffinity (nativeHandle, OS_THREAD_ATTRIB_AFFINITY_CPU2)) 416 | { 417 | OSSleepTicks (OSMillisecondsToTicks (16)); 418 | } 419 | while (!OSSetThreadPriority (nativeHandle, 16)) 420 | { 421 | OSSleepTicks (OSMillisecondsToTicks (16)); 422 | } 423 | } 424 | 425 | /// \brief Underlying thread 426 | std::thread thread; 427 | }; 428 | 429 | /////////////////////////////////////////////////////////////////////////// 430 | platform::Thread::~Thread () = default; 431 | 432 | platform::Thread::Thread () : m_d (new privateData_t ()) 433 | { 434 | } 435 | 436 | platform::Thread::Thread (std::function &&func_) 437 | : m_d (new privateData_t (std::move (func_))) 438 | { 439 | } 440 | 441 | platform::Thread::Thread (Thread &&that_) : m_d (new privateData_t ()) 442 | { 443 | std::swap (m_d, that_.m_d); 444 | } 445 | 446 | platform::Thread &platform::Thread::operator= (Thread &&that_) 447 | { 448 | std::swap (m_d, that_.m_d); 449 | return *this; 450 | } 451 | 452 | void platform::Thread::join () 453 | { 454 | m_d->thread.join (); 455 | } 456 | 457 | void platform::Thread::sleep (std::chrono::milliseconds const timeout_) 458 | { 459 | std::this_thread::sleep_for (timeout_); 460 | } 461 | 462 | std::string const &platform::hostname () 463 | { 464 | static std::string const hostname = "wiiu-ftpd"; 465 | return hostname; 466 | } 467 | 468 | /////////////////////////////////////////////////////////////////////////// 469 | #define USE_STD_MUTEX 1 470 | 471 | /// \brief Platform mutex pimpl 472 | class platform::Mutex::privateData_t 473 | { 474 | public: 475 | #if USE_STD_MUTEX 476 | /// \brief Underlying mutex 477 | std::mutex mutex; 478 | #else 479 | /// \brief Underlying mutex 480 | ::Mutex mutex; 481 | #endif 482 | }; 483 | 484 | /////////////////////////////////////////////////////////////////////////// 485 | platform::Mutex::~Mutex () = default; 486 | 487 | platform::Mutex::Mutex () : m_d (new privateData_t ()) 488 | { 489 | #if !USE_STD_MUTEX 490 | mutexInit (&m_d->mutex); 491 | #endif 492 | } 493 | 494 | void platform::Mutex::lock () 495 | { 496 | #if USE_STD_MUTEX 497 | m_d->mutex.lock (); 498 | #else 499 | mutexLock (&m_d->mutex); 500 | #endif 501 | } 502 | 503 | void platform::Mutex::unlock () 504 | { 505 | #if USE_STD_MUTEX 506 | m_d->mutex.unlock (); 507 | #else 508 | mutexUnlock (&m_d->mutex); 509 | #endif 510 | } 511 | -------------------------------------------------------------------------------- /source/wiiu/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define VERSION_EXTRA "" --------------------------------------------------------------------------------