├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── SlimFTPd.cpp ├── SlimFTPd.rc ├── SlimFTPd31.sln ├── SlimFTPd31.vcproj ├── SlimFTPd31.vcxproj ├── SlimFTPd31.vcxproj.filters ├── permdb.cpp ├── permdb.h ├── resource.h ├── synclogger.cpp ├── synclogger.h ├── tree.h ├── userdb.cpp ├── userdb.h ├── vfs.cpp └── vfs.h /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Matt Whitlock and WhitSoft Development 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the names of Matt Whitlock and WhitSoft Development nor the names 13 | of their contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlimFTPd 2 | 3 | SlimFTPd is a fully standards-compliant FTP server implementation with an advanced virtual file system. It is extremely small, but don't let its file size deceive you: SlimFTPd packs a lot of bang for the kilobyte. It is written in pure Win32 C++ and requires no messy installer. SlimFTPd is a fully multi-threaded application that runs as a system service on Windows 98/ME or Windows NT/2K/XP, and it comes with a tool to simplify its installation or uninstallation as a system service. Once the service is started, SlimFTPd runs quietly in the background. It reads its configuration from a config file in the same folder as the executable, and it outputs all activity to a log file in the same place. The virtual file system allows you to mount any local drive or path to any virtual path on the server. This allows you to have multiple local drives represented on the server's virtual file system or just different folders from the same drive. SlimFTPd allows you to set individual permissions for server paths. Open slimftpd.conf in your favorite text editor to set up SlimFTPd's configuration. The format of SlimFTPd's config file is similar to Apache Web Server's for those familiar with Apache. 4 | 5 | SlimFTPd features: 6 | 7 | * Standards-compliant FTP server implementation that works with all major FTP clients 8 | * Fully multi-threaded 32-bit application that runs as a Windows system service on all Windows platforms 9 | * Supports passive mode transfers and allows resume of failed transfers 10 | * Small memory footprint; won't hog system resources 11 | * Easy configuration of server options through configuration file 12 | * All activity logged to file 13 | * Support for binding to a specific interface in multihomed environments 14 | * User definable timeouts 15 | * No installation routine; won't take over your system 16 | * Supports all standard FTP commands: ABOR, APPE, CDUP/XCUP, CWD/XCWD, DELE, HELP, LIST, MKD/XMKD, NOOP, PASS, PASV, PORT, PWD/XPWD, QUIT, REIN, RETR, RMD/XRMD, RNFR/RNTO, STAT, STOR, SYST, TYPE, USER 17 | * Supports these extended FTP commands: MDTM, NLST, REST, SIZE 18 | * Supports setting of file timestamps 19 | * Conforms to [RFC 959](http://www.ietf.org/rfc/rfc0959.txt) and [RFC 1123](http://www.ietf.org/rfc/rfc1123.txt) standards 20 | 21 | # Changes from original SlimFTPd 3.181 22 | 23 | * Dropped support for non-NT OSes 24 | * Unicode support 25 | * Minor bug fixes 26 | -------------------------------------------------------------------------------- /SlimFTPd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "permdb.h" 37 | #include "synclogger.h" 38 | #include "userdb.h" 39 | #include "vfs.h" 40 | #include "tree.h" 41 | 42 | using namespace std; 43 | 44 | #define SERVERID L"SlimFTPd 3.181, by WhitSoft Development (www.whitsoftdev.com)" 45 | #define PACKET_SIZE 1452 46 | enum class IpAddressType { 47 | LAN = 1, 48 | WAN, 49 | LOCAL 50 | }; 51 | enum class SocketFileIODirection { 52 | SEND = 1, 53 | RECEIVE 54 | }; 55 | enum class ReceiveStatus { 56 | OK = 1, 57 | NETWORK_ERROR, 58 | TIMEOUT, 59 | INVALID_DATA, 60 | INSUFFICIENT_BUFFER 61 | }; 62 | 63 | // Service functions { 64 | VOID WINAPI ServiceMain(DWORD, LPTSTR); 65 | VOID WINAPI ServiceHandler(DWORD); 66 | bool Startup(); 67 | void Cleanup(); 68 | // } 69 | 70 | // Configuration functions { 71 | void LogConfError(const wchar_t *, DWORD, const wchar_t *); 72 | bool ConfParseScript(const wchar_t *); 73 | bool ConfSetBindInterface(const wchar_t *pszArg, DWORD dwLine); 74 | bool ConfSetBindPort(const wchar_t *pszArg, DWORD dwLine); 75 | bool ConfSetMaxConnections(const wchar_t *pszArg, DWORD dwLine); 76 | bool ConfSetCommandTimeout(const wchar_t *pszArg, DWORD dwLine); 77 | bool ConfSetConnectTimeout(const wchar_t *pszArg, DWORD dwLine); 78 | bool ConfSetLookupHosts(const wchar_t *pszArg, DWORD dwLine); 79 | bool ConfAddUser(const wchar_t *pszArg, DWORD dwLine); 80 | bool ConfSetUserPassword(const wchar_t *pszUser, const wchar_t *pszArg, DWORD dwLine); 81 | bool ConfSetMountPoint(const wchar_t *pszUser, const wchar_t *pszVirtual, const wchar_t *pszLocal, DWORD dwLine); 82 | bool ConfSetPermission(DWORD dwMode, const wchar_t *pszUser, const wchar_t *pszVirtual, const wchar_t *pszPerms, DWORD dwLine); 83 | // } 84 | 85 | // Network functions { 86 | void __cdecl ListenThread(void *); 87 | void __cdecl ConnectionThread(void *); 88 | bool SocketSendString(SOCKET, const wchar_t *); 89 | ReceiveStatus SocketReceiveString(SOCKET, wchar_t *, DWORD, DWORD *); 90 | ReceiveStatus SocketReceiveLetter(SOCKET, wchar_t *, DWORD, DWORD *); 91 | ReceiveStatus SocketReceiveData(SOCKET, char *, DWORD, DWORD *); 92 | SOCKET EstablishDataConnection(SOCKADDR_IN *, SOCKET *); 93 | void LookupHost(const SOCKADDR_IN *sai, wchar_t *pszHostName, size_t stHostName); 94 | bool DoSocketFileIO(SOCKET sCmd, SOCKET sData, HANDLE hFile, SocketFileIODirection direction, DWORD *pdwAbortFlag); 95 | // } 96 | 97 | // Miscellaneous support functions { 98 | bool FileSkipBOM(HANDLE hFile); 99 | DWORD FileReadLine(HANDLE, wchar_t *, DWORD); 100 | DWORD SplitTokens(wchar_t *); 101 | const wchar_t * GetToken(const wchar_t *, DWORD); 102 | IpAddressType GetIPAddressType(IN_ADDR ia); 103 | // } 104 | 105 | // Global Variables { 106 | HINSTANCE hInst; 107 | SERVICE_STATUS_HANDLE hServiceStatus; 108 | SERVICE_STATUS ServiceStatus; 109 | bool isService; 110 | DWORD dwMaxConnections = 20, dwCommandTimeout = 300, dwConnectTimeout = 15; 111 | bool bLookupHosts = true; 112 | volatile DWORD dwActiveConnections = 0; 113 | SOCKET sListen; 114 | SOCKADDR_IN saiListen; 115 | UserDB *pUsers; 116 | SyncLogger *pLog; 117 | // } 118 | 119 | int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR pszCmdLine, int nShowCmd) 120 | { 121 | SERVICE_TABLE_ENTRY ste[]={ { L"SlimFTPd", (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { 0, 0 } }; 122 | MSG msg; 123 | 124 | hInst = hInstance; 125 | 126 | // Are we starting as a service? 127 | if (wcsstr(pszCmdLine, L"-service") != 0) { 128 | isService = true; 129 | StartServiceCtrlDispatcher(ste); 130 | Cleanup(); 131 | return 0; 132 | } else { 133 | isService = false; 134 | } 135 | 136 | if (Startup()) { 137 | while (GetMessage(&msg,0,0,0)) { 138 | TranslateMessage(&msg); 139 | DispatchMessage(&msg); 140 | } 141 | } else { 142 | pLog->Log(L"An error occurred while starting SlimFTPd."); 143 | } 144 | 145 | Cleanup(); 146 | 147 | return 0; 148 | } 149 | 150 | VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR lpszArgv) 151 | { 152 | // Starting up as a Windows NT service 153 | hServiceStatus=RegisterServiceCtrlHandler(L"SlimFTPd",(LPHANDLER_FUNCTION)ServiceHandler); 154 | ServiceStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; 155 | ServiceStatus.dwCurrentState=SERVICE_RUNNING; 156 | ServiceStatus.dwControlsAccepted=SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN; 157 | ServiceStatus.dwWin32ExitCode=NO_ERROR; 158 | ServiceStatus.dwServiceSpecificExitCode=0; 159 | ServiceStatus.dwCheckPoint=0; 160 | ServiceStatus.dwWaitHint=0; 161 | SetServiceStatus(hServiceStatus,&ServiceStatus); 162 | 163 | if (!Startup()) { 164 | pLog->Log(L"An error occurred while starting SlimFTPd."); 165 | ServiceStatus.dwCurrentState=SERVICE_STOPPED; 166 | SetServiceStatus(hServiceStatus,&ServiceStatus); 167 | } 168 | } 169 | 170 | VOID WINAPI ServiceHandler(DWORD fdwControl) 171 | { 172 | switch (fdwControl) { 173 | case SERVICE_CONTROL_INTERROGATE: 174 | SetServiceStatus(hServiceStatus,&ServiceStatus); 175 | break; 176 | case SERVICE_CONTROL_STOP: 177 | pLog->Log(L"The SlimFTPd service has received a request to stop."); 178 | ServiceStatus.dwCurrentState=SERVICE_STOPPED; 179 | SetServiceStatus(hServiceStatus,&ServiceStatus); 180 | break; 181 | case SERVICE_CONTROL_SHUTDOWN: 182 | pLog->Log(L"The SlimFTPd service has received notification of a system shutdown."); 183 | ServiceStatus.dwCurrentState=SERVICE_STOPPED; 184 | SetServiceStatus(hServiceStatus,&ServiceStatus); 185 | break; 186 | } 187 | } 188 | 189 | bool Startup() 190 | { 191 | WSADATA wsad; 192 | wchar_t szLogFile[512], szConfFile[512]; 193 | 194 | // Construct log and config filenames 195 | GetModuleFileName(0,szLogFile,ARRAYSIZE(szLogFile)); 196 | *wcsrchr(szLogFile, L'\\') = 0; 197 | wcscpy_s(szConfFile,szLogFile); 198 | wcscat_s(szLogFile, L"\\SlimFTPd.log"); 199 | wcscat_s(szConfFile, L"\\SlimFTPd.conf"); 200 | 201 | // Start logger thread 202 | pLog=new SyncLogger(szLogFile); 203 | 204 | // Allocate user database 205 | pUsers = new UserDB; 206 | 207 | // Log some startup info 208 | pLog->Log(L"-------------------------------------------------------------------------------"); 209 | pLog->Log(SERVERID); 210 | if (isService) pLog->Log(L"The SlimFTPd service is starting."); 211 | else pLog->Log(L"SlimFTPd is starting."); 212 | 213 | // Init listen socket to defaults 214 | ZeroMemory(&saiListen,sizeof(SOCKADDR_IN)); 215 | saiListen.sin_family=AF_INET; 216 | saiListen.sin_addr.S_un.S_addr=INADDR_ANY; 217 | saiListen.sin_port=htons(21); 218 | 219 | // Start Winsock 220 | WSAStartup(MAKEWORD(2,2),&wsad); 221 | 222 | // Exec config script 223 | if (!ConfParseScript(szConfFile)) return false; 224 | 225 | // Create and bind the listen socket 226 | sListen=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 227 | if (bind(sListen,(SOCKADDR *)&saiListen,sizeof(SOCKADDR_IN))) { 228 | pLog->Log(L"Unable to bind socket. Specified port may already be in use."); 229 | closesocket(sListen); 230 | return false; 231 | } 232 | listen(sListen,SOMAXCONN); 233 | 234 | // Launch the listen thread 235 | _beginthread(ListenThread,0,NULL); 236 | 237 | return true; 238 | } 239 | 240 | void Cleanup() 241 | { 242 | // Cleanup Winsock 243 | WSACleanup(); 244 | 245 | // Log the stop of the service 246 | if (isService) pLog->Log(L"The SlimFTPd service has stopped."); 247 | else pLog->Log(L"SlimFTPd has stopped."); 248 | 249 | // Deallocate the user database 250 | delete pUsers; 251 | 252 | // Shut down the logger thread 253 | delete pLog; 254 | } 255 | 256 | void LogConfError(const wchar_t *pszError, DWORD dwLine, const wchar_t *pszArg) 257 | { 258 | wchar_t sz[1024]; 259 | swprintf_s(sz, (wstring(L"Error on line %u: ") + pszError).c_str(), dwLine, pszArg); 260 | pLog->Log(sz); 261 | } 262 | 263 | bool ConfParseScript(const wchar_t *pszFileName) 264 | { 265 | // Opens and parses a SlimFTPd configuration script file. 266 | // Returns false on error, or true on success. 267 | 268 | wchar_t sz[512], *psz, *psz2; 269 | wstring strUser; 270 | DWORD dwLen, dwLine, dwTokens; 271 | HANDLE hFile; 272 | 273 | swprintf_s(sz,L"Executing \"%s\"...",wcsrchr(pszFileName,L'\\')+1); 274 | pLog->Log(sz); 275 | 276 | // Open config file 277 | hFile=CreateFile(pszFileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0); 278 | if (hFile==INVALID_HANDLE_VALUE) { 279 | pLog->Log(L"Unable to open \"SlimFTPd.conf\"."); 280 | return false; 281 | } 282 | 283 | FileSkipBOM(hFile); 284 | 285 | for (dwLine=1;;dwLine++) { 286 | dwLen=FileReadLine(hFile, sz, ARRAYSIZE(sz)); 287 | if (dwLen==-1) { 288 | CloseHandle(hFile); 289 | if (!strUser.empty()) { 290 | LogConfError(L"Premature end of script encountered: unterminated User block.",dwLine,0); 291 | return false; 292 | } else { 293 | pLog->Log(L"Configuration script parsed successfully."); 294 | return true; 295 | } 296 | } else if (dwLen>=512) { 297 | LogConfError(L"Line is too long to parse.",dwLine,0); 298 | break; 299 | } 300 | psz=sz; 301 | while (*psz==L' ' || *psz==L'\t') psz++; 302 | if (!*psz || *psz==L'#') continue; 303 | 304 | if (*psz==L'<') { 305 | psz2=wcschr(psz,L'>'); 306 | if (psz2) { 307 | *(psz2++)=0; 308 | while (*psz2==L' ' || *psz2==L'\t') psz2++; 309 | if (*psz2) { 310 | LogConfError(L"Syntax error. Expected end of line after '>'.",dwLine,0); 311 | break; 312 | } 313 | psz++; 314 | } else { 315 | LogConfError(L"Syntax error. Expected '>' before end of line.",dwLine,0); 316 | break; 317 | } 318 | } 319 | 320 | dwTokens=SplitTokens(psz); 321 | 322 | if (!_wcsicmp(psz,L"BindInterface")) { 323 | if (dwTokens==2) { 324 | if (!ConfSetBindInterface(GetToken(psz,2),dwLine)) break; 325 | } else { 326 | LogConfError(L"BindInterface directive should have exactly 1 argument.",dwLine,0); 327 | break; 328 | } 329 | } 330 | 331 | else if (!_wcsicmp(psz,L"BindPort")) { 332 | if (dwTokens==2) { 333 | if (!ConfSetBindPort(GetToken(psz,2),dwLine)) break; 334 | } else { 335 | LogConfError(L"BindPort directive should have exactly 1 argument.",dwLine,0); 336 | break; 337 | } 338 | } 339 | 340 | else if (!_wcsicmp(psz,L"MaxConnections")) { 341 | if (dwTokens==2) { 342 | if (!ConfSetMaxConnections(GetToken(psz,2),dwLine)) break; 343 | } else { 344 | LogConfError(L"MaxConnections directive should have exactly 1 argument.",dwLine,0); 345 | break; 346 | } 347 | } 348 | 349 | else if (!_wcsicmp(psz,L"CommandTimeout")) { 350 | if (dwTokens==2) { 351 | if (!ConfSetCommandTimeout(GetToken(psz,2),dwLine)) break; 352 | } else { 353 | LogConfError(L"CommandTimeout directive should have exactly 1 argument.",dwLine,0); 354 | break; 355 | } 356 | } 357 | 358 | else if (!_wcsicmp(psz,L"ConnectTimeout")) { 359 | if (dwTokens==2) { 360 | if (!ConfSetConnectTimeout(GetToken(psz,2),dwLine)) break; 361 | } else { 362 | LogConfError(L"ConnectTimeout directive should have exactly 1 argument.",dwLine,0); 363 | break; 364 | } 365 | } 366 | 367 | else if (!_wcsicmp(psz,L"LookupHosts")) { 368 | if (dwTokens==2) { 369 | if (!ConfSetLookupHosts(GetToken(psz,2),dwLine)) break; 370 | } else { 371 | LogConfError(L"LookupHosts directive should have exactly 1 argument.",dwLine,0); 372 | break; 373 | } 374 | } 375 | 376 | else if (!_wcsicmp(psz,L"User")) { 377 | if (!strUser.empty()) { 378 | LogConfError(L" directive invalid inside User block.",dwLine,0); 379 | break; 380 | } else if (dwTokens==2) { 381 | if (ConfAddUser(GetToken(psz,2),dwLine)) { 382 | strUser = GetToken(psz, 2); 383 | } else { 384 | break; 385 | } 386 | } else { 387 | LogConfError(L" directive should have exactly 1 argument.",dwLine,0); 388 | break; 389 | } 390 | } 391 | 392 | else if (!_wcsicmp(psz,L"/User")) { 393 | if (strUser.empty()) { 394 | LogConfError(L" directive invalid outside of User block.",dwLine,0); 395 | break; 396 | } else if (dwTokens==1) { 397 | strUser.clear(); 398 | } else { 399 | LogConfError(L" directive should not have any arguments.",dwLine,0); 400 | break; 401 | } 402 | } 403 | 404 | else if (!_wcsicmp(psz,L"Password")) { 405 | if (strUser.empty()) { 406 | LogConfError(L"Password directive invalid outside of User block.",dwLine,0); 407 | break; 408 | } else if (dwTokens==2) { 409 | if (!ConfSetUserPassword(strUser.c_str(), GetToken(psz, 2), dwLine)) break; 410 | } else { 411 | LogConfError(L"Password directive should have exactly 1 argument.",dwLine,0); 412 | break; 413 | } 414 | } 415 | 416 | else if (!_wcsicmp(psz,L"Mount")) { 417 | if (strUser.empty()) { 418 | LogConfError(L"Mount directive invalid outside of User block.",dwLine,0); 419 | break; 420 | } else if (dwTokens==3) { 421 | if (!ConfSetMountPoint(strUser.c_str(), GetToken(psz, 2), GetToken(psz, 3), dwLine)) break; 422 | } else { 423 | LogConfError(L"Mount directive should have exactly 2 arguments.",dwLine,0); 424 | break; 425 | } 426 | } 427 | 428 | else if (!_wcsicmp(psz,L"Allow")) { 429 | if (strUser.empty()) { 430 | LogConfError(L"Allow directive invalid outside of User block.",dwLine,0); 431 | break; 432 | } else if (dwTokens>=3) { 433 | if (!ConfSetPermission(1, strUser.c_str(), GetToken(psz, 2), GetToken(psz, 3), dwLine)) break; 434 | } else { 435 | LogConfError(L"Allow directive should have at least 2 arguments.",dwLine,0); 436 | break; 437 | } 438 | } 439 | 440 | else if (!_wcsicmp(psz,L"Deny")) { 441 | if (strUser.empty()) { 442 | LogConfError(L"Deny directive invalid outside of User block.",dwLine,0); 443 | break; 444 | } else if (dwTokens>=3) { 445 | if (!ConfSetPermission(0, strUser.c_str(), GetToken(psz, 2), GetToken(psz, 3), dwLine)) break; 446 | } else { 447 | LogConfError(L"Deny directive should have at least 2 arguments.",dwLine,0); 448 | break; 449 | } 450 | } 451 | 452 | else { 453 | LogConfError(L"Directive \"%s\" not recognized.",dwLine,psz); 454 | break; 455 | } 456 | } 457 | 458 | CloseHandle(hFile); 459 | pLog->Log(L"Failed parsing configuration script."); 460 | return false; 461 | } 462 | 463 | bool ConfSetBindInterface(const wchar_t *pszArg, DWORD dwLine) 464 | { 465 | char sz[512]; 466 | HOSTENT *phe; 467 | DWORD dw; 468 | 469 | if (!_wcsicmp(pszArg,L"All")) { 470 | saiListen.sin_addr.S_un.S_addr=INADDR_ANY; 471 | } else if (!_wcsicmp(pszArg,L"Local")) { 472 | saiListen.sin_addr.S_un.S_addr=htonl(INADDR_LOOPBACK); 473 | } else if (!_wcsicmp(pszArg,L"LAN")) { 474 | saiListen.sin_addr.S_un.S_addr=INADDR_NONE; 475 | gethostname(sz,ARRAYSIZE(sz)); 476 | phe=gethostbyname(sz); 477 | if (phe) { 478 | for (dw=0;phe->h_addr_list[dw];dw++) { 479 | if (GetIPAddressType(*(IN_ADDR*)phe->h_addr_list[dw]) == IpAddressType::LAN) { 480 | saiListen.sin_addr.S_un.S_addr=((IN_ADDR*)phe->h_addr_list[dw])->S_un.S_addr; 481 | break; 482 | } 483 | } 484 | } 485 | if (saiListen.sin_addr.S_un.S_addr==INADDR_NONE) { 486 | LogConfError(L"BindInterface directive could not find a LAN interface.",dwLine,0); 487 | return false; 488 | } 489 | } else if (!_wcsicmp(pszArg,L"WAN")) { 490 | saiListen.sin_addr.S_un.S_addr=INADDR_NONE; 491 | gethostname(sz,ARRAYSIZE(sz)); 492 | phe=gethostbyname(sz); 493 | if (phe) { 494 | for (dw=0;phe->h_addr_list[dw];dw++) { 495 | if (GetIPAddressType(*(IN_ADDR*)phe->h_addr_list[dw]) == IpAddressType::WAN) { 496 | saiListen.sin_addr.S_un.S_addr=((IN_ADDR*)phe->h_addr_list[dw])->S_un.S_addr; 497 | break; 498 | } 499 | } 500 | } 501 | if (saiListen.sin_addr.S_un.S_addr==INADDR_NONE) { 502 | LogConfError(L"BindInterface directive could not find a WAN interface.",dwLine,0); 503 | return false; 504 | } 505 | } else { 506 | if (InetPton(AF_INET, pszArg, &saiListen.sin_addr.S_un.S_addr)!=1) { 507 | LogConfError(L"BindInterface directive does not recognize argument \"%s\".",dwLine,pszArg); 508 | return false; 509 | } 510 | } 511 | return true; 512 | } 513 | 514 | bool ConfSetBindPort(const wchar_t *pszArg, DWORD dwLine) 515 | { 516 | WORD wPort; 517 | 518 | wPort = (WORD)StrToInt(pszArg); 519 | if (wPort) { 520 | saiListen.sin_port=htons(wPort); 521 | return true; 522 | } else { 523 | LogConfError(L"BindPort directive does not recognize argument \"%s\".",dwLine,pszArg); 524 | return false; 525 | } 526 | } 527 | 528 | bool ConfSetMaxConnections(const wchar_t *pszArg, DWORD dwLine) 529 | { 530 | DWORD dw; 531 | 532 | if (!_wcsicmp(pszArg,L"Off")) { 533 | dwMaxConnections=-1; 534 | return true; 535 | } else { 536 | dw = StrToInt(pszArg); 537 | if (dw) { 538 | dwMaxConnections=dw; 539 | return true; 540 | } else { 541 | LogConfError(L"MaxConnections directive does not recognize argument \"%s\".",dwLine,pszArg); 542 | return false; 543 | } 544 | } 545 | } 546 | 547 | bool ConfSetCommandTimeout(const wchar_t *pszArg, DWORD dwLine) 548 | { 549 | DWORD dw; 550 | 551 | dw = StrToInt(pszArg); 552 | if (dw) { 553 | dwCommandTimeout=dw; 554 | return true; 555 | } else { 556 | LogConfError(L"CommandTimeout directive does not recognize argument \"%s\".",dwLine,pszArg); 557 | return false; 558 | } 559 | } 560 | 561 | bool ConfSetConnectTimeout(const wchar_t *pszArg, DWORD dwLine) 562 | { 563 | DWORD dw; 564 | 565 | dw = StrToInt(pszArg); 566 | if (dw) { 567 | dwConnectTimeout=dw; 568 | return true; 569 | } else { 570 | LogConfError(L"ConnectTimeout directive does not recognize argument \"%s\".",dwLine,pszArg); 571 | return false; 572 | } 573 | } 574 | 575 | bool ConfSetLookupHosts(const wchar_t *pszArg, DWORD dwLine) 576 | { 577 | if (!_wcsicmp(pszArg,L"Off")) { 578 | bLookupHosts = false; 579 | return true; 580 | } else if (!_wcsicmp(pszArg,L"On")) { 581 | bLookupHosts = true; 582 | return true; 583 | } else { 584 | LogConfError(L"LookupHosts directive does not recognize argument \"%s\".",dwLine,pszArg); 585 | return false; 586 | } 587 | } 588 | 589 | bool ConfAddUser(const wchar_t *pszArg, DWORD dwLine) 590 | { 591 | if (wcslen(pszArg)<32) { 592 | if (pUsers->Add(pszArg)) { 593 | return true; 594 | } else { 595 | LogConfError(L"User \"%s\" already defined.",dwLine,pszArg); 596 | return false; 597 | } 598 | } else { 599 | LogConfError(L"Argument to User directive must be less than 32 characters long.",dwLine,0); 600 | return false; 601 | } 602 | } 603 | 604 | bool ConfSetUserPassword(const wchar_t *pszUser, const wchar_t *pszArg, DWORD dwLine) 605 | { 606 | if (wcslen(pszArg)<32) { 607 | pUsers->SetPassword(pszUser,pszArg); 608 | return true; 609 | } else { 610 | LogConfError(L"Argument to Password directive must be less than 32 characters long.",dwLine,0); 611 | return false; 612 | } 613 | } 614 | 615 | bool ConfSetMountPoint(const wchar_t *pszUser, const wchar_t *pszVirtual, const wchar_t *pszLocal, DWORD dwLine) 616 | { 617 | VFS *pvfs; 618 | wstring strVirtual, strLocal; 619 | 620 | VFS::CleanVirtualPath(pszVirtual, strVirtual); 621 | 622 | if (strVirtual.at(0) != L'/') { 623 | LogConfError(L"Mount directive cannot parse invalid virtual path \"%s\". Virtual paths must begin with a slash.", dwLine, strVirtual.c_str()); 624 | return false; 625 | } 626 | if (pszLocal) { 627 | strLocal = pszLocal; 628 | replace(strLocal.begin(), strLocal.end(), L'/', L'\\'); 629 | if (*strLocal.rbegin() == L'\\') { 630 | strLocal = strLocal.substr(0, strLocal.length() - 1); 631 | } 632 | if (GetFileAttributes(strLocal.c_str()) == INVALID_FILE_ATTRIBUTES) { 633 | LogConfError(L"Mount directive cannot find local path \"%s\".", dwLine, strLocal.c_str()); 634 | return false; 635 | } 636 | } 637 | pvfs=pUsers->GetVFS(pszUser); 638 | if (pvfs) pvfs->Mount(pszVirtual, pszLocal); 639 | return true; 640 | } 641 | 642 | bool ConfSetPermission(DWORD dwMode, const wchar_t *pszUser, const wchar_t *pszVirtual, const wchar_t *pszPerms, DWORD dwLine) 643 | { 644 | PermDB *pperms; 645 | 646 | wstring strVirtual; 647 | VFS::CleanVirtualPath(pszVirtual, strVirtual); 648 | 649 | if (strVirtual.at(0) != L'/') { 650 | if (dwMode) { 651 | LogConfError(L"Allow directive cannot parse invalid virtual path \"%s\". Virtual paths must begin with a slash.", dwLine, strVirtual.c_str()); 652 | } else { 653 | LogConfError(L"Deny directive cannot parse invalid virtual path \"%s\". Virtual paths must begin with a slash.", dwLine, strVirtual.c_str()); 654 | } 655 | return false; 656 | } 657 | 658 | pperms=pUsers->GetPermDB(pszUser); 659 | if (!pperms) return false; 660 | 661 | while (*pszPerms) { 662 | if (!_wcsicmp(pszPerms,L"Read")) { 663 | pperms->SetPerm(strVirtual.c_str(), PERM_READ, dwMode); 664 | } else if (!_wcsicmp(pszPerms,L"Write")) { 665 | pperms->SetPerm(strVirtual.c_str(), PERM_WRITE, dwMode); 666 | } else if (!_wcsicmp(pszPerms,L"List")) { 667 | pperms->SetPerm(strVirtual.c_str(), PERM_LIST, dwMode); 668 | } else if (!_wcsicmp(pszPerms,L"Admin")) { 669 | pperms->SetPerm(strVirtual.c_str(), PERM_ADMIN, dwMode); 670 | } else if (!_wcsicmp(pszPerms,L"All")) { 671 | pperms->SetPerm(strVirtual.c_str(), PERM_READ, dwMode); 672 | pperms->SetPerm(strVirtual.c_str(), PERM_WRITE, dwMode); 673 | pperms->SetPerm(strVirtual.c_str(), PERM_LIST, dwMode); 674 | pperms->SetPerm(strVirtual.c_str(), PERM_ADMIN, dwMode); 675 | } else { 676 | if (dwMode) { 677 | LogConfError(L"Allow directive does not recognize argument \"%s\".",dwLine,pszPerms); 678 | } else { 679 | LogConfError(L"Deny directive does not recognize argument \"%s\".",dwLine,pszPerms); 680 | } 681 | return false; 682 | } 683 | pszPerms=GetToken(pszPerms,2); 684 | } 685 | return true; 686 | } 687 | 688 | void __cdecl ListenThread(void *) 689 | { 690 | SOCKET sIncoming; 691 | 692 | pLog->Log(L"Waiting for incoming connections..."); 693 | 694 | // Accept incoming connections and pass them to connection threads 695 | while ((sIncoming=accept(sListen,0,0))!=INVALID_SOCKET) { 696 | _beginthread(ConnectionThread,0,(void *)sIncoming); 697 | } 698 | 699 | closesocket(sListen); 700 | } 701 | 702 | void __cdecl ConnectionThread(void *pParam) 703 | { 704 | SOCKET sCmd = (SOCKET)pParam; 705 | SOCKET sData=0, sPasv=0; 706 | SOCKADDR_IN saiCmd, saiCmdPeer, saiData, saiPasv; 707 | wchar_t szPeerName[64], szOutput[1024], szCmd[512], *pszParam; 708 | wstring strUser, strCurrentVirtual, strNewVirtual, strRnFr; 709 | DWORD dw, dwRestOffset=0; 710 | ReceiveStatus status; 711 | bool isLoggedIn = false; 712 | HANDLE hFile; 713 | SYSTEMTIME st; 714 | FILETIME ft; 715 | VFS *pVFS = NULL; 716 | PermDB *pPerms = NULL; 717 | VFS::listing_type listing; 718 | UINT_PTR i; 719 | 720 | ZeroMemory(&saiData, sizeof(SOCKADDR_IN)); 721 | 722 | // Get peer address 723 | dw=sizeof(SOCKADDR_IN); 724 | getpeername(sCmd, (SOCKADDR *)&saiCmdPeer, (int *)&dw); 725 | LookupHost(&saiCmdPeer, szPeerName, ARRAYSIZE(szPeerName)); 726 | 727 | // Log incoming connection 728 | swprintf_s(szOutput, L"[%u] Incoming connection from %s:%u.", sCmd, szPeerName, ntohs(saiCmdPeer.sin_port)); 729 | pLog->Log(szOutput); 730 | 731 | // Send greeting 732 | swprintf_s(szOutput, L"220-%s\r\n220-You are connecting from %s:%u.\r\n220 Proceed with login.\r\n", SERVERID, szPeerName, ntohs(saiCmdPeer.sin_port)); 733 | SocketSendString(sCmd, szOutput); 734 | 735 | // Get host address 736 | dw=sizeof(SOCKADDR_IN); 737 | getsockname(sCmd, (SOCKADDR *)&saiCmd, (int *)&dw); 738 | 739 | // Command processing loop 740 | for (;;) { 741 | 742 | status=SocketReceiveString(sCmd,szCmd,ARRAYSIZE(szCmd),&dw); 743 | 744 | if (status==ReceiveStatus::NETWORK_ERROR) { 745 | SocketSendString(sCmd,L"421 Network error.\r\n"); 746 | break; 747 | } else if (status==ReceiveStatus::TIMEOUT) { 748 | SocketSendString(sCmd,L"421 Connection timed out.\r\n"); 749 | break; 750 | } else if (status==ReceiveStatus::INVALID_DATA) { 751 | SocketSendString(sCmd,L"500 Malformed request.\r\n"); 752 | continue; 753 | } else if (status==ReceiveStatus::INSUFFICIENT_BUFFER) { 754 | SocketSendString(sCmd,L"500 Command line too long.\r\n"); 755 | continue; 756 | } 757 | 758 | if (pszParam = wcschr(szCmd, L' ')) *(pszParam++) = 0; 759 | else pszParam = szCmd+wcslen(szCmd); 760 | 761 | if (!_wcsicmp(szCmd, L"USER")) { 762 | if (!*pszParam) { 763 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 764 | continue; 765 | } else if (isLoggedIn) { 766 | SocketSendString(sCmd, L"503 Already logged in. Use REIN to change users.\r\n"); 767 | continue; 768 | } else { 769 | strUser = pszParam; 770 | if (pUsers->CheckPassword(strUser.c_str(), L"")) { 771 | wcscpy_s(szCmd, L"PASS"); 772 | szCmd[5] = 0; 773 | } else { 774 | swprintf_s(szOutput, L"331 Need password for user \"%s\".\r\n", strUser.c_str()); 775 | SocketSendString(sCmd, szOutput); 776 | continue; 777 | } 778 | } 779 | } 780 | 781 | if (!_wcsicmp(szCmd, L"PASS")) { 782 | if (strUser.empty()) { 783 | SocketSendString(sCmd, L"503 Bad sequence of commands. Send USER first.\r\n"); 784 | } else if (isLoggedIn) { 785 | SocketSendString(sCmd, L"503 Already logged in. Use REIN to change users.\r\n"); 786 | } else { 787 | if (pUsers->CheckPassword(strUser.c_str(), pszParam)) { 788 | if (InterlockedIncrement(&dwActiveConnections) <= dwMaxConnections) { 789 | isLoggedIn = true; 790 | strCurrentVirtual = L"/"; 791 | swprintf_s(szOutput, L"230 User \"%s\" logged in.\r\n", strUser.c_str()); 792 | SocketSendString(sCmd, szOutput); 793 | swprintf_s(szOutput, L"[%u] User \"%s\" logged in.", sCmd, strUser.c_str()); 794 | pLog->Log(szOutput); 795 | pVFS = pUsers->GetVFS(strUser.c_str()); 796 | pPerms = pUsers->GetPermDB(strUser.c_str()); 797 | } else { 798 | InterlockedDecrement(&dwActiveConnections); 799 | SocketSendString(sCmd, L"421 Your login was refused due to a server connection limit.\r\n"); 800 | swprintf_s(szOutput, L"[%u] Login for user \"%s\" refused due to connection limit.", sCmd, strUser.c_str()); 801 | pLog->Log(szOutput); 802 | break; 803 | } 804 | } else { 805 | SocketSendString(sCmd,L"530 Incorrect password.\r\n"); 806 | } 807 | } 808 | } 809 | 810 | else if (!_wcsicmp(szCmd, L"REIN")) { 811 | if (isLoggedIn) { 812 | isLoggedIn = false; 813 | InterlockedDecrement(&dwActiveConnections); 814 | swprintf_s(szOutput, L"220-User \"%s\" logged out.\r\n", strUser.c_str()); 815 | SocketSendString(sCmd, szOutput); 816 | swprintf_s(szOutput, L"[%u] User \"%s\" logged out.", sCmd, strUser.c_str()); 817 | pLog->Log(szOutput); 818 | strUser.clear(); 819 | } 820 | SocketSendString(sCmd, L"220 REIN command successful.\r\n"); 821 | } 822 | 823 | else if (!_wcsicmp(szCmd, L"HELP")) { 824 | SocketSendString(sCmd, L"214 For help, please visit www.whitsoftdev.com.\r\n"); 825 | } 826 | 827 | else if (!_wcsicmp(szCmd, L"FEAT")) { 828 | SocketSendString(sCmd, L"211-Extensions supported:\r\n SIZE\r\n REST STREAM\r\n MDTM\r\n TVFS\r\n UTF8\r\n211 END\r\n"); 829 | } 830 | 831 | else if (!_wcsicmp(szCmd, L"SYST")) { 832 | swprintf_s(szOutput, L"215 WIN32 Type: L8 Version: %s\r\n", SERVERID); 833 | SocketSendString(sCmd, szOutput); 834 | } 835 | 836 | else if (!_wcsicmp(szCmd, L"QUIT")) { 837 | if (isLoggedIn) { 838 | isLoggedIn = false; 839 | InterlockedDecrement(&dwActiveConnections); 840 | swprintf_s(szOutput, L"221-User \"%s\" logged out.\r\n", strUser.c_str()); 841 | SocketSendString(sCmd, szOutput); 842 | swprintf_s(szOutput, L"[%u] User \"%s\" logged out.", sCmd, strUser.c_str()); 843 | pLog->Log(szOutput); 844 | } 845 | SocketSendString(sCmd, L"221 Goodbye!\r\n"); 846 | break; 847 | } 848 | 849 | else if (!_wcsicmp(szCmd, L"NOOP")) { 850 | SocketSendString(sCmd, L"200 NOOP command successful.\r\n"); 851 | } 852 | 853 | else if (!_wcsicmp(szCmd, L"PWD") || !_wcsicmp(szCmd, L"XPWD")) { 854 | if (!isLoggedIn) { 855 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 856 | } else { 857 | swprintf_s(szOutput, L"257 \"%s\" is current directory.\r\n", strCurrentVirtual.c_str()); 858 | SocketSendString(sCmd, szOutput); 859 | } 860 | } 861 | 862 | else if (!_wcsicmp(szCmd, L"CWD") || !_wcsicmp(szCmd, L"XCWD")) { 863 | if (!*pszParam) { 864 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 865 | } else if (!isLoggedIn) { 866 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 867 | } else { 868 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 869 | if (pVFS->IsFolder(strNewVirtual.c_str())) { 870 | strCurrentVirtual = strNewVirtual; 871 | swprintf_s(szOutput, L"250 \"%s\" is now current directory.\r\n", strNewVirtual.c_str()); 872 | SocketSendString(sCmd, szOutput); 873 | } else { 874 | swprintf_s(szOutput, L"550 \"%s\": Path not found.\r\n", strNewVirtual.c_str()); 875 | SocketSendString(sCmd, szOutput); 876 | } 877 | } 878 | } 879 | 880 | else if (!_wcsicmp(szCmd, L"CDUP") || !_wcsicmp(szCmd, L"XCUP")) { 881 | if (!isLoggedIn) { 882 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 883 | } else { 884 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), L"..", strNewVirtual); 885 | strCurrentVirtual = strNewVirtual; 886 | swprintf_s(szOutput,L"250 \"%s\" is now current directory.\r\n", strCurrentVirtual.c_str()); 887 | SocketSendString(sCmd, szOutput); 888 | } 889 | } 890 | 891 | else if (!_wcsicmp(szCmd,L"TYPE")) { 892 | if (!*pszParam) { 893 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 894 | } else if (!isLoggedIn) { 895 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 896 | } else { 897 | SocketSendString(sCmd, L"200 TYPE command successful.\r\n"); 898 | } 899 | } 900 | 901 | else if (!_wcsicmp(szCmd, L"REST")) { 902 | if (!*pszParam || (!(dw = StrToInt(pszParam)) && (*pszParam!=L'0'))) { 903 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 904 | } else if (!isLoggedIn) { 905 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 906 | } else { 907 | dwRestOffset = dw; 908 | swprintf_s(szOutput, L"350 Ready to resume transfer at %u bytes.\r\n", dwRestOffset); 909 | SocketSendString(sCmd, szOutput); 910 | } 911 | } 912 | 913 | else if (!_wcsicmp(szCmd, L"PORT")) { 914 | if (!*pszParam) { 915 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 916 | } else if (!isLoggedIn) { 917 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 918 | } else { 919 | ZeroMemory(&saiData, sizeof(SOCKADDR_IN)); 920 | saiData.sin_family = AF_INET; 921 | for (dw = 0; dw < 6; dw++) { 922 | if (dw < 4) ((unsigned char *)&saiData.sin_addr)[dw] = (unsigned char)StrToInt(pszParam); 923 | else ((unsigned char *)&saiData.sin_port)[dw-4] = (unsigned char)StrToInt(pszParam); 924 | if (!(pszParam = wcschr(pszParam, L','))) break; 925 | pszParam++; 926 | } 927 | if (dw == 5) { 928 | if (sPasv) { 929 | closesocket(sPasv); 930 | sPasv = 0; 931 | } 932 | SocketSendString(sCmd, L"200 PORT command successful.\r\n"); 933 | } else { 934 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 935 | ZeroMemory(&saiData, sizeof(SOCKADDR_IN)); 936 | } 937 | } 938 | } 939 | 940 | else if (!_wcsicmp(szCmd, L"PASV")) { 941 | if (!isLoggedIn) { 942 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 943 | } else { 944 | if (sPasv) closesocket(sPasv); 945 | ZeroMemory(&saiPasv, sizeof(SOCKADDR_IN)); 946 | saiPasv.sin_family = AF_INET; 947 | saiPasv.sin_addr.S_un.S_addr = INADDR_ANY; 948 | saiPasv.sin_port = 0; 949 | sPasv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 950 | bind(sPasv, (SOCKADDR *)&saiPasv, sizeof(SOCKADDR_IN)); 951 | listen(sPasv, 1); 952 | dw = sizeof(SOCKADDR_IN); 953 | getsockname(sPasv, (SOCKADDR *)&saiPasv, (int *)&dw); 954 | swprintf_s(szOutput, L"227 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n", saiCmd.sin_addr.S_un.S_un_b.s_b1, saiCmd.sin_addr.S_un.S_un_b.s_b2, saiCmd.sin_addr.S_un.S_un_b.s_b3, saiCmd.sin_addr.S_un.S_un_b.s_b4, ((unsigned char *)&saiPasv.sin_port)[0], ((unsigned char *)&saiPasv.sin_port)[1]); 955 | SocketSendString(sCmd, szOutput); 956 | } 957 | } 958 | 959 | else if (!_wcsicmp(szCmd, L"LIST") || !_wcsicmp(szCmd, L"NLST")) { 960 | if (!isLoggedIn) { 961 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 962 | } else { 963 | if (*pszParam == L'-') if (pszParam = wcschr(pszParam, L' ')) pszParam++; 964 | if (pszParam && *pszParam) { 965 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 966 | } 967 | else { 968 | strNewVirtual = strCurrentVirtual; 969 | } 970 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_LIST) == 1) { 971 | if (pVFS->GetDirectoryListing(strNewVirtual.c_str(), _wcsicmp(szCmd, L"LIST"), listing)) { 972 | swprintf_s(szOutput, L"150 Opening %s mode data connection for listing of \"%s\".\r\n", sPasv ? L"passive" : L"active", strNewVirtual.c_str()); 973 | SocketSendString(sCmd, szOutput); 974 | sData = EstablishDataConnection(&saiData, &sPasv); 975 | if (sData!=INVALID_SOCKET) { 976 | for (VFS::listing_type::const_iterator it = listing.begin(); it != listing.end(); ++it) { 977 | SocketSendString(sData, it->second.c_str()); 978 | } 979 | listing.clear(); 980 | closesocket(sData); 981 | swprintf_s(szOutput, L"226 %s command successful.\r\n", _wcsicmp(szCmd, L"NLST") ? L"LIST" : L"NLST"); 982 | SocketSendString(sCmd, szOutput); 983 | } else { 984 | listing.clear(); 985 | SocketSendString(sCmd, L"425 Can't open data connection.\r\n"); 986 | } 987 | } else { 988 | swprintf_s(szOutput, L"550 \"%s\": Path not found.\r\n", strNewVirtual.c_str()); 989 | SocketSendString(sCmd, szOutput); 990 | } 991 | } else { 992 | swprintf_s(szOutput, L"550 \"%s\": List permission denied.\r\n", strNewVirtual.c_str()); 993 | SocketSendString(sCmd, szOutput); 994 | } 995 | } 996 | } 997 | 998 | else if (!_wcsicmp(szCmd, L"STAT")) { 999 | if (!isLoggedIn) { 1000 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1001 | } else { 1002 | if (*pszParam == L'-') if (pszParam = wcschr(pszParam, L' ')) pszParam++; 1003 | if (pszParam && *pszParam) { 1004 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1005 | } 1006 | else { 1007 | strNewVirtual = strCurrentVirtual; 1008 | } 1009 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_LIST) == 1) { 1010 | if (pVFS->GetDirectoryListing(strNewVirtual.c_str(), 0, listing)) { 1011 | swprintf_s(szOutput, L"212-Sending directory listing of \"%s\".\r\n", strNewVirtual.c_str()); 1012 | SocketSendString(sCmd,szOutput); 1013 | for (VFS::listing_type::const_iterator it = listing.begin(); it != listing.end(); ++it) { 1014 | SocketSendString(sCmd, it->second.c_str()); 1015 | } 1016 | listing.clear(); 1017 | SocketSendString(sCmd, L"212 STAT command successful.\r\n"); 1018 | } else { 1019 | swprintf_s(szOutput, L"550 \"%s\": Path not found.\r\n", strNewVirtual.c_str()); 1020 | SocketSendString(sCmd, szOutput); 1021 | } 1022 | } else { 1023 | swprintf_s(szOutput ,L"550 \"%s\": List permission denied.\r\n", strNewVirtual.c_str()); 1024 | SocketSendString(sCmd, szOutput); 1025 | } 1026 | } 1027 | } 1028 | 1029 | else if (!_wcsicmp(szCmd, L"RETR")) { 1030 | if (!*pszParam) { 1031 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1032 | } else if (!isLoggedIn) { 1033 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1034 | } else { 1035 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1036 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_READ) == 1) { 1037 | hFile = pVFS->CreateFile(strNewVirtual.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING); 1038 | if (hFile == INVALID_HANDLE_VALUE) { 1039 | swprintf_s(szOutput, L"550 \"%s\": Unable to open file.\r\n", strNewVirtual.c_str()); 1040 | SocketSendString(sCmd, szOutput); 1041 | } else { 1042 | if (dwRestOffset) { 1043 | SetFilePointer(hFile, dwRestOffset, 0, FILE_BEGIN); 1044 | dwRestOffset = 0; 1045 | } 1046 | swprintf_s(szOutput, L"150 Opening %s mode data connection for \"%s\".\r\n", sPasv ? L"passive" : L"active", strNewVirtual.c_str()); 1047 | SocketSendString(sCmd, szOutput); 1048 | sData = EstablishDataConnection(&saiData, &sPasv); 1049 | if (sData!=INVALID_SOCKET) { 1050 | swprintf_s(szOutput, L"[%u] User \"%s\" began downloading \"%s\".", sCmd, strUser.c_str(), strNewVirtual.c_str()); 1051 | pLog->Log(szOutput); 1052 | if (DoSocketFileIO(sCmd, sData, hFile, SocketFileIODirection::SEND, &dw)) { 1053 | swprintf_s(szOutput, L"226 \"%s\" transferred successfully.\r\n", strNewVirtual.c_str()); 1054 | SocketSendString(sCmd, szOutput); 1055 | swprintf_s(szOutput, L"[%u] Download completed.", sCmd); 1056 | pLog->Log(szOutput); 1057 | } else { 1058 | SocketSendString(sCmd, L"426 Connection closed; transfer aborted.\r\n"); 1059 | if (dw) SocketSendString(sCmd, L"226 ABOR command successful.\r\n"); 1060 | swprintf_s(szOutput, L"[%u] Download aborted.", sCmd); 1061 | pLog->Log(szOutput); 1062 | } 1063 | closesocket(sData); 1064 | } else { 1065 | SocketSendString(sCmd,L"425 Can't open data connection.\r\n"); 1066 | } 1067 | CloseHandle(hFile); 1068 | } 1069 | } else { 1070 | swprintf_s(szOutput, L"550 \"%s\": Read permission denied.\r\n", strNewVirtual.c_str()); 1071 | SocketSendString(sCmd, szOutput); 1072 | } 1073 | } 1074 | } 1075 | 1076 | else if (!_wcsicmp(szCmd, L"STOR") || !_wcsicmp(szCmd, L"APPE")) { 1077 | if (!*pszParam) { 1078 | SocketSendString(sCmd,L"501 Syntax error in parameters or arguments.\r\n"); 1079 | } else if (!isLoggedIn) { 1080 | SocketSendString(sCmd,L"530 Not logged in.\r\n"); 1081 | } else { 1082 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1083 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_WRITE) == 1) { 1084 | hFile = pVFS->CreateFile(strNewVirtual.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_ALWAYS); 1085 | if (hFile == INVALID_HANDLE_VALUE) { 1086 | swprintf_s(szOutput, L"550 \"%s\": Unable to open file.\r\n", strNewVirtual.c_str()); 1087 | SocketSendString(sCmd, szOutput); 1088 | } else { 1089 | if (_wcsicmp(szCmd, L"APPE") == 0) { 1090 | SetFilePointer(hFile, 0, 0, FILE_END); 1091 | } 1092 | else { 1093 | SetFilePointer(hFile, dwRestOffset, 0, FILE_BEGIN); 1094 | SetEndOfFile(hFile); 1095 | } 1096 | dwRestOffset = 0; 1097 | swprintf_s(szOutput, L"150 Opening %s mode data connection for \"%s\".\r\n", sPasv ? L"passive" : L"active", strNewVirtual.c_str()); 1098 | SocketSendString(sCmd, szOutput); 1099 | sData = EstablishDataConnection(&saiData, &sPasv); 1100 | if (sData!=INVALID_SOCKET) { 1101 | swprintf_s(szOutput, L"[%u] User \"%s\" began uploading \"%s\".", sCmd, strUser.c_str(), strNewVirtual.c_str()); 1102 | pLog->Log(szOutput); 1103 | if (DoSocketFileIO(sCmd, sData, hFile, SocketFileIODirection::RECEIVE, 0)) { 1104 | swprintf_s(szOutput, L"226 \"%s\" transferred successfully.\r\n", strNewVirtual.c_str()); 1105 | SocketSendString(sCmd, szOutput); 1106 | swprintf_s(szOutput, L"[%u] Upload completed.", sCmd); 1107 | pLog->Log(szOutput); 1108 | } else { 1109 | SocketSendString(sCmd, L"426 Connection closed; transfer aborted.\r\n"); 1110 | swprintf_s(szOutput, L"[%u] Upload aborted.", sCmd); 1111 | pLog->Log(szOutput); 1112 | } 1113 | closesocket(sData); 1114 | } else { 1115 | SocketSendString(sCmd,L"425 Can't open data connection.\r\n"); 1116 | } 1117 | CloseHandle(hFile); 1118 | } 1119 | } else { 1120 | swprintf_s(szOutput, L"550 \"%s\": Write permission denied.\r\n", strNewVirtual.c_str()); 1121 | SocketSendString(sCmd, szOutput); 1122 | } 1123 | } 1124 | } 1125 | 1126 | else if (!_wcsicmp(szCmd, L"ABOR")) { 1127 | if (sPasv) { 1128 | closesocket(sPasv); 1129 | sPasv = 0; 1130 | } 1131 | dwRestOffset = 0; 1132 | SocketSendString(sCmd,L"200 ABOR command successful.\r\n"); 1133 | } 1134 | 1135 | else if (!_wcsicmp(szCmd, L"SIZE")) { 1136 | if (!*pszParam) { 1137 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1138 | } else if (!isLoggedIn) { 1139 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1140 | } else { 1141 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1142 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_READ) == 1) { 1143 | hFile = pVFS->CreateFile(strNewVirtual.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING); 1144 | if (hFile == INVALID_HANDLE_VALUE) { 1145 | swprintf_s(szOutput, L"550 \"%s\": File not found.\r\n", strNewVirtual.c_str()); 1146 | SocketSendString(sCmd, szOutput); 1147 | } else { 1148 | swprintf_s(szOutput, L"213 %u\r\n", GetFileSize(hFile, 0)); 1149 | SocketSendString(sCmd, szOutput); 1150 | CloseHandle(hFile); 1151 | } 1152 | } else { 1153 | swprintf_s(szOutput, L"550 \"%s\": Read permission denied.\r\n", strNewVirtual.c_str()); 1154 | SocketSendString(sCmd, szOutput); 1155 | } 1156 | } 1157 | } 1158 | 1159 | else if (!_wcsicmp(szCmd, L"MDTM")) { 1160 | if (!*pszParam) { 1161 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1162 | } else if (!isLoggedIn) { 1163 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1164 | } else { 1165 | for (i = 0; i < 14; i++) { 1166 | if ((pszParam[i] < L'0') || (pszParam[i] > L'9')) { 1167 | break; 1168 | } 1169 | } 1170 | if ((i == 14) && (pszParam[14] == L' ')) { 1171 | wcsncpy_s(szOutput, pszParam, 4); 1172 | szOutput[4] = 0; 1173 | st.wYear = StrToInt(szOutput); 1174 | wcsncpy_s(szOutput, pszParam + 4, 2); 1175 | szOutput[2] = 0; 1176 | st.wMonth = StrToInt(szOutput); 1177 | wcsncpy_s(szOutput, pszParam + 6, 2); 1178 | st.wDay = StrToInt(szOutput); 1179 | wcsncpy_s(szOutput, pszParam + 8, 2); 1180 | st.wHour = StrToInt(szOutput); 1181 | wcsncpy_s(szOutput, pszParam + 10, 2); 1182 | st.wMinute = StrToInt(szOutput); 1183 | wcsncpy_s(szOutput, pszParam + 12, 2); 1184 | st.wSecond = StrToInt(szOutput); 1185 | pszParam += 15; 1186 | dw = 1; 1187 | } else { 1188 | dw = 0; 1189 | } 1190 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1191 | if (dw) { 1192 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_WRITE) == 1) { 1193 | hFile = pVFS->CreateFile(strNewVirtual.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING); 1194 | if (hFile == INVALID_HANDLE_VALUE) { 1195 | swprintf_s(szOutput, L"550 \"%s\": File not found.\r\n", strNewVirtual.c_str()); 1196 | SocketSendString(sCmd, szOutput); 1197 | } else { 1198 | SystemTimeToFileTime(&st, &ft); 1199 | SetFileTime(hFile, 0, 0, &ft); 1200 | CloseHandle(hFile); 1201 | SocketSendString(sCmd, L"250 MDTM command successful.\r\n"); 1202 | } 1203 | } else { 1204 | swprintf_s(szOutput, L"550 \"%s\": Write permission denied.\r\n", strNewVirtual.c_str()); 1205 | SocketSendString(sCmd, szOutput); 1206 | } 1207 | } else { 1208 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_READ) == 1) { 1209 | hFile = pVFS->CreateFile(strNewVirtual.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING); 1210 | if (hFile == INVALID_HANDLE_VALUE) { 1211 | swprintf_s(szOutput, L"550 \"%s\": File not found.\r\n", strNewVirtual.c_str()); 1212 | SocketSendString(sCmd, szOutput); 1213 | } else { 1214 | GetFileTime(hFile, 0, 0, &ft); 1215 | CloseHandle(hFile); 1216 | FileTimeToSystemTime(&ft, &st); 1217 | swprintf_s(szOutput, L"213 %04u%02u%02u%02u%02u%02u\r\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); 1218 | SocketSendString(sCmd, szOutput); 1219 | } 1220 | } else { 1221 | swprintf_s(szOutput, L"550 \"%s\": Read permission denied.\r\n", strNewVirtual.c_str()); 1222 | SocketSendString(sCmd, szOutput); 1223 | } 1224 | } 1225 | } 1226 | } 1227 | 1228 | else if (!_wcsicmp(szCmd, L"DELE")) { 1229 | if (!*pszParam) { 1230 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1231 | } else if (!isLoggedIn) { 1232 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1233 | } else { 1234 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1235 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_ADMIN) == 1) { 1236 | if (pVFS->FileExists(strNewVirtual.c_str())) { 1237 | if (pVFS->DeleteFile(strNewVirtual.c_str())) { 1238 | swprintf_s(szOutput, L"250 \"%s\" deleted successfully.\r\n", strNewVirtual.c_str()); 1239 | SocketSendString(sCmd, szOutput); 1240 | swprintf_s(szOutput, L"[%u] User \"%s\" deleted \"%s\".", sCmd, strUser.c_str(), strNewVirtual.c_str()); 1241 | pLog->Log(szOutput); 1242 | } else { 1243 | swprintf_s(szOutput, L"550 \"%s\": Unable to delete file.\r\n", strNewVirtual.c_str()); 1244 | SocketSendString(sCmd, szOutput); 1245 | } 1246 | } else { 1247 | swprintf_s(szOutput, L"550 \"%s\": File not found.\r\n", strNewVirtual.c_str()); 1248 | SocketSendString(sCmd, szOutput); 1249 | } 1250 | } else { 1251 | swprintf_s(szOutput, L"550 \"%s\": Admin permission denied.\r\n", strNewVirtual.c_str()); 1252 | SocketSendString(sCmd,szOutput); 1253 | } 1254 | } 1255 | } 1256 | 1257 | else if (!_wcsicmp(szCmd, L"RNFR")) { 1258 | if (!*pszParam) { 1259 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1260 | } else if (!isLoggedIn) { 1261 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1262 | } else { 1263 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1264 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_ADMIN) == 1) { 1265 | if (pVFS->FileExists(strNewVirtual.c_str())) { 1266 | strRnFr = strNewVirtual; 1267 | swprintf_s(szOutput, L"350 \"%s\": File exists; proceed with RNTO.\r\n", strNewVirtual.c_str()); 1268 | SocketSendString(sCmd, szOutput); 1269 | } else { 1270 | swprintf_s(szOutput, L"550 \"%s\": File not found.\r\n", strNewVirtual.c_str()); 1271 | SocketSendString(sCmd, szOutput); 1272 | } 1273 | } else { 1274 | swprintf_s(szOutput, L"550 \"%s\": Admin permission denied.\r\n", strNewVirtual.c_str()); 1275 | SocketSendString(sCmd, szOutput); 1276 | } 1277 | } 1278 | } 1279 | 1280 | else if (!_wcsicmp(szCmd, L"RNTO")) { 1281 | if (!*pszParam) { 1282 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1283 | } else if (!isLoggedIn) { 1284 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1285 | } else if (strRnFr.length() == 0) { 1286 | SocketSendString(sCmd, L"503 Bad sequence of commands. Send RNFR first.\r\n"); 1287 | } else { 1288 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1289 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_ADMIN) == 1) { 1290 | if (pVFS->MoveFile(strRnFr.c_str(), strNewVirtual.c_str())) { 1291 | SocketSendString(sCmd, L"250 RNTO command successful.\r\n"); 1292 | swprintf_s(szOutput, L"[%u] User \"%s\" renamed \"%s\" to \"%s\".", sCmd, strUser.c_str(), strRnFr.c_str(), strNewVirtual.c_str()); 1293 | pLog->Log(szOutput); 1294 | strRnFr.clear(); 1295 | } else { 1296 | swprintf_s(szOutput, L"553 \"%s\": Unable to rename file.\r\n", strNewVirtual.c_str()); 1297 | SocketSendString(sCmd, szOutput); 1298 | } 1299 | } else { 1300 | SocketSendString(sCmd, L"550 Admin permission denied.\r\n"); 1301 | } 1302 | } 1303 | } 1304 | 1305 | else if (!_wcsicmp(szCmd, L"MKD") || !_wcsicmp(szCmd, L"XMKD")) { 1306 | if (!*pszParam) { 1307 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1308 | } else if (!isLoggedIn) { 1309 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1310 | } else { 1311 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1312 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_WRITE) == 1) { 1313 | if (pVFS->CreateDirectory(strNewVirtual.c_str())) { 1314 | swprintf_s(szOutput, L"250 \"%s\" created successfully.\r\n", strNewVirtual.c_str()); 1315 | SocketSendString(sCmd, szOutput); 1316 | swprintf_s(szOutput, L"[%u] User \"%s\" created directory \"%s\".", sCmd, strUser.c_str(), strNewVirtual.c_str()); 1317 | pLog->Log(szOutput); 1318 | } else { 1319 | swprintf_s(szOutput, L"550 \"%s\": Unable to create directory.\r\n", strNewVirtual.c_str()); 1320 | SocketSendString(sCmd, szOutput); 1321 | } 1322 | } else { 1323 | swprintf_s(szOutput, L"550 \"%s\": Write permission denied.\r\n", strNewVirtual.c_str()); 1324 | SocketSendString(sCmd, szOutput); 1325 | } 1326 | } 1327 | } 1328 | 1329 | else if (!_wcsicmp(szCmd, L"RMD") || !_wcsicmp(szCmd, L"XRMD")) { 1330 | if (!*pszParam) { 1331 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1332 | } else if (!isLoggedIn) { 1333 | SocketSendString(sCmd, L"530 Not logged in.\r\n"); 1334 | } else { 1335 | pVFS->ResolveRelative(strCurrentVirtual.c_str(), pszParam, strNewVirtual); 1336 | if (pPerms->GetPerm(strNewVirtual.c_str(), PERM_ADMIN) == 1) { 1337 | if (pVFS->RemoveDirectory(strNewVirtual.c_str())) { 1338 | swprintf_s(szOutput, L"250 \"%s\" removed successfully.\r\n", strNewVirtual.c_str()); 1339 | SocketSendString(sCmd, szOutput); 1340 | swprintf_s(szOutput, L"[%u] User \"%s\" removed directory \"%s\".", sCmd, strUser.c_str(), strNewVirtual.c_str()); 1341 | pLog->Log(szOutput); 1342 | } else { 1343 | swprintf_s(szOutput, L"550 \"%s\": Unable to remove directory.\r\n", strNewVirtual.c_str()); 1344 | SocketSendString(sCmd, szOutput); 1345 | } 1346 | } else { 1347 | swprintf_s(szOutput, L"550 \"%s\": Admin permission denied.\r\n", strNewVirtual.c_str()); 1348 | SocketSendString(sCmd, szOutput); 1349 | } 1350 | } 1351 | } 1352 | 1353 | else if (!_wcsicmp(szCmd, L"OPTS")) { 1354 | if (!*pszParam) { 1355 | SocketSendString(sCmd, L"501 Syntax error in parameters or arguments.\r\n"); 1356 | } else if (!_wcsicmp(pszParam, L"UTF8 On")) { 1357 | SocketSendString(sCmd, L"200 Always in UTF8 mode.\r\n"); 1358 | } else { 1359 | SocketSendString(sCmd, L"501 Option not understood.\r\n"); 1360 | } 1361 | } 1362 | 1363 | else { 1364 | swprintf_s(szOutput,L"500 Syntax error, command \"%s\" unrecognized.\r\n",szCmd); 1365 | SocketSendString(sCmd,szOutput); 1366 | } 1367 | 1368 | } 1369 | 1370 | if (sPasv) closesocket(sPasv); 1371 | closesocket(sCmd); 1372 | 1373 | if (isLoggedIn) { 1374 | InterlockedDecrement(&dwActiveConnections); 1375 | } 1376 | 1377 | swprintf_s(szOutput,L"[%u] Connection closed.",sCmd); 1378 | pLog->Log(szOutput); 1379 | } 1380 | 1381 | bool SocketSendString(SOCKET s, const wchar_t *psz) 1382 | { 1383 | int nWideSize = wcslen(psz); 1384 | int nUtf8Size; 1385 | char *buf; 1386 | bool bSuccess = false; 1387 | 1388 | nUtf8Size = WideCharToMultiByte(CP_UTF8, 0, psz, nWideSize, NULL, 0, NULL, NULL); 1389 | if (nUtf8Size==0) return false; 1390 | 1391 | buf = new char[nUtf8Size]; 1392 | nUtf8Size = WideCharToMultiByte(CP_UTF8, 0, psz, nWideSize, buf, nUtf8Size, NULL, NULL); 1393 | if (nUtf8Size != 0) { 1394 | bSuccess = send(s, buf, nUtf8Size, 0)!=SOCKET_ERROR; 1395 | } 1396 | delete[] buf; 1397 | return bSuccess; 1398 | } 1399 | 1400 | ReceiveStatus SocketReceiveString(SOCKET s, wchar_t *psz, DWORD dwMaxChars, DWORD *pdwCharsReceived) 1401 | { 1402 | DWORD dwChars = 0; 1403 | ReceiveStatus status, statusError; 1404 | wchar_t buf[2]; 1405 | DWORD dw; 1406 | 1407 | for (;;) { 1408 | if (dwChars==dwMaxChars) { 1409 | statusError = ReceiveStatus::INSUFFICIENT_BUFFER; 1410 | break; 1411 | } 1412 | 1413 | status = SocketReceiveLetter(s, psz, dwMaxChars-dwChars, &dw); 1414 | if (status == ReceiveStatus::OK) { 1415 | dwChars += dw; 1416 | if (*psz==L'\r') *psz=0; 1417 | else if (*psz==L'\n') { 1418 | *psz=0; 1419 | *pdwCharsReceived=dwChars; 1420 | return ReceiveStatus::OK; 1421 | } 1422 | psz += dw; 1423 | } else if (status == ReceiveStatus::INVALID_DATA || status == ReceiveStatus::INSUFFICIENT_BUFFER) { 1424 | statusError = status; 1425 | break; 1426 | } else { 1427 | return status; 1428 | } 1429 | } 1430 | 1431 | // A non-critical error occurred, read until end of line 1432 | for (;;) { 1433 | status = SocketReceiveLetter(s, buf, sizeof(buf)/sizeof(wchar_t), &dw); 1434 | if (status == ReceiveStatus::OK) { 1435 | if (*buf==L'\n') { 1436 | return statusError; 1437 | } 1438 | } else if (status == ReceiveStatus::INVALID_DATA || status == ReceiveStatus::INSUFFICIENT_BUFFER) { 1439 | // Go on... 1440 | } else { 1441 | return status; 1442 | } 1443 | } 1444 | } 1445 | 1446 | ReceiveStatus SocketReceiveLetter(SOCKET s, wchar_t *pch, DWORD dwMaxChars, DWORD *pdwCharsReceived) 1447 | { 1448 | char buf[4]; 1449 | DWORD dwCharLength; 1450 | DWORD dw; 1451 | TIMEVAL tv; 1452 | fd_set fds; 1453 | 1454 | tv.tv_sec = dwCommandTimeout; 1455 | tv.tv_usec = 0; 1456 | FD_ZERO(&fds); 1457 | FD_SET(s, &fds); 1458 | dw = select(0, &fds, 0, 0, &tv); 1459 | if (dw == SOCKET_ERROR || dw == 0) return ReceiveStatus::TIMEOUT; 1460 | dw = recv(s, &buf[0], 1, 0); 1461 | if (dw == SOCKET_ERROR || dw == 0) return ReceiveStatus::NETWORK_ERROR; 1462 | 1463 | if ((buf[0] & 0x80) == 0x00) { // 0xxxxxxx 1464 | dwCharLength = 1; 1465 | } else if ((buf[0] & 0xE0) == 0xC0) { // 110xxxxx 1466 | dwCharLength = 2; 1467 | } else if ((buf[0] & 0xF0) == 0xE0) { // 1110xxxx 1468 | dwCharLength = 3; 1469 | } else if ((buf[0] & 0xF8) == 0xF0) { // 11110xxx 1470 | dwCharLength = 4; 1471 | } else { 1472 | return ReceiveStatus::INVALID_DATA; 1473 | } 1474 | 1475 | if (dwCharLength > 1) { 1476 | dw = recv(s, &buf[1], dwCharLength-1, 0); 1477 | if (dw == SOCKET_ERROR || dw == 0) return ReceiveStatus::NETWORK_ERROR; 1478 | } 1479 | 1480 | if (dwMaxChars == 0) { 1481 | return ReceiveStatus::INSUFFICIENT_BUFFER; 1482 | } 1483 | 1484 | dw = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buf, dwCharLength, pch, dwMaxChars); 1485 | if (dw == 0) { 1486 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 1487 | return ReceiveStatus::INSUFFICIENT_BUFFER; 1488 | } else { 1489 | return ReceiveStatus::INVALID_DATA; 1490 | } 1491 | } 1492 | 1493 | *pdwCharsReceived = dw; 1494 | return ReceiveStatus::OK; 1495 | } 1496 | 1497 | ReceiveStatus SocketReceiveData(SOCKET s, char *psz, DWORD dwBytesToRead, DWORD *pdwBytesRead) 1498 | { 1499 | DWORD dw; 1500 | TIMEVAL tv; 1501 | fd_set fds; 1502 | 1503 | tv.tv_sec=dwConnectTimeout; 1504 | tv.tv_usec=0; 1505 | FD_ZERO(&fds); 1506 | FD_SET(s,&fds); 1507 | dw=select(0,&fds,0,0,&tv); 1508 | if (dw==SOCKET_ERROR || dw==0) return ReceiveStatus::TIMEOUT; 1509 | dw=recv(s,psz,dwBytesToRead,0); 1510 | if (dw==SOCKET_ERROR) return ReceiveStatus::NETWORK_ERROR; 1511 | *pdwBytesRead=dw; 1512 | return ReceiveStatus::OK; 1513 | } 1514 | 1515 | SOCKET EstablishDataConnection(SOCKADDR_IN *psaiData, SOCKET *psPasv) 1516 | { 1517 | SOCKET sData; 1518 | DWORD dw; 1519 | TIMEVAL tv; 1520 | fd_set fds; 1521 | 1522 | if (psPasv && *psPasv) { 1523 | tv.tv_sec=dwConnectTimeout; 1524 | tv.tv_usec=0; 1525 | FD_ZERO(&fds); 1526 | FD_SET(*psPasv,&fds); 1527 | dw=select(0,&fds,0,0,&tv); 1528 | if (dw && dw!=SOCKET_ERROR) { 1529 | dw=sizeof(SOCKADDR_IN); 1530 | sData=accept(*psPasv,(SOCKADDR *)psaiData,(int *)&dw); 1531 | } else { 1532 | sData=0; 1533 | } 1534 | closesocket(*psPasv); 1535 | *psPasv=0; 1536 | return sData; 1537 | } else { 1538 | sData=socket(AF_INET,SOCK_STREAM, IPPROTO_TCP); 1539 | if (connect(sData,(SOCKADDR *)psaiData,sizeof(SOCKADDR_IN))) { 1540 | closesocket(sData); 1541 | return INVALID_SOCKET; 1542 | } else { 1543 | return sData; 1544 | } 1545 | } 1546 | } 1547 | 1548 | void LookupHost(const SOCKADDR_IN *sai, wchar_t *pszHostName, size_t stHostName) 1549 | // Performs a reverse DNS lookup on ia. If no host name could be resolved, or 1550 | // if LookupHosts is Off, pszHostName will contain a string representation of 1551 | // the given IP address. 1552 | { 1553 | DWORD dw; 1554 | 1555 | if (bLookupHosts) { 1556 | if (GetNameInfo((SOCKADDR *)sai, sizeof(SOCKADDR_IN), pszHostName, stHostName, NULL, 0, 0) == 0) { 1557 | return; 1558 | } 1559 | } 1560 | 1561 | dw = stHostName; 1562 | if (WSAAddressToString((SOCKADDR *)sai, sizeof(SOCKADDR_IN), NULL, pszHostName, &dw) == 0) { 1563 | return; 1564 | } 1565 | 1566 | wcscpy_s(pszHostName, stHostName, L"???"); 1567 | } 1568 | 1569 | bool DoSocketFileIO(SOCKET sCmd, SOCKET sData, HANDLE hFile, SocketFileIODirection direction, DWORD *pdwAbortFlag) 1570 | { 1571 | char szBuffer[PACKET_SIZE]; 1572 | wchar_t szCmd[512]; 1573 | DWORD dw; 1574 | 1575 | if (pdwAbortFlag) *pdwAbortFlag = 0; 1576 | switch (direction) { 1577 | case SocketFileIODirection::SEND: 1578 | for (;;) { 1579 | if (!ReadFile(hFile, szBuffer, PACKET_SIZE, &dw, 0)) return false; 1580 | if (!dw) return true; 1581 | if (send(sData, szBuffer, dw, 0) == SOCKET_ERROR) return false; 1582 | ioctlsocket(sCmd, FIONREAD, &dw); 1583 | if (dw) { 1584 | if (SocketReceiveString(sCmd, szCmd, ARRAYSIZE(szCmd), &dw) == ReceiveStatus::OK) { 1585 | if (!_wcsicmp(szCmd, L"ABOR")) { 1586 | *pdwAbortFlag = 1; 1587 | return false; 1588 | } else { 1589 | SocketSendString(sCmd, L"500 Only command allowed at this time is ABOR.\r\n"); 1590 | } 1591 | } 1592 | } 1593 | } 1594 | break; 1595 | case SocketFileIODirection::RECEIVE: 1596 | for (;;) { 1597 | if (SocketReceiveData(sData, szBuffer, PACKET_SIZE, &dw) != ReceiveStatus::OK) return false; 1598 | if (dw == 0) return true; 1599 | if (!WriteFile(hFile, szBuffer, dw, &dw, 0)) return false; 1600 | } 1601 | break; 1602 | default: 1603 | return false; 1604 | } 1605 | } 1606 | 1607 | bool FileSkipBOM(HANDLE hFile) 1608 | { 1609 | DWORD dw, dwBytesRead; 1610 | wchar_t chBOM; 1611 | 1612 | if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { 1613 | return false; 1614 | } 1615 | 1616 | dw = ReadFile(hFile, &chBOM, sizeof(wchar_t), &dwBytesRead, 0); 1617 | if (!dw) { 1618 | return false; 1619 | } 1620 | 1621 | if (dwBytesRead > 0) { 1622 | if (dwBytesRead < sizeof(wchar_t) || chBOM!=0xFEFF) { 1623 | SetFilePointer(hFile, 0, NULL, FILE_BEGIN); 1624 | } 1625 | } 1626 | 1627 | return true; 1628 | } 1629 | 1630 | DWORD FileReadLine(HANDLE hFile, wchar_t *pszBuf, DWORD dwBufLen) 1631 | { 1632 | // Reads a line from an open text file into a character buffer, discarding the 1633 | // trailing CR/LF, up to dwBufLen bytes. Any additional bytes are discarded. 1634 | // Returns the number of characters in the line, excluding the CR/LF, or -1 if 1635 | // the end of the file was reached. May be greater than dwBufLen to indicate 1636 | // that bytes were discarded. Note that a return value of 0 does not 1637 | // necessarily indicate an error; it could mean a blank line was read. 1638 | 1639 | DWORD dw, dwBytesRead, dwCount; 1640 | 1641 | for (dwCount=0;;) { 1642 | dw=ReadFile(hFile,pszBuf,sizeof(wchar_t),&dwBytesRead,0); 1643 | if (!dw || (dw && !dwBytesRead && !dwCount)) return -1; 1644 | if (dwBytesRead!=sizeof(wchar_t) || *pszBuf==L'\n') break; 1645 | if (*pszBuf!=L'\r') { 1646 | dwCount++; 1647 | if (dwCount 2 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 25 | 28 | 31 | 34 | 37 | 40 | 53 | 56 | 59 | 62 | 72 | 75 | 78 | 81 | 84 | 87 | 90 | 93 | 96 | 97 | 106 | 109 | 112 | 115 | 118 | 121 | 132 | 135 | 138 | 141 | 153 | 156 | 159 | 162 | 165 | 168 | 171 | 174 | 177 | 178 | 179 | 180 | 181 | 182 | 186 | 189 | 190 | 193 | 194 | 197 | 198 | 201 | 202 | 205 | 206 | 207 | 211 | 214 | 215 | 218 | 219 | 222 | 223 | 226 | 227 | 230 | 231 | 234 | 235 | 236 | 240 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /SlimFTPd31.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {06EF787B-A2B5-4D63-9B1B-C5614640D8B6} 15 | Win32Proj 16 | 10.0.17134.0 17 | 18 | 19 | 20 | Application 21 | v141 22 | Unicode 23 | true 24 | 25 | 26 | Application 27 | v141 28 | Unicode 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | <_ProjectFileVersion>12.0.30501.0 42 | 43 | 44 | Debug\ 45 | Debug\ 46 | true 47 | 48 | 49 | Release\ 50 | Release\ 51 | false 52 | 53 | 54 | 55 | Disabled 56 | true 57 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 58 | true 59 | MultiThreadedDebug 60 | false 61 | 62 | Level3 63 | EditAndContinue 64 | 65 | 66 | ws2_32.lib;shlwapi.lib;%(AdditionalDependencies) 67 | true 68 | $(OutDir)SlimFTPd31.pdb 69 | Windows 70 | MachineX86 71 | false 72 | 73 | 74 | 75 | 76 | true 77 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 78 | MultiThreadedDLL 79 | false 80 | 81 | Level3 82 | ProgramDatabase 83 | 84 | 85 | ws2_32.lib;shlwapi.lib;%(AdditionalDependencies) 86 | Windows 87 | true 88 | true 89 | true 90 | MachineX86 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /SlimFTPd31.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {0615eb23-345c-4e82-8b5f-5b9d6a6bd835} 6 | cpp;c;cxx;def;odl;idl;hpj;bat;asm 7 | 8 | 9 | {b7e36405-cc54-47ff-b36a-0e6256246325} 10 | h;hpp;hxx;hm;inl;inc 11 | 12 | 13 | {0123e42b-7fcc-4d5f-896a-4e23283be001} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | 35 | 36 | Header Files 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | 55 | 56 | Resource Files 57 | 58 | 59 | -------------------------------------------------------------------------------- /permdb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include "permdb.h" 31 | #include "tree.h" 32 | 33 | PermDB::PermDB() 34 | { 35 | _root._data.dwPerms[PERM_READ] = 0; 36 | _root._data.dwPerms[PERM_WRITE] = 0; 37 | _root._data.dwPerms[PERM_LIST] = 0; 38 | _root._data.dwPerms[PERM_ADMIN] = 0; 39 | } 40 | 41 | void PermDB::SetPerm(const wchar_t *pszVirtual, DWORD dwPermId, DWORD dwStatus) 42 | { 43 | tree *ptree, *pparent; 44 | wchar_t sz[512], *psz, *pszCut; 45 | 46 | ptree = &_root; 47 | wcscpy_s(sz, pszVirtual + 1); 48 | psz = sz; 49 | while (*psz) { 50 | if (pszCut = wcschr(psz, L'/')) *pszCut = 0; 51 | pparent = ptree; 52 | ptree = ptree->_pdown; 53 | while (ptree && _wcsicmp(ptree->_data.strVirtual.c_str(), psz)) ptree = ptree->_pright; 54 | if (!ptree) { 55 | ptree=new tree(pparent); 56 | ptree->_data.strVirtual = psz; 57 | ptree->_data.dwPerms[PERM_READ] = -1; 58 | ptree->_data.dwPerms[PERM_WRITE] = -1; 59 | ptree->_data.dwPerms[PERM_LIST] = -1; 60 | ptree->_data.dwPerms[PERM_ADMIN] = -1; 61 | } 62 | if (!pszCut) break; 63 | psz = pszCut + 1; 64 | } 65 | ptree->_data.strVirtual = psz; 66 | ptree->_data.dwPerms[dwPermId] = dwStatus; 67 | } 68 | 69 | DWORD PermDB::GetPerm(const wchar_t *pszVirtual, DWORD dwPermId) 70 | { 71 | return GetPermFunc(pszVirtual, dwPermId, &_root); 72 | } 73 | 74 | DWORD PermDB::GetPermFunc(const wchar_t *pszVirtual, DWORD dwPermId, tree *ptree) 75 | { 76 | const wchar_t *psz; 77 | DWORD dw; 78 | UINT_PTR dwLen; 79 | 80 | psz = wcschr(pszVirtual, L'/'); 81 | if (psz) dwLen = psz - pszVirtual; 82 | else dwLen = wcslen(pszVirtual); 83 | while (ptree) { 84 | if ((ptree->_data.strVirtual.length() == dwLen) && (!dwLen || !_wcsnicmp(pszVirtual, ptree->_data.strVirtual.c_str(), dwLen))) { 85 | if (psz) { 86 | dw = GetPermFunc(psz + 1, dwPermId, ptree->_pdown); 87 | if (dw != -1) return dw; 88 | else return ptree->_data.dwPerms[dwPermId]; 89 | } else { 90 | return ptree->_data.dwPerms[dwPermId]; 91 | } 92 | } else { 93 | ptree = ptree->_pright; 94 | } 95 | } 96 | return -1; 97 | } 98 | -------------------------------------------------------------------------------- /permdb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef _INCL_PERMDB_H 31 | #define _INCL_PERMDB_H 32 | 33 | #include 34 | #include 35 | #include "tree.h" 36 | 37 | using namespace std; 38 | 39 | #define PERM_READ 0 40 | #define PERM_WRITE 1 41 | #define PERM_LIST 2 42 | #define PERM_ADMIN 3 43 | 44 | class PermDB 45 | { 46 | private: 47 | struct FTPPERM { 48 | wstring strVirtual; 49 | DWORD dwPerms[4]; 50 | }; 51 | 52 | tree _root; 53 | 54 | static DWORD GetPermFunc(const wchar_t *pszVirtual, DWORD dwPermId, tree *ptree); 55 | 56 | public: 57 | PermDB(); 58 | void SetPerm(const wchar_t *pszVirtual, DWORD dwPermId, DWORD dwStatus); 59 | DWORD GetPerm(const wchar_t *pszVirtual, DWORD dwPermId); 60 | }; 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by SlimFTPd.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /synclogger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include "synclogger.h" 31 | #include 32 | 33 | SyncLogger::SyncLogger(const wchar_t *pszFilename) 34 | { 35 | _hLogFile=CreateFile(pszFilename,GENERIC_WRITE,FILE_SHARE_READ,0,OPEN_ALWAYS,0,0); 36 | if (_hLogFile!=INVALID_HANDLE_VALUE) { 37 | SetFilePointer(_hLogFile,0,0,FILE_END); 38 | _hLoggerThread=(HANDLE)_beginthreadex(NULL,0,SyncLoggerThread,this,0,(unsigned int *)&_dwLoggerThreadId); 39 | } else { 40 | _hLoggerThread=NULL; 41 | } 42 | } 43 | 44 | SyncLogger::~SyncLogger() 45 | { 46 | if (_hLoggerThread) { 47 | PostThreadMessage(_dwLoggerThreadId,WM_QUIT,0,0); 48 | WaitForSingleObject(_hLoggerThread,INFINITE); 49 | CloseHandle(_hLoggerThread); 50 | } 51 | if (_hLogFile!=INVALID_HANDLE_VALUE) CloseHandle(_hLogFile); 52 | } 53 | 54 | void SyncLogger::Log(const wchar_t *pszText) 55 | { 56 | wchar_t *psz; 57 | DWORD dwDateLen, dwTimeLen; 58 | 59 | if (_hLogFile!=INVALID_HANDLE_VALUE && _dwLoggerThreadId && pszText) { 60 | dwDateLen=GetDateFormat(LOCALE_SYSTEM_DEFAULT,DATE_SHORTDATE,0,0,0,0); 61 | dwTimeLen=GetTimeFormat(LOCALE_SYSTEM_DEFAULT,0,0,0,0,0); 62 | size_t buflen = dwDateLen+dwTimeLen+wcslen(pszText)+5; 63 | psz=new wchar_t[buflen]; 64 | psz[0]=L'['; 65 | GetDateFormat(LOCALE_SYSTEM_DEFAULT,DATE_SHORTDATE,0,0,psz+1,dwDateLen); 66 | psz[dwDateLen]=L' '; 67 | GetTimeFormat(LOCALE_SYSTEM_DEFAULT,0,0,0,psz+dwDateLen+1,dwTimeLen); 68 | wcscat_s(psz, buflen, L"] "); 69 | wcscat_s(psz, buflen, pszText); 70 | wcscat_s(psz, buflen, L"\r\n"); 71 | while (!PostThreadMessage(_dwLoggerThreadId,WM_USER,0,(LPARAM)psz)) Sleep(0); 72 | } 73 | } 74 | 75 | unsigned __stdcall SyncLogger::SyncLoggerThread(void *pParam) 76 | { 77 | SyncLogger *pthis = (SyncLogger *)pParam; 78 | MSG msg; 79 | DWORD dw; 80 | 81 | PeekMessage(&msg,0,0,0,PM_NOREMOVE); 82 | while (GetMessage(&msg,0,0,0)) { 83 | switch (msg.message) { 84 | case WM_USER: 85 | WriteFile(pthis->_hLogFile, (wchar_t *)msg.lParam, (DWORD)wcslen((wchar_t *)msg.lParam)*sizeof(wchar_t), &dw, 0); 86 | delete[] (wchar_t *)msg.lParam; 87 | break; 88 | } 89 | } 90 | 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /synclogger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef _INCL_SYNCLOGGER_H 31 | #define _INCL_SYNCLOGGER_H 32 | 33 | #include 34 | 35 | class SyncLogger 36 | { 37 | private: 38 | HANDLE _hLogFile; 39 | HANDLE _hLoggerThread; 40 | DWORD _dwLoggerThreadId; 41 | 42 | static unsigned __stdcall SyncLoggerThread(void *pParam); 43 | 44 | public: 45 | SyncLogger(const wchar_t *pszFilename); 46 | ~SyncLogger(); 47 | void Log(const wchar_t *pszText); 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /tree.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef _INCL_TREE_H 31 | #define _INCL_TREE_H 32 | 33 | #include 34 | 35 | template 36 | class tree 37 | { 38 | public: 39 | tree *_pup; 40 | tree *_pleft; 41 | tree *_pright; 42 | tree *_pdown; 43 | T _data; 44 | 45 | tree() 46 | { 47 | _pup=0; 48 | _pleft=0; 49 | _pright=0; 50 | _pdown=0; 51 | } 52 | 53 | tree(tree *pparent) 54 | { 55 | if (pparent->_pdown) { 56 | _pleft=pparent->_pdown; 57 | while (_pleft->_pright) _pleft=_pleft->_pright; 58 | _pleft->_pright=this; 59 | } else { 60 | pparent->_pdown=this; 61 | _pleft=0; 62 | } 63 | _pup=pparent; 64 | _pright=0; 65 | _pdown=0; 66 | } 67 | 68 | ~tree() 69 | { 70 | while (_pdown) delete _pdown; 71 | if (_pleft) _pleft->_pright=_pright; 72 | if (_pright) _pright->_pleft=_pleft; 73 | if (_pup && _pup->_pdown==this) _pup->_pdown=_pright; 74 | } 75 | }; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /userdb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include "userdb.h" 31 | #include "tree.h" 32 | 33 | bool UserDB::Add(const wchar_t *pszUsername) 34 | { 35 | if (_users.find(pszUsername) == _users.end()) { 36 | _users.insert(std::make_pair(pszUsername, USERDBRECORD())); 37 | return true; 38 | } else { 39 | return false; 40 | } 41 | } 42 | 43 | bool UserDB::SetPassword(const wchar_t *pszUsername, const wchar_t *pszPassword) 44 | { 45 | map_type::iterator it = _users.find(pszUsername); 46 | if (it != _users.end()) { 47 | it->second.strPassword = pszPassword; 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | VFS * UserDB::GetVFS(const wchar_t *pszUsername) 54 | { 55 | map_type::iterator it = _users.find(pszUsername); 56 | if (it != _users.end()) { 57 | return &it->second.vfs; 58 | } 59 | return NULL; 60 | } 61 | 62 | PermDB * UserDB::GetPermDB(const wchar_t *pszUsername) 63 | { 64 | map_type::iterator it = _users.find(pszUsername); 65 | if (it != _users.end()) { 66 | return &it->second.perms; 67 | } 68 | return NULL; 69 | } 70 | 71 | bool UserDB::CheckPassword(const wchar_t *pszUsername, const wchar_t *pszPassword) 72 | { 73 | map_type::iterator it = _users.find(pszUsername); 74 | return (it != _users.end()) && (it->second.strPassword == pszPassword); 75 | } 76 | -------------------------------------------------------------------------------- /userdb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef _INCL_USERDB_H 31 | #define _INCL_USERDB_H 32 | 33 | #include 34 | #include 35 | #include "String.h" 36 | #include "vfs.h" 37 | #include "permdb.h" 38 | 39 | class UserDB { 40 | private: 41 | struct USERDBRECORD { 42 | wstring strPassword; 43 | VFS vfs; 44 | PermDB perms; 45 | }; 46 | typedef std::map map_type; 47 | map_type _users; 48 | 49 | public: 50 | bool Add(const wchar_t *pszUsername); 51 | bool SetPassword(const wchar_t *pszUsername, const wchar_t *pszPassword); 52 | VFS *GetVFS(const wchar_t *pszUsername); 53 | PermDB *GetPermDB(const wchar_t *pszUsername); 54 | bool CheckPassword(const wchar_t *pszUsername, const wchar_t *pszPassword); 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /vfs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include "vfs.h" 31 | #include 32 | #include "tree.h" 33 | #define STRSAFE_NO_DEPRECATE 34 | #include 35 | 36 | VFS::VFS() 37 | { 38 | } 39 | 40 | void VFS::Mount(const wchar_t *pszVirtual, const wchar_t *pszLocal) 41 | // Creates a new mount point in the virtual file system. 42 | { 43 | tree *ptree, *pparent; 44 | 45 | ptree = &_root; 46 | size_t i = 0; 47 | wstring dir; 48 | while (pszVirtual[i] != 0) { 49 | ++i; 50 | if (pszVirtual[i] == 0) break; 51 | size_t j = wcscspn(pszVirtual + i, L"/"); 52 | dir.assign(pszVirtual + i, j); 53 | pparent = ptree; 54 | ptree = ptree->_pdown; 55 | while (ptree && _wcsicmp(ptree->_data.strVirtual.c_str(), dir.c_str())) ptree = ptree->_pright; 56 | if (!ptree) { 57 | ptree = new tree(pparent); 58 | ptree->_data.strVirtual = dir; 59 | } 60 | i += j; 61 | } 62 | ptree->_data.strLocal = pszLocal; 63 | } 64 | 65 | DWORD VFS::GetDirectoryListing(const wchar_t *pszVirtual, DWORD dwIsNLST, listing_type &listing) 66 | // Fills a map class with lines comprising an FTP-style directory listing. 67 | // If dwIsNLST is non-zero, will return filenames only. 68 | { 69 | wchar_t szLine[512]; 70 | const wchar_t *pszMonthAbbr[]={L"Jan",L"Feb",L"Mar",L"Apr",L"May",L"Jun",L"Jul",L"Aug",L"Sep",L"Oct",L"Nov",L"Dec"}; 71 | LPVOID hFind; 72 | WIN32_FIND_DATA w32fd; 73 | SYSTEMTIME stCutoff, stFile; 74 | 75 | if (IsFolder(pszVirtual)) { 76 | wstring str; 77 | ResolveRelative(pszVirtual, L"*", str); 78 | hFind = FindFirstFile(str.c_str(), &w32fd); 79 | } 80 | else { 81 | hFind = FindFirstFile(pszVirtual, &w32fd); 82 | } 83 | if (hFind) { 84 | GetSystemTime(&stCutoff); 85 | stCutoff.wYear--; 86 | do { 87 | if (!wcscmp(w32fd.cFileName, L".") || !wcscmp(w32fd.cFileName, L"..")) continue; 88 | FileTimeToSystemTime(&w32fd.ftLastWriteTime, &stFile); 89 | if (dwIsNLST) { 90 | wcscpy_s(szLine, w32fd.cFileName); 91 | if (w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 92 | wcscat_s(szLine, L"/"); 93 | } 94 | } else { 95 | wsprintf(szLine, L"%c--------- 1 ftp ftp %10u %s %2u ", (w32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? L'd' : L'-', w32fd.nFileSizeLow, pszMonthAbbr[stFile.wMonth-1], stFile.wDay); 96 | if ((stFile.wYear > stCutoff.wYear) || ((stFile.wYear == stCutoff.wYear) && ((stFile.wMonth > stCutoff.wMonth) || ((stFile.wMonth == stCutoff.wMonth) && (stFile.wDay > stCutoff.wDay))))) { 97 | wsprintf(szLine + wcslen(szLine), L"%.2u:%.2u ", stFile.wHour, stFile.wMinute); 98 | } else { 99 | wsprintf(szLine + wcslen(szLine), L"%5u ", stFile.wYear); 100 | } 101 | wcscat_s(szLine, w32fd.cFileName); 102 | } 103 | wcscat_s(szLine,L"\r\n"); 104 | listing_type::iterator it = listing.find(w32fd.cFileName); 105 | if (it != listing.end()) { 106 | it->second = szLine; 107 | } 108 | else { 109 | listing.insert(std::make_pair(w32fd.cFileName, szLine)); 110 | } 111 | } while (FindNextFile(hFind, &w32fd)); 112 | FindClose(hFind); 113 | return 1; 114 | } else { 115 | return 0; 116 | } 117 | } 118 | 119 | DWORD VFS::Map(const wchar_t *pszVirtual, wstring &strLocal, tree *ptree) 120 | // Recursive function to map a virtual path to a local path. 121 | { 122 | const wchar_t *psz; 123 | UINT_PTR dwLen; 124 | 125 | psz = wcschr(pszVirtual, L'/'); 126 | if (psz) dwLen = psz - pszVirtual; 127 | else dwLen = wcslen(pszVirtual); 128 | while (ptree) { 129 | if ((ptree->_data.strVirtual.length() == dwLen) && (!dwLen || !_wcsnicmp(pszVirtual, ptree->_data.strVirtual.c_str(), dwLen))) { 130 | if (psz) { 131 | if (Map(psz + 1, strLocal, ptree->_pdown)) return 1; 132 | else { 133 | if (ptree->_data.strLocal.length() != 0) { 134 | strLocal = ptree->_data.strLocal; 135 | strLocal += psz; 136 | replace(strLocal.begin(), strLocal.end(), L'/', L'\\'); 137 | return 1; 138 | } else { 139 | return 0; 140 | } 141 | } 142 | } else { 143 | strLocal = ptree->_data.strLocal; 144 | return 1; 145 | } 146 | } else { 147 | ptree = ptree->_pright; 148 | } 149 | } 150 | strLocal.clear(); 151 | return 0; 152 | } 153 | 154 | tree * VFS::FindMountPoint(const wchar_t *pszVirtual, tree *ptree) 155 | // Returns a pointer to the tree node described by pszVirtual, or 0. 156 | { 157 | const wchar_t *psz; 158 | UINT_PTR dwLen; 159 | 160 | if (!wcscmp(pszVirtual, L"/")) return ptree; 161 | psz = wcschr(pszVirtual, L'/'); 162 | if (psz) dwLen = psz - pszVirtual; 163 | else dwLen = wcslen(pszVirtual); 164 | while (ptree) { 165 | if ((ptree->_data.strVirtual.length() == dwLen) && (!dwLen || !_wcsnicmp(pszVirtual, ptree->_data.strVirtual.c_str(), dwLen))) { 166 | if (psz) { 167 | return FindMountPoint(psz + 1, ptree->_pdown); 168 | } else { 169 | return ptree; 170 | } 171 | } else { 172 | ptree = ptree->_pright; 173 | } 174 | } 175 | return 0; 176 | } 177 | 178 | void VFS::CleanVirtualPath(const wchar_t *pszVirtual, wstring &strNewVirtual) 179 | // Strips utter rubbish out of a virtual path. 180 | // Ex: /home/./user//...\ftp/ => /home/ftp 181 | { 182 | const wchar_t *in = pszVirtual; 183 | wchar_t *buf = new wchar_t[wcslen(pszVirtual) + 4]; 184 | buf[0] = L'\0'; buf[1] = L'\0'; buf[2] = L'\0'; 185 | wchar_t *out = buf + 3; 186 | do { 187 | *out = *in; 188 | if (*out == L'\\') *out = L'/'; // convert backslashes to forward slashes 189 | if ((*out == L'\0') || (*out == L'/')) { 190 | if (out[-1] == L'.') { // output ends with "." 191 | if (out[-2] == L'\0') --out; // entire output is "." 192 | else if (out[-2] == L'/') { // output ends with "/." 193 | if (out[-3] == L'\0') --out; // entire output is "/." 194 | else out -= 2; 195 | } 196 | else if (out[-2] == L'.') { // output ends with ".." 197 | if (out[-3] == L'\0') out -= 2; // entire output is ".." 198 | else if (out[-3] == L'/') { // output ends with "/.." 199 | if (out[-4] == L'\0') out -= 2; // entire output is "/.." 200 | else { 201 | out -= 3; 202 | while ((out[-1] != L'\0') && (out[-1] != L'/')) --out; 203 | } 204 | } 205 | } 206 | else ++in; 207 | } 208 | else { 209 | ++in; 210 | if (out[-1] != L'/') ++out; 211 | } 212 | } 213 | else ++in, ++out; 214 | } while (in[-1] != L'\0'); 215 | strNewVirtual = buf + 3; 216 | delete[] buf; 217 | } 218 | 219 | void VFS::ResolveRelative(const wchar_t *pszCurrentVirtual, const wchar_t *pszRelativeVirtual, wstring &strNewVirtual) 220 | // Concatenates pszRelativeVirtual to pszCurrentVirtual and resolves. 221 | { 222 | if (*pszRelativeVirtual!=L'/') { 223 | strNewVirtual = pszCurrentVirtual; 224 | strNewVirtual += L"/"; 225 | strNewVirtual += pszRelativeVirtual; 226 | CleanVirtualPath(strNewVirtual.c_str(), strNewVirtual); 227 | } 228 | else { 229 | CleanVirtualPath(pszRelativeVirtual, strNewVirtual); 230 | } 231 | } 232 | 233 | bool VFS::WildcardMatch(const wchar_t *pszFilespec, const wchar_t *pszFilename) 234 | // Returns true iff pszFilename matches wildcard pattern pszFilespec. 235 | { 236 | if (*pszFilespec == 0) return true; 237 | while (*pszFilespec) { 238 | if (*pszFilespec == L'*') { 239 | pszFilespec++; 240 | do { 241 | if (WildcardMatch(pszFilespec, pszFilename)) return true; 242 | } while (*pszFilename++); 243 | return false; 244 | } else if (((*pszFilespec | 0x20) != (*pszFilename | 0x20)) && (*pszFilespec != L'?')) { 245 | return false; 246 | } 247 | pszFilespec++; 248 | pszFilename++; 249 | } 250 | if (!*pszFilespec && !*pszFilename) return true; 251 | else return false; 252 | } 253 | 254 | LPVOID VFS::FindFirstFile(const wchar_t *pszVirtual, WIN32_FIND_DATA *pw32fd) 255 | // Returns a find handle if a match was found. Otherwise returns 0. 256 | // Supports wildcards. 257 | { 258 | FINDDATA *pfd; 259 | const wchar_t *psz; 260 | wstring str; 261 | 262 | psz = wcsrchr(pszVirtual, L'/'); 263 | if (psz == NULL) return NULL; 264 | str.assign(pszVirtual, psz); 265 | pfd = new FINDDATA; 266 | pfd->hFind = 0; 267 | pfd->strVirtual = pszVirtual; 268 | pfd->strFilespec = psz + 1; 269 | pfd->ptree = FindMountPoint(str.c_str(), &_root); 270 | if (pfd->ptree) pfd->ptree = pfd->ptree->_pdown; 271 | 272 | if (FindNextFile(pfd, pw32fd)) return pfd; 273 | else { 274 | delete pfd; 275 | return 0; 276 | } 277 | } 278 | 279 | bool VFS::FindNextFile(LPVOID lpFindHandle, WIN32_FIND_DATA *pw32fd) 280 | { 281 | FINDDATA *pfd = (FINDDATA *)lpFindHandle; 282 | wstring str; 283 | 284 | while (pfd->ptree) { 285 | str = pfd->ptree->_data.strVirtual; 286 | if (str.find_first_of(L'.') == wstring::npos) str.push_back(L'.'); 287 | if (WildcardMatch(pfd->strFilespec.c_str(), str.c_str())) { 288 | GetMountPointFindData(pfd->ptree, pw32fd); 289 | pfd->ptree = pfd->ptree->_pright; 290 | return true; 291 | } 292 | pfd->ptree = pfd->ptree->_pright; 293 | } 294 | 295 | if (pfd->hFind) { 296 | return ::FindNextFile(pfd->hFind, pw32fd) ? true : false; 297 | } else { 298 | if (!Map(pfd->strVirtual.c_str(), str, &_root)) return false; 299 | if (str.length() != 0) { 300 | pfd->hFind = ::FindFirstFile(str.c_str(), pw32fd); 301 | return (pfd->hFind != INVALID_HANDLE_VALUE); 302 | } else { 303 | return false; 304 | } 305 | } 306 | } 307 | 308 | void VFS::FindClose(LPVOID lpFindHandle) 309 | { 310 | FINDDATA *pfd = (FINDDATA *)lpFindHandle; 311 | 312 | if (pfd->hFind) ::FindClose(pfd->hFind); 313 | delete pfd; 314 | } 315 | 316 | bool VFS::FileExists(const wchar_t *pszVirtual) 317 | // Returns true iff pszVirtual denotes an existing file or folder. 318 | // Supports wildcards. 319 | { 320 | LPVOID hFind; 321 | WIN32_FIND_DATA w32fd; 322 | 323 | hFind = FindFirstFile(pszVirtual, &w32fd); 324 | if (hFind) { 325 | FindClose(hFind); 326 | return true; 327 | } else { 328 | return false; 329 | } 330 | } 331 | 332 | bool VFS::IsFolder(const wchar_t *pszVirtual) 333 | // Returns true iff pszVirtual denotes an existing folder. 334 | // Does NOT support wildcards. 335 | { 336 | wstring strLocal; 337 | DWORD dw; 338 | 339 | if (FindMountPoint(pszVirtual, &_root)) return true; 340 | if (!Map(pszVirtual, strLocal, &_root)) return true; 341 | dw = GetFileAttributes(strLocal.c_str()); 342 | return ((dw != INVALID_FILE_ATTRIBUTES) && (dw & FILE_ATTRIBUTE_DIRECTORY)); 343 | } 344 | 345 | void VFS::GetMountPointFindData(tree *ptree, WIN32_FIND_DATA *pw32fd) 346 | // Fills in the WIN32_FIND_DATA structure with data about the mount point. 347 | { 348 | HANDLE hFind; 349 | SYSTEMTIME st = {1980, 1, 2, 1, 0, 0, 0, 0}; 350 | 351 | if ((ptree->_data.strLocal.length() != 0) && ((hFind = ::FindFirstFile(ptree->_data.strLocal.c_str(), pw32fd)) != INVALID_HANDLE_VALUE)) { 352 | ::FindClose(hFind); 353 | } else { 354 | memset(pw32fd, 0, sizeof(WIN32_FIND_DATA)); 355 | pw32fd->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; 356 | SystemTimeToFileTime(&st, &pw32fd->ftLastWriteTime); 357 | } 358 | wcscpy_s(pw32fd->cFileName, sizeof(pw32fd->cFileName)/sizeof(wchar_t), ptree->_data.strVirtual.c_str()); 359 | } 360 | 361 | HANDLE VFS::CreateFile(const wchar_t *pszVirtual, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition) 362 | { 363 | wstring strLocal; 364 | 365 | if (Map(pszVirtual, strLocal, &_root)) { 366 | return ::CreateFile(strLocal.c_str(), dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, FILE_FLAG_SEQUENTIAL_SCAN, 0); 367 | } else { 368 | return INVALID_HANDLE_VALUE; 369 | } 370 | } 371 | 372 | BOOL VFS::DeleteFile(const wchar_t *pszVirtual) 373 | { 374 | wstring strLocal; 375 | 376 | return (Map(pszVirtual, strLocal, &_root) && ::DeleteFile(strLocal.c_str())); 377 | } 378 | 379 | BOOL VFS::MoveFile(const wchar_t *pszOldVirtual, const wchar_t *pszNewVirtual) 380 | { 381 | wstring strOldLocal, strNewLocal; 382 | 383 | return (Map(pszOldVirtual, strOldLocal, &_root) && Map(pszNewVirtual, strNewLocal, &_root) && ::MoveFile(strOldLocal.c_str(), strNewLocal.c_str())); 384 | } 385 | 386 | BOOL VFS::CreateDirectory(const wchar_t *pszVirtual) 387 | { 388 | wstring strLocal; 389 | 390 | return (Map(pszVirtual, strLocal, &_root) && ::CreateDirectory(strLocal.c_str(), NULL)); 391 | } 392 | 393 | BOOL VFS::RemoveDirectory(const wchar_t *pszVirtual) 394 | { 395 | wstring strLocal; 396 | 397 | return (Map(pszVirtual, strLocal, &_root) && ::RemoveDirectory(strLocal.c_str())); 398 | } 399 | -------------------------------------------------------------------------------- /vfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, Matt Whitlock and WhitSoft Development 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 are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * 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 | * * Neither the names of Matt Whitlock and WhitSoft Development nor the 14 | * names of their contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef _INCL_VFS_H 31 | #define _INCL_VFS_H 32 | 33 | #include 34 | #include 35 | #include 36 | #include "tree.h" 37 | 38 | using namespace std; 39 | 40 | class VFS 41 | { 42 | private: 43 | struct MOUNTPOINT { 44 | wstring strVirtual; 45 | wstring strLocal; 46 | }; 47 | struct FINDDATA { 48 | wstring strVirtual; 49 | wstring strFilespec; 50 | HANDLE hFind; 51 | tree *ptree; 52 | }; 53 | 54 | tree _root; 55 | 56 | static DWORD Map(const wchar_t *pszVirtual, wstring &strLocal, tree *ptree); 57 | static tree * FindMountPoint(const wchar_t *pszVirtual, tree *ptree); 58 | static bool WildcardMatch(const wchar_t *pszFilespec, const wchar_t *pszFilename); 59 | static void GetMountPointFindData(tree *ptree, WIN32_FIND_DATA *pw32fd); 60 | 61 | public: 62 | typedef map listing_type; 63 | VFS(); 64 | void Mount(const wchar_t *pszVirtual, const wchar_t *pszLocal); 65 | DWORD GetDirectoryListing(const wchar_t *pszVirtual, DWORD dwIsNLST, listing_type &listing); 66 | bool FileExists(const wchar_t *pszVirtual); 67 | bool IsFolder(const wchar_t *pszVirtual); 68 | LPVOID FindFirstFile(const wchar_t *pszVirtual, WIN32_FIND_DATA *pw32fd); 69 | bool FindNextFile(LPVOID lpFindHandle, WIN32_FIND_DATA *pw32fd); 70 | void FindClose(LPVOID lpFindHandle); 71 | HANDLE CreateFile(const wchar_t *pszVirtual, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition); 72 | BOOL DeleteFile(const wchar_t *pszVirtual); 73 | BOOL MoveFile(const wchar_t *pszOldVirtual, const wchar_t *pszNewVirtual); 74 | BOOL CreateDirectory(const wchar_t *pszVirtual); 75 | BOOL RemoveDirectory(const wchar_t *pszVirtual); 76 | static void CleanVirtualPath(const wchar_t *pszVirtual, wstring &strNewVirtual); 77 | static void ResolveRelative(const wchar_t *pszCurrentVirtual, const wchar_t *pszRelativeVirtual, wstring &strNewVirtual); 78 | }; 79 | 80 | #endif 81 | --------------------------------------------------------------------------------