├── .idea ├── .gitignore ├── artifacts │ └── LiteFTPD_UNIX_jar.xml ├── misc.xml ├── modules.xml ├── uiDesigner.xml └── vcs.xml ├── How-to-LiteFTPD-UNIX.gif ├── LICENSE ├── LiteFTPD-UNIX.iml ├── README-EN.md ├── README.md ├── config.prop ├── release └── LiteFTPD-UNIX.jar └── src ├── META-INF └── MANIFEST.MF └── pers └── adlered └── liteftpd ├── analyze ├── CommandAnalyze.java └── PrivateVariable.java ├── dict ├── Dict.java └── StatusCode.java ├── graphic ├── main │ ├── GraphicMain.java │ └── model │ │ └── MainModels.java └── update │ └── RunningUpdate.java ├── logger ├── Filter.java ├── Logger.java └── enums │ ├── Levels.java │ └── Types.java ├── main └── Main.java ├── mode ├── PASV.java └── PORT.java ├── pool └── handler │ └── HandlerPool.java ├── tool ├── AutoInputStream.java ├── CharsetSelector.java ├── ConsoleTable.java ├── GoodXX.java ├── LocalAddress.java ├── RandomNum.java └── Status.java ├── user ├── User.java ├── UserProps.java ├── info │ ├── OnlineInfo.java │ └── bind │ │ └── UserInfoBind.java ├── status │ ├── Online.java │ └── bind │ │ ├── IPAddressBind.java │ │ ├── IpLimitBind.java │ │ ├── SpeedLimitBind.java │ │ └── UserLimitBind.java └── verify │ └── OnlineRules.java ├── variable ├── OnlineUserController.java └── Variable.java └── wizard ├── config └── Prop.java └── init ├── PauseListen.java ├── Receive.java ├── Send.java └── SocketHandler.java /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/artifacts/LiteFTPD_UNIX_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/release 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /How-to-LiteFTPD-UNIX.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/LiteFTPD-UNIX/d01f345d9573d272f7943219ef82bfc2cca7956c/How-to-LiteFTPD-UNIX.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LiteFTPD-UNIX.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | # LiteFTPD-UNIX 2 | 🚧 Project developed using Intellij IDEA 🚧 3 | Interested friends are welcome to participate in assisting my development, see my signature for the WeChat account. 4 | The main reason for this flaw is that I found that the encoding problem is very serious when I use the FTP server of MacOS, and Windows can hardly use it. 5 | So I want to make a compatible, humanized, high-performance and low-occupancy FTP server in my spare time. 6 | 7 | ## Current development progress: 8 | ✳️ Goal: Strong compatibility, high performance, low occupancy 9 | Current progress: **File upload, download, delete, move, rename function for FTP clients (or FTP-enabled programs) such as Windows, Linux, MacOS, FileZilla, FlashFXP, Thunder, Chrome, Windows Explorer, etc.** 10 | 11 | - [x] Extremely simple multi-user identification && permission control && default directory && allows directory setting 12 | - [x] User directory lock 13 | - [x] Passive mode 14 | - [x] Active mode 15 | - [x] Breakpoint resume function 16 | - [x] Multi-platform client compatibility 17 | - [x] Binary download 18 | - [x] Binary upload 19 | - [x] ASCII download 20 | - [x] ASCII upload 21 | - [x] File move/rename 22 | - [x] Memory recycling mechanism 23 | - [x] User Defined Settings 24 | - [x] Idle port reclamation mechanism 25 | - [x] Small function: Automatically judge time greeting information after successful login ( Good morning / noon / afternoon / evening / night ) 26 | - [x] long connection (noop) 27 | - [x] User connection limit and settings (support global settings) 28 | - [x] IP connection limit and setting (support fuzzy matching) 29 | - [x] Configuration file (delete will be automatically generated) 30 | - [x] Upload / Download speed limit 31 | - [ ] Graphic Control Panel 32 | 33 | ## How to experience? 34 | 35 | ### GIF Demo 36 | 37 | ![How-to-LiteFTPD-UNIX.gif](/How-to-LiteFTPD-UNIX.gif) 38 | 39 | ### Text Description 40 | 41 | The server ** supports most FTP client connections** for most systems, but the server itself is only required to run on Linux/MacOS systems (due to file system and encoding) due to the need to be stable. 42 | 43 | You can clone the project or download it directly to the local (or [click here](https://github.com/AdlerED/LiteFTPD-UNIX/releases) directly download the separate Jar package), go to the release folder, run `java -jar LiteFTPD -UNIX.jar` is fine. LiteFTPD will automatically listen to port 21 by default. 44 | 45 | **If you need to change the language of LiteFTPD to another language**, you can add the `-l [language]` parameter when starting Jar. For example, `java -jar LiteFTPD-UNIX.jar -l zh-cn`, currently supports `zh-cn`, `en-us`. 46 | 47 | If the configuration file `config.prop` does not exist in the same directory, it will automatically generate one when you first run it. 48 | Modify the file in `config.prop`, you can modify the configuration of LiteFTPD, it is very simple and easy to use. 49 | 50 | Controlling user permissions is also very simple. Edit the value of the `user` variable in `config.prop`. Let's take the default example: 51 | ``` 52 | Format: [username];[password];[permission];[directed access to the directory];[default directory after login] 53 | user=anonymous;;r;/;/;admin;123456;r;/;/root; 54 | ``` 55 | In the default value we define the information of two users. We talk separately: 56 | 57 | ``` 58 | User 1 login name: anonymous 59 | User 1 password: empty 60 | User 1 permission: r Read only (r stands for read-read, w stands for write-write, d stands for delete-delete, c stands for create-create, m stands for move-move) 61 | Directory that User 1 allows access to: / (all subdirectories and files under the root directory) 62 | User 1 logs in to the directory by default: / 63 | 64 | User 2 login name: admin 65 | User 2 password: 123456 66 | User 2 permission: r Read only 67 | Directory that User 2 is allowed to access: / 68 | User 2 logs in to the directory by default: /root 69 | ``` 70 | 71 | For the same reason, you can also add more users at will. 72 |    73 | How to use LiteFTPD? Simply put, this will start: 74 | 75 | ``` 76 | git clone https://github.com/adlered/liteftpd-unix 77 | mv liteftpd-unix/release/LiteFTPD-UNIX.jar ./ 78 | rm -rf liteftpd-unix 79 | java -jar LiteFTPD-UNIX.jar 80 | ``` 81 | 82 | After the first startup, LiteFTPD will automatically generate the `config.prop` configuration file in the same name directory. You can modify the configuration of LiteFTPD in the configuration file and restart LiteFTPD to take effect. 83 | No other files are required, you can run the FTP service only through LiteFTPD-UNIX.jar. You only need to run LiteFTPD-UNIX.jar directly on the computer with Java installed, which is very convenient. 84 | 85 | ![Screenshot 2019-09-30 8.45.56.png](https://pic.stackoverflow.wiki/uploadImages/79a47e02-0623-427f-ae43-e08ab4be11f9.png) 86 | 87 | ## Multithreading does not occupy CPU, memory 88 | 89 | ![屏幕快照 2019-10-11 上午11.38.35.png](https://pic.stackoverflow.wiki/uploadImages/6dd550d3-825e-4fdd-828e-bd53b1104dbb.png) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiteFTPD-UNIX 2 | 🚧 项目使用Intellij IDEA开发 🚧 3 | 欢迎感兴趣的朋友参与协助我的开发,微信号详见我的签名。 4 | 造这个轱辘的原因主要是因为我在使用MacOS的FTP服务端时发现编码问题很严重,Windows几乎无法正常使用。 5 | 于是想在业余时间做出一个兼容性强、人性化、高性能且低占用的FTP服务端。 6 | 7 | ### [→ English Version README ←](https://github.com/AdlerED/LiteFTPD-UNIX/blob/master/README-EN.md) 8 | 9 | ## 当前开发进度: 10 | ✳️ 目标:强兼容,高性能,低占用 11 | 当前进度: **已支持Windows、Linux、MacOS系统FileZilla、FlashFXP、迅雷、Chrome、Windows资源管理器等FTP客户端(或支持FTP的程序)的文件上传、下载、删除、移动、重命名功能** 12 | 13 | - [x] 极其简单的多用户身份识别&&权限控制&&默认目录&&允许目录设定 14 | - [x] 用户目录锁定 15 | - [x] 被动模式 16 | - [x] 主动模式 17 | - [x] 断点续传功能 18 | - [x] 多平台客户端兼容 19 | - [x] 二进制下载 20 | - [x] 二进制上传 21 | - [x] ASCII下载 22 | - [x] ASCII上传 23 | - [x] 文件移动/重命名 24 | - [x] 内存回收机制 25 | - [x] 用户自定义设置 26 | - [x] 空闲端口回收机制 27 | - [x] 小功能:登录成功后自动判断时间问候信息 ( Good morning / noon / afternoon / evening / night ) 28 | - [x] 长连接(noop) 29 | - [x] 用户连接数限制及设置(支持全局设置) 30 | - [x] IP连接数限制及设置(支持模糊匹配) 31 | - [x] 配置文件(删除会自动生成) 32 | - [x] 上传 / 下载速度限制 33 | - [ ] 图形控制面板 34 | 35 | ## 如何体验? 36 | 37 | ### GIF演示 38 | 39 | 如果你面临长城防火墙的阻挡,建议你将下面的GIF(位于项目根目录)直接下载下来预览,否则可能变成静态图片。 40 | 41 | ![How-to-LiteFTPD-UNIX.gif](/How-to-LiteFTPD-UNIX.gif) 42 | 43 | ### 国际化 44 | 45 | 虽然用处可能不是很大,但LiteFTPD支持国际化语言——支持一部分语言。以中文为例: 46 | 47 | ![屏幕快照 2019-10-06 下午9.04.14.png](https://pic.stackoverflow.wiki/uploadImages/7f53ebe8-5b31-4baa-9a68-77de37bc69b0.png) 48 | 49 | ### 文本说明 50 | 51 | 该服务端**支持绝大部分系统的绝大部分FTP客户端连接**,但服务端本身由于需要保证稳定,仅支持在Linux/MacOS系统上运行(由于文件系统和编码)。 52 | 53 | 你可以将项目clone或直接下载到本地(或[在这里](https://github.com/AdlerED/LiteFTPD-UNIX/releases)直接下载单独Jar包),进入release文件夹,运行`java -jar LiteFTPD-UNIX.jar`即可。LiteFTPD默认会自动监听21端口。 54 | **如果需要将LiteFTPD的语言修改为其它语言**,你可以在启动Jar时增加`-l [语言]`参数。例如`java -jar LiteFTPD-UNIX.jar -l zh-cn`,目前支持`zh-cn`、`en-us`。 55 | 如果在同目录下不存在配置文件`config.prop`,在你第一次运行时它会自动生成一个。 56 | 修改`config.prop`中的文件,就可以修改LiteFTPD的配置了,十分简单易用。 57 | 58 | 控制用户权限也十分简单,编辑`config.prop`中`user`变量的值,我们以默认情况举例: 59 | ``` 60 | 格式:[用户名];[密码];[权限];[允许访问的目录];[登录后默认的目录] 61 | user=anonymous;;r;/;/;admin;123456;r;/;/root; 62 | ``` 63 | 默认值中我们定义了两个用户的信息。我们分开讲: 64 | 65 | ``` 66 | 用户1登录名:anonymous 67 | 用户1密码:空 68 | 用户1权限:r 只读(r代表read-读,w代表write-写,d代表delete-删除, c代表create-创建,m代表move-移动) 69 | 用户1允许访问的目录:/(根目录下的所有子目录和文件) 70 | 用户1登录后默认定位到目录:/ 71 | 72 | 用户2登录名:admin 73 | 用户2密码:123456 74 | 用户2权限:r 只读 75 | 用户2允许访问的目录:/ 76 | 用户2登录后默认定位到目录:/root 77 | ``` 78 | 79 | 同理,你也可以随意添加更多用户。 80 | 81 | 如何使用LiteFTPD?简单来讲,这么启动就行了: 82 | 83 | ``` 84 | git clone https://github.com/adlered/liteftpd-unix 85 | mv liteftpd-unix/release/LiteFTPD-UNIX.jar ./ 86 | rm -rf liteftpd-unix 87 | java -jar LiteFTPD-UNIX.jar 88 | ``` 89 | 90 | 第一次启动后,LiteFTPD会自动在同名目录下生成`config.prop`配置文件,你可以在配置文件中修改LiteFTPD的配置,重启LiteFTPD即可生效。 91 | 不需要其它文件,你可以只通过LiteFTPD-UNIX.jar运行FTP服务。你只需要在安装了Java的电脑中直接运行LiteFTPD-UNIX.jar就可以了,十分便捷。 92 | 93 | ![屏幕快照 2019-09-30 下午8.44.56.png](https://pic.stackoverflow.wiki/uploadImages/79a47e02-0623-427f-ae43-e08ab4be11f9.png) 94 | 95 | ## 多线程不占CPU、内存 96 | 97 | ![屏幕快照 2019-10-11 上午11.38.35.png](https://pic.stackoverflow.wiki/uploadImages/6dd550d3-825e-4fdd-828e-bd53b1104dbb.png) 98 | 99 | ## 中文不乱码 100 | 101 | MacOS Finder: 102 | 103 | ![屏幕快照 2019-09-30 下午8.32.54.png](https://pic.stackoverflow.wiki/uploadImages/0b1f4d52-16f0-447f-9e00-ac01d2d0309c.png) 104 | 105 | Windows 资源管理器: 106 | 107 | ![屏幕快照 2019-09-30 下午8.33.14.png](https://pic.stackoverflow.wiki/uploadImages/cb097630-5819-4d97-9c2a-18cbc77e9cb3.png) 108 | 109 | MacOS FileZilla: 110 | 111 | ![屏幕快照 2019-09-30 下午8.29.02.png](https://pic.stackoverflow.wiki/uploadImages/1afb6892-7bbd-43e5-aa41-3adf6ac33db6.png) 112 | 113 | Windows FlashFXP: 114 | 115 | ![屏幕快照 2019-09-30 下午8.30.00.png](https://pic.stackoverflow.wiki/uploadImages/8903355f-a31b-41cf-83df-5a7e534d8855.png) 116 | 117 | Windows自带客户端(由于客户端特殊性,需要先输入`quote gb`适应中文): 118 | 119 | ![屏幕快照 2019-09-30 下午8.26.40.png](https://pic.stackoverflow.wiki/uploadImages/4a9b1c25-90df-44c1-ba14-c463b98089f5.png) 120 | 121 | Windows Chrome: 122 | 123 | ![屏幕快照 2019-09-30 下午8.27.58.png](https://pic.stackoverflow.wiki/uploadImages/c8962604-35b3-49c8-b840-36c7a54ede08.png) 124 | 125 | Unix自带客户端: 126 | 127 | ![屏幕快照 2019-09-30 下午8.28.32.png](https://pic.stackoverflow.wiki/uploadImages/067ea9f1-3bc1-4841-b45a-9375b0b53128.png) 128 | 129 | MacOS Chrome: 130 | 131 | ![屏幕快照 2019-09-30 下午8.27.41.png](https://pic.stackoverflow.wiki/uploadImages/f7257a4a-b892-4401-82bf-c92b92abe00d.png) -------------------------------------------------------------------------------- /config.prop: -------------------------------------------------------------------------------- 1 | # ================================================================================================ 2 | # ================================================================================================ 3 | # >>> LiteFTPD-UNIX Configure File 4 | # 5 | # >> debugLevel 6 | # 7 | # Too high level can affect performance! 8 | # 0: NONE; 9 | # 1: INFO; 10 | # 2: WARN && INFO; 11 | # 3: ERROR && WARN && INFO; 12 | # 4: DEBUG && ERROR && WARN && INFO. 13 | # 14 | # Debug等级,调整过高可能会影响性能! 15 | # 0:无输出; 16 | # 1:输出 INFO 信息; 17 | # 2:输出 WARN 及 INFO 信息; 18 | # 3:输出 ERROR 、 WARN 及 INFO 信息; 19 | # 4:输出 DEBUG 、 ERROR 、 WARN 及 INFO 信息。 20 | # 21 | # >> maxUserLimit 22 | # 23 | # Set to 0, will be ignore the limit. Too small value may make multi-thread ftp client not working. 24 | # 25 | # 同时连接数限制。设置至0代表不限制。过小的值可能会导致多线程的FTP客户端无法正常工作。 26 | # 27 | # >> timeout 28 | # 29 | # Timeout in second. 30 | # 31 | # 连接空闲超时时间。 32 | # 33 | # >> maxTimeout 34 | # 35 | # On mode timeout when client is on passive or initiative mode. (default: 21600 sec = 6 hrs) 36 | # 37 | # 传输时最高的超时时间。(默认:21600秒 = 6小时) 38 | # 39 | # >> smartEncode 40 | # 41 | # Smart choose transmission encode. 42 | # 43 | # 开启后,LiteFTPD会自动检测编码,以兼容各种系统的FTP客户端。 44 | # 45 | # >> defaultEncode 46 | # 47 | # Set the default translating encode. Unix is UTF-8, Windows is GB2312. 48 | # 49 | # 设置默认的传输编码。 Unix系统为UTF-8,Windows为GB2312。 50 | # 51 | # >> port 52 | # 53 | # FTP Server listening tcp port. 54 | # 55 | # FTP服务监听的TCP端口号。 56 | # 57 | # >> welcomeMessage 58 | # 59 | # Customize welcome message when user visited. 60 | # 61 | # 自定义用户连接时的欢迎信息。 62 | # 63 | # >> minPort && maxPort 64 | # 65 | # Appoint passive mode port range. 66 | # Recommend 100+ ports in the range to make sure generation have high-performance! 67 | # 68 | # 自定义被动模式使用的端口范围。 69 | # 建议在范围中有100个端口以上,以确保FTP服务端的性能。 70 | # 71 | # >> user 72 | # 73 | # Multi users. Format: 74 | # user=[username];[password];[permission];[permitDir];[defaultDir]; ... 75 | # username: User's login name. 76 | # password: User's password. 77 | # permission: 78 | # r = read 79 | # w = write 80 | # d = delete 81 | # c = create 82 | # m = move 83 | # Example: rw means read and write permission. 84 | # permitDir: Set dir that user can access. 85 | # Example: "/" means user can access the hole disk; "/home" means user can access folder/subFolder/files under "/home" directory. 86 | # defaultDir: The default dir will be re-directed when user logged. 87 | # 88 | # 多用户管理。格式: 89 | # user=[用户名];[密码];[权限];[允许访问的目录];[登录时的默认目录]; ... 90 | # 权限: 91 | # r = 读 92 | # w = 写 93 | # d = 删除 94 | # c = 创建 95 | # m = 移动 96 | # 举例:rw 代表读和写的权限。 97 | # 允许访问的目录:设置用户可以访问的目录。 98 | # 举例:“/”代表用户可以访问整个硬盘;“/home”代表用户可以访问在“/home”目录下的所有子目录、目录和文件。 99 | # 登录时的默认目录:登录成功后,用户所在的默认目录。 100 | # 101 | # >> ipOnlineLimit 102 | # 103 | # Max connections limit for specify IP Address. 104 | # ipOnlineLimit=[IP];[Limit];[IP];[Limit]; ... 105 | # If you defined IP Address as "0.0.0.0", priority will be given to limiting the number of connections per IP address to a specified number (Except for IP Address that have been set up separately). 106 | # "x" means "All". If you defined "192.168.x.x", that connections from range "192.168.0-255.0-255" all will be refused. 107 | # BlackList for Ip Address? Set limit to "0"! 108 | # !!! Please note! The higher the configuration, the lower the weight of the connection limit (meaning that the more forward, the less likely it is to match). It is recommended to write the configuration of the specified IP at the end, and write the IP configuration using the wildcard in the front. !!! 109 | # For example, =127.0.0.1; 1; 0.0.0.0; 100; When 127.0.0.1 is connected to the server, the maximum number of simultaneous connections allowed is 100! The configuration should be modified to =0.0.0.0;100;127.0.0.1;1; 110 | # 111 | # 限制指定IP地址的最大同时连接数。 112 | # ipOnlineLimit=[IP地址];[最大连接数];[IP地址];[最大连接数]; ... 113 | # 如果你将IP地址定义为“0.0.0.0”,服务端将把最大连接数规则应用到所有的IP地址中(除非指定IP地址也被单独定义了)。 114 | # “x”代表“所有”。如果你定义为“192.168.x.x”,那么来自“192.168.0-255.0-255”范围的所有IP地址都将受到该规则的限制。 115 | # 想将指定IP地址拉黑?把最大连接数限制为“0”! 116 | # !!! 请注意!配置越往前,连接数限制的权重越低(意味着越往前,匹配到的可能性越小)。建议将指定IP的配置写在最后面,将使用通配符的IP配置写在前面。 !!! 117 | # 例如 =127.0.0.1;1;0.0.0.0;100; 当127.0.0.1连接服务端时,最终获取到允许同时的连接数最大为100!应将配置修改为 =0.0.0.0;100;127.0.0.1;1; 118 | # 119 | # >> userOnlineLimit 120 | # 121 | # Max connections limit for specify User. 122 | # userOnlineLimit=[username];[Limit];[username];[Limit]; ... 123 | # If you defined User as "%", priority will be given to limiting the number of connections per User to a specified number (Except for users that have been set up separately). 124 | # BlackList for User? Set limit to "0"! 125 | # !!! Please note! The higher the configuration, the lower the weight of the connection limit (meaning that the more forward, the less likely it is to match). It is recommended to write the configuration of the specified user to the end, and write the user configuration using the wildcard to the front. !!! 126 | # For example, =admin;1;%;100; When logging in to the user admin, the maximum number of connections allowed to log in at the same time is 100! The configuration should be modified to =%;100;admin;1; 127 | # 128 | # 限制指定用户的最大同时连接数。 129 | # userOnlineLimit=[用户名];[最大连接数];[用户名];[最大连接数]; ... 130 | # 如果你将用户名定义为“%”,服务端讲把最大连接数规则应用到所有的用户中(除非指定用户也被单独定义了)。 131 | # 想将指定用户拉黑?把最大连接数限制为“0”! 132 | # !!! 请注意!配置越往前,连接数限制的权重越低(意味着越往前,匹配到的可能性越小)。建议将指定用户的配置写在最后面,将使用通配符的用户配置写在前面。 !!! 133 | # 例如 =admin;1;%;100; 当登录用户admin时,最终获取到允许同时登录的连接数最大为100!应将配置修改为 =%;100;admin;1; 134 | # 135 | # >> speedLimit 136 | # 137 | # Limit user's upload & download speed. There is no limit 138 | # format: speedLimit=[username];[upload(kb/s)];[download(kb/s)]; ... 139 | # 140 | # 限制用户的上传和下载速度。数值为0或不填时不限制。 141 | # 格式:speedList=[用户名];[上传速度(kb/秒)];[下载速度(kb/秒)]; ... 142 | # 143 | # ================================================================================================ 144 | # = ↓ CONFIG ↓ = 145 | # ================================================================================================ 146 | # 147 | user=anonymous;;r;/;/;admin;123456;r;/;/root; 148 | ipOnlineLimit=0.0.0.0;100;127.x.x.x;100;192.168.1.x;100; 149 | userOnlineLimit=%;100;anonymous;100;admin;100; 150 | speedLimit=anonymous;0;10240;admin;;20480; 151 | debugLevel=3 152 | maxUserLimit=100 153 | timeout=100 154 | maxTimeout=21600 155 | smartEncode=true 156 | defaultEncode=UTF-8 157 | port=21 158 | welcomeMessage=オレは ルフィ、海賊王になる男だ 159 | minPort=10240 160 | maxPort=20480 161 | -------------------------------------------------------------------------------- /release/LiteFTPD-UNIX.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlered/LiteFTPD-UNIX/d01f345d9573d272f7943219ef82bfc2cca7956c/release/LiteFTPD-UNIX.jar -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: pers.adlered.liteftpd.main.Main 3 | 4 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/analyze/CommandAnalyze.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.analyze; 2 | 3 | import pers.adlered.liteftpd.user.status.bind.IPAddressBind; 4 | import pers.adlered.liteftpd.dict.StatusCode; 5 | import pers.adlered.liteftpd.dict.Dict; 6 | import pers.adlered.liteftpd.logger.enums.Levels; 7 | import pers.adlered.liteftpd.logger.Logger; 8 | import pers.adlered.liteftpd.logger.enums.Types; 9 | import pers.adlered.liteftpd.wizard.init.PauseListen; 10 | import pers.adlered.liteftpd.wizard.init.Send; 11 | import pers.adlered.liteftpd.mode.PASV; 12 | import pers.adlered.liteftpd.mode.PORT; 13 | import pers.adlered.liteftpd.tool.GoodXX; 14 | import pers.adlered.liteftpd.tool.RandomNum; 15 | import pers.adlered.liteftpd.user.User; 16 | import pers.adlered.liteftpd.user.UserProps; 17 | import pers.adlered.liteftpd.user.info.OnlineInfo; 18 | import pers.adlered.liteftpd.user.info.bind.UserInfoBind; 19 | import pers.adlered.liteftpd.user.status.bind.IpLimitBind; 20 | import pers.adlered.liteftpd.user.status.bind.UserLimitBind; 21 | import pers.adlered.liteftpd.user.verify.OnlineRules; 22 | import pers.adlered.liteftpd.variable.OnlineUserController; 23 | import pers.adlered.liteftpd.variable.Variable; 24 | 25 | import java.io.BufferedReader; 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.io.InputStreamReader; 29 | import java.net.Socket; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | /** 34 | *

LiteFTPD-UNIX

35 | *

To analyze user input into command, and execute it.

36 | * 37 | * @author : https://github.com/AdlerED 38 | * @date : 2019-09-19 09:21 39 | **/ 40 | public class CommandAnalyze { 41 | /** 42 | * Step 1: Required username; 43 | * Step 2: Required password; 44 | * Step 3: Logged in. 45 | */ 46 | private int step = 1; 47 | 48 | private String loginUser = null; 49 | private String loginPass = null; 50 | private String SRVIPADD = null; 51 | 52 | private Send send = null; 53 | 54 | private File file = null; 55 | private PASV passiveMode = null; 56 | private PORT portMode = null; 57 | private String mode = null; 58 | 59 | private String currentPath = null; 60 | private String lockPath = null; 61 | private String RNFR = null; 62 | //Trans type default A 63 | //A: ASCII I: BINARY 64 | private String type = "A"; 65 | //To change timeout when command input 66 | private PauseListen pauseListen = null; 67 | private IPAddressBind ipAddressBind = null; 68 | 69 | private PrivateVariable privateVariable = null; 70 | private UserProps userProps = null; 71 | 72 | private IpLimitBind ipLimitBind = null; 73 | private UserLimitBind userLimitBind = null; 74 | 75 | public CommandAnalyze(Send send, String SRVIPADD, PrivateVariable privateVariable, PauseListen pauseListen, IPAddressBind ipAddressBind, IpLimitBind ipLimitBind) { 76 | this.send = send; 77 | this.SRVIPADD = SRVIPADD; 78 | this.privateVariable = privateVariable; 79 | this.pauseListen = pauseListen; 80 | this.ipAddressBind = ipAddressBind; 81 | this.ipLimitBind = ipLimitBind; 82 | } 83 | 84 | public static boolean isPortUsing(String host, int port) { 85 | boolean flag = false; 86 | try { 87 | Socket socket = new Socket(host, port); 88 | flag = true; 89 | socket.close(); 90 | } catch (IOException IOE) { 91 | Logger.log(Types.SYS, Levels.DEBUG, "Port " + port + " already in use, re-generating..."); 92 | } 93 | return flag; 94 | } 95 | 96 | public static void delFolder(String folderPath) { 97 | try { 98 | delAllFile(folderPath); //删除完里面所有内容 99 | String filePath = folderPath; 100 | filePath = filePath; 101 | java.io.File myFilePath = new java.io.File(filePath); 102 | myFilePath.delete(); //删除空文件夹 103 | } catch (Exception e) { 104 | e.printStackTrace(); 105 | } 106 | } 107 | 108 | public static boolean delAllFile(String path) { 109 | boolean flag = false; 110 | File file = new File(path); 111 | if (!file.exists()) { 112 | return flag; 113 | } 114 | if (!file.isDirectory()) { 115 | return flag; 116 | } 117 | String[] tempList = file.list(); 118 | File temp = null; 119 | for (int i = 0; i < tempList.length; i++) { 120 | if (path.endsWith(File.separator)) { 121 | temp = new File(path + tempList[i]); 122 | } else { 123 | temp = new File(path + File.separator + tempList[i]); 124 | } 125 | if (temp.isFile()) { 126 | temp.delete(); 127 | } 128 | if (temp.isDirectory()) { 129 | delAllFile(path + "/" + tempList[i]);//先删除文件夹里面的文件 130 | delFolder(path + "/" + tempList[i]);//再删除空文件夹 131 | flag = true; 132 | } 133 | } 134 | return flag; 135 | } 136 | 137 | public void analyze(String command) { 138 | pauseListen.resetTimeout(); 139 | String cmd = null; 140 | String arg1 = null; 141 | String arg2 = null; 142 | String[] split = null; 143 | try { 144 | split = command.split(" "); 145 | for (int i = 0; i < split.length; i++) { 146 | split[i] = split[i].replaceAll("(\r|\n)", ""); 147 | } 148 | cmd = split[0]; 149 | if (split.length == 2) { 150 | arg1 = split[1]; 151 | } else if (split.length > 2) { 152 | arg1 = split[1]; 153 | arg2 = split[2]; 154 | } 155 | } catch (Exception E) { 156 | //TODO 157 | E.printStackTrace(); 158 | } 159 | if (cmd != null) { 160 | cmd = cmd.toUpperCase(); 161 | switch (step) { 162 | case 1: 163 | if (cmd.equals("USER")) { 164 | if (arg1 == null) { 165 | arg1 = "anonymous"; 166 | } 167 | privateVariable.setUsername(arg1); 168 | Logger.log(Types.SYS, Levels.DEBUG, Thread.currentThread() + " User login: " + arg1); 169 | loginUser = arg1; 170 | send.send(Dict.passwordRequired(loginUser)); 171 | step = 2; 172 | } else if (cmd.equals("BYE") || cmd.equals("QUIT")) { 173 | send.send(Dict.bye()); 174 | privateVariable.setInterrupted(true); 175 | } else if (cmd.equals("OPTS")) { 176 | if (arg1 != null) { 177 | arg1 = arg1.toUpperCase(); 178 | if (arg1.equals("UTF8")) { 179 | if (arg2 != null) { 180 | arg2 = arg2.toUpperCase(); 181 | if (arg2.equals("ON")) { 182 | privateVariable.setEncode("UTF-8"); 183 | privateVariable.setEncodeLock(true); 184 | send.send(Dict.utf8(true)); 185 | } else if (arg2.equals("OFF")) { 186 | privateVariable.setEncode(Variable.defaultEncode); 187 | privateVariable.setEncodeLock(true); 188 | send.send(Dict.utf8(false)); 189 | } 190 | } 191 | } 192 | } 193 | } else { 194 | unknownCommand(); 195 | } 196 | break; 197 | case 2: 198 | if (cmd.equals("PASS")) { 199 | Logger.log(Types.SYS, Levels.DEBUG, "User " + loginUser + "'s password: " + arg1); 200 | loginPass = arg1; 201 | userLimitBind = OnlineRules.checkUsername(loginUser); 202 | if (userLimitBind.getUsername() == null) { 203 | send.send(Dict.tooMuchLoginInUser(loginUser)); 204 | privateVariable.reason = "user \"" + loginUser + "\" has too much login"; 205 | privateVariable.setInterrupted(true); 206 | } else { 207 | try { 208 | userProps = User.getUserProps(loginUser); 209 | if ((loginUser.equals("anonymous") && userProps.getPermission() != null) || User.checkPassword(loginUser, loginPass)) { 210 | OnlineInfo.usersOnlineInfo.add(new UserInfoBind(ipLimitBind, userLimitBind)); 211 | send.send(Dict.loggedIn(loginUser)); 212 | Logger.log(Types.SYS, Levels.INFO, "User " + loginUser + " logged in."); 213 | lockPath = userProps.getPermitDir(); 214 | currentPath = userProps.getDefaultDir(); 215 | OnlineUserController.printOnline(); 216 | step = 3; 217 | } else { 218 | send.send(Dict.wrongPassword()); 219 | privateVariable.setInterrupted(true); 220 | } 221 | } catch (NullPointerException NPE) { 222 | send.send(Dict.wrongPassword()); 223 | privateVariable.setInterrupted(true); 224 | } 225 | } 226 | } else if (cmd.equals("BYE") || cmd.equals("QUIT")) { 227 | send.send(Dict.bye()); 228 | privateVariable.setInterrupted(true); 229 | } else { 230 | unknownCommand(); 231 | } 232 | break; 233 | case 3: 234 | if (cmd.equals("USER") || cmd.equals("PASS")) { 235 | send.send(Dict.alreadyLogIn()); 236 | } 237 | /** 238 | * INFO COMMANDS 239 | */ 240 | else if (cmd.equals("FEAT")) { 241 | send.send(Dict.features()); 242 | } else if (cmd.equals("SITE")) { 243 | send.send(Dict.notSupportSITE()); 244 | } 245 | /** 246 | * NORMAL COMMANDS 247 | */ 248 | else if (cmd.equals("PWD")) { 249 | //if (file.isDirectory()) { 250 | send.send(Dict.currentDir(getLockPath(currentPath, lockPath))); 251 | //} else { 252 | // send.send(Dict.isFile + file.getName() + "" + Dict.newLine); 253 | //} 254 | } else if (cmd.equals("TYPE")) { 255 | arg1 = arg1.toUpperCase(); 256 | type = arg1; 257 | send.send(Dict.type(arg1)); 258 | } else if (cmd.equals("BINARY")) { 259 | type = "I"; 260 | send.send(Dict.type("I")); 261 | } else if (cmd.equals("ASCII")) { 262 | type = "A"; 263 | send.send(Dict.type("A")); 264 | } else if (cmd.equals("BYE") || cmd.equals("QUIT") || cmd.equals("EXIT")) { 265 | send.send(Dict.bye()); 266 | privateVariable.setInterrupted(true); 267 | } else if (cmd.equals("LIST")) { 268 | send.send(Dict.list()); 269 | privateVariable.setTimeoutLock(true); 270 | try { 271 | Process process = Runtime.getRuntime().exec(new String[]{"ls", "-l", currentPath}); 272 | process.waitFor(); 273 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); 274 | String line; 275 | StringBuilder result = new StringBuilder(); 276 | while ((line = bufferedReader.readLine()) != null) { 277 | result.append(line).append('\n'); 278 | } 279 | if (mode != null) { 280 | Logger.log(Types.TRANS, Levels.DEBUG, "Reset mode."); 281 | String mode = this.mode; 282 | this.mode = null; 283 | Logger.log(Types.TRANS, Levels.DEBUG, "Hello " + mode + " mode."); 284 | switch (mode) { 285 | case "port": 286 | portMode.hello(result.toString()); 287 | portMode.start(); 288 | break; 289 | case "passive": 290 | passiveMode.hello(result.toString()); 291 | break; 292 | } 293 | } 294 | } catch (IOException IOE) { 295 | //TODO 296 | IOE.printStackTrace(); 297 | } catch (InterruptedException IE) { 298 | //TODO 299 | IE.printStackTrace(); 300 | } 301 | } else if (cmd.equals("CDUP")) { 302 | upperDirectory(); 303 | send.send(Dict.changeDir(getLockPath(currentPath, lockPath))); 304 | } else if (cmd.equals("CWD")) { 305 | if (arg1 != null) { 306 | String completePath = arg1; 307 | if (arg2 != null) { 308 | for (int i = 2; i < split.length; i++) { 309 | //Make "/Users/$" to "/Users$" 310 | if (i == split.length - 1) { 311 | split[i] = split[i].replaceAll("/$", ""); 312 | } 313 | //Add 314 | completePath += " " + split[i]; 315 | } 316 | } 317 | if (completePath.equals("..")) { 318 | upperDirectory(); 319 | send.send(Dict.changeDir(getLockPath(currentPath, lockPath))); 320 | } else { 321 | if (completePath.indexOf("../") != -1) { 322 | completePath = completePath.replaceAll("\\.\\./", ""); 323 | } 324 | completePath = getAbsolutePath(completePath); 325 | File file = new File(completePath); 326 | if (file.exists()) { 327 | if (file.isFile()) { 328 | send.send(Dict.notFound(getLockPath(completePath, lockPath))); 329 | } else { 330 | currentPath = completePath; 331 | send.send(Dict.changeDir(getLockPath(currentPath, lockPath))); 332 | } 333 | } else { 334 | send.send(Dict.notFound(getLockPath(completePath, lockPath))); 335 | } 336 | } 337 | } else { 338 | send.send(Dict.unknownCommand()); 339 | } 340 | } else if (cmd.equals("SYST")) { 341 | send.send(Dict.unixType()); 342 | } else if (cmd.equals("NOOP")) { 343 | privateVariable.setTimeoutLock(true); 344 | send.send(Dict.commandOK()); 345 | } else if (cmd.equals("MKD")) { 346 | if (userProps.getPermission().contains("c")) { 347 | String completePath = arg1; 348 | if (arg2 != null) { 349 | for (int i = 2; i < split.length; i++) { 350 | completePath += " " + split[i]; 351 | } 352 | } 353 | if (completePath.indexOf("../") != -1) { 354 | completePath = completePath.replaceAll("\\.\\./", ""); 355 | } 356 | completePath = getAbsolutePath(completePath); 357 | File file = new File(completePath); 358 | if (!file.exists()) { 359 | file.mkdirs(); 360 | send.send(Dict.dirCreated(getLockPath(completePath, lockPath))); 361 | } else { 362 | send.send(Dict.createFailed(getLockPath(completePath, lockPath))); 363 | } 364 | } else { 365 | send.send(Dict.permissionDenied()); 366 | } 367 | } else if (cmd.equals("RMD")) { 368 | if (userProps.getPermission().contains("d")) { 369 | String completePath = arg1; 370 | if (arg2 != null) { 371 | for (int i = 2; i < split.length; i++) { 372 | completePath += " " + split[i]; 373 | } 374 | } 375 | if (completePath.indexOf("../") != -1) { 376 | completePath = completePath.replaceAll("\\.\\./", ""); 377 | } 378 | completePath = getAbsolutePath(completePath); 379 | File file = new File(completePath); 380 | if (file.isDirectory()) { 381 | delFolder(completePath); 382 | send.send(Dict.rmdSuccess()); 383 | } else if (file.isFile()) { 384 | file.delete(); 385 | send.send(Dict.rmdSuccess()); 386 | } else { 387 | send.send(Dict.notFound(getLockPath(completePath, lockPath))); 388 | } 389 | } else { 390 | send.send(Dict.permissionDenied()); 391 | } 392 | } else if (cmd.equals("DELE")) { 393 | if (userProps.getPermission().contains("d")) { 394 | String completePath = arg1; 395 | if (arg2 != null) { 396 | for (int i = 2; i < split.length; i++) { 397 | completePath += " " + split[i]; 398 | } 399 | } 400 | if (completePath.indexOf("../") != -1) { 401 | completePath = completePath.replaceAll("\\.\\./", ""); 402 | } 403 | completePath = getAbsolutePath(completePath); 404 | File file = new File(completePath); 405 | if (file.isFile()) { 406 | file.delete(); 407 | send.send(Dict.deleSuccess()); 408 | } else { 409 | send.send(Dict.notFound(getLockPath(completePath, lockPath))); 410 | } 411 | } else { 412 | send.send(Dict.permissionDenied()); 413 | } 414 | } else if (cmd.equals("RNFR")) { 415 | if (userProps.getPermission().contains("m")) { 416 | String completePath = arg1; 417 | if (arg2 != null) { 418 | for (int i = 2; i < split.length; i++) { 419 | completePath += " " + split[i]; 420 | } 421 | } 422 | if (completePath.indexOf("../") != -1) { 423 | completePath = completePath.replaceAll("\\.\\./", ""); 424 | } 425 | completePath = getAbsolutePath(completePath); 426 | File file = new File(completePath); 427 | if (file.exists()) { 428 | RNFR = completePath; 429 | send.send(Dict.rnfrSuccess()); 430 | } else { 431 | send.send(Dict.notFound(getLockPath(completePath, lockPath))); 432 | } 433 | } else { 434 | send.send(Dict.permissionDenied()); 435 | } 436 | } else if (cmd.equals("RNTO")) { 437 | if (userProps.getPermission().contains("m")) { 438 | String completePath = arg1; 439 | if (arg2 != null) { 440 | for (int i = 2; i < split.length; i++) { 441 | completePath += " " + split[i]; 442 | } 443 | } 444 | if (completePath.indexOf("../") != -1) { 445 | completePath = completePath.replaceAll("\\.\\./", ""); 446 | } 447 | completePath = getAbsolutePath(completePath); 448 | File file = new File(RNFR); 449 | if (file.renameTo(new File(completePath))) { 450 | send.send(Dict.rntoSuccess()); 451 | } else { 452 | send.send(Dict.notFound(getLockPath(completePath, lockPath))); 453 | } 454 | RNFR = null; 455 | } else { 456 | send.send(Dict.permissionDenied()); 457 | } 458 | } 459 | /** 460 | * TRANSMISSION COMMANDS 461 | */ 462 | else if (cmd.equals("PORT")) { 463 | String completePath = arg1; 464 | if (arg2 != null) { 465 | for (int i = 2; i < split.length; i++) { 466 | completePath += " " + split[i]; 467 | } 468 | } 469 | String[] analyzeStep1 = completePath.split(","); 470 | int[] analyzeStep2 = new int[analyzeStep1.length]; 471 | for (int i = 0; i < analyzeStep1.length; i++) { 472 | analyzeStep2[i] = Integer.parseInt(analyzeStep1[i]); 473 | } 474 | String ip = ""; 475 | int port = -1; 476 | try { 477 | for (int i = 0; i < 4; i++) { 478 | if (i < 3) { 479 | ip += analyzeStep2[i] + "."; 480 | } else { 481 | ip += analyzeStep2[i]; 482 | } 483 | } 484 | port = (analyzeStep2[4] * 256) + analyzeStep2[5]; 485 | } catch (ArrayIndexOutOfBoundsException AIOOBE) { 486 | AIOOBE.printStackTrace(); 487 | } 488 | if (portMode != null) { 489 | portMode.stopSocket(); 490 | } 491 | portMode = new PORT(send, privateVariable, pauseListen, type, OnlineRules.getSpeedLimit(loginUser)); 492 | portMode.setTarget(ip, port); 493 | send.send(Dict.portSuccess()); 494 | mode = "port"; 495 | } else if (cmd.equals("PASV")) { 496 | if (passiveMode != null) { 497 | passiveMode.stopSocket(); 498 | } 499 | passiveMode = new PASV(send, privateVariable, pauseListen, type, OnlineRules.getSpeedLimit(loginUser)); 500 | int randomPort; 501 | int randomSub; 502 | int calcPort; 503 | int finalPort; 504 | do { 505 | Map map = generatePort(); 506 | randomPort = map.get("randomPort"); 507 | randomSub = map.get("randomSub"); 508 | calcPort = map.get("calcPort"); 509 | finalPort = map.get("finalPort"); 510 | } while (!passiveMode.listen(finalPort)); 511 | String[] IPADD = (SRVIPADD.split(":")[0]).split("\\."); 512 | send.send(Dict.pasvMode(IPADD, calcPort, randomSub)); 513 | passiveMode.start(); 514 | mode = "passive"; 515 | } else if (cmd.equals("RETR")) { 516 | if (userProps.getPermission().contains("r")) { 517 | String completePath = arg1; 518 | if (arg2 != null) { 519 | for (int i = 2; i < split.length; i++) { 520 | completePath += " " + split[i]; 521 | } 522 | } 523 | if (completePath.indexOf("../") != -1) { 524 | completePath = completePath.replaceAll("\\.\\./", ""); 525 | } 526 | completePath = getAbsolutePath(completePath); 527 | try { 528 | File file = new File(completePath); 529 | if (file.exists()) { 530 | if (type.equals("I")) { 531 | send.send(Dict.openPasvBin(getLockPath(completePath, lockPath), file.length())); 532 | } else { 533 | send.send(Dict.openPasvAscii(getLockPath(completePath, lockPath), file.length())); 534 | } 535 | if (mode != null) { 536 | Logger.log(Types.TRANS, Levels.DEBUG, "Reset mode."); 537 | String mode = this.mode; 538 | this.mode = null; 539 | Logger.log(Types.TRANS, Levels.DEBUG, "Hello " + mode + " mode."); 540 | switch (mode) { 541 | case "port": 542 | portMode.hello(file); 543 | portMode.start(); 544 | break; 545 | case "passive": 546 | passiveMode.hello(file); 547 | break; 548 | } 549 | } 550 | } else { 551 | send.send(Dict.notFound(getLockPath(completePath, lockPath))); 552 | } 553 | } catch (NullPointerException NPE) { 554 | send.send(Dict.pasvDataFailed()); 555 | } 556 | } else { 557 | send.send(Dict.permissionDenied()); 558 | } 559 | } else if (cmd.equals("STOR")) { 560 | if (userProps.getPermission().contains("w")) { 561 | String completePath = arg1; 562 | if (arg2 != null) { 563 | for (int i = 2; i < split.length; i++) { 564 | completePath += " " + split[i]; 565 | } 566 | } 567 | if (completePath.indexOf("../") != -1) { 568 | completePath = completePath.replaceAll("\\.\\./", ""); 569 | } 570 | completePath = getAbsolutePath(completePath); 571 | if (type.equals("I")) { 572 | send.send(Dict.openBin(getLockPath(completePath, lockPath))); 573 | } else { 574 | send.send(Dict.openAscii(getLockPath(completePath, lockPath))); 575 | } 576 | try { 577 | if (mode != null) { 578 | Logger.log(Types.TRANS, Levels.DEBUG, "Reset mode."); 579 | String mode = this.mode; 580 | this.mode = null; 581 | Logger.log(Types.TRANS, Levels.DEBUG, "Hello " + mode + " mode."); 582 | switch (mode) { 583 | case "port": 584 | portMode.helloSTOR(completePath); 585 | portMode.start(); 586 | break; 587 | case "passive": 588 | passiveMode.helloSTOR(completePath); 589 | break; 590 | } 591 | } 592 | } catch (NullPointerException NPE) { 593 | send.send(Dict.pasvDataFailed()); 594 | } 595 | } else { 596 | send.send(Dict.permissionDenied()); 597 | } 598 | } else if (cmd.equals("SIZE")) { 599 | if (userProps.getPermission().contains("r")) { 600 | String completePath = arg1; 601 | if (arg2 != null) { 602 | for (int i = 2; i < split.length; i++) { 603 | completePath += " " + split[i]; 604 | } 605 | } 606 | completePath = getAbsolutePath(completePath); 607 | File file = new File(completePath); 608 | if (file.exists() && file.isFile()) { 609 | send.send(Dict.fileSize(file.length())); 610 | } else { 611 | send.send(Dict.noSuchFile(completePath)); 612 | } 613 | } else { 614 | send.send(Dict.permissionDenied()); 615 | } 616 | } else if (cmd.equals("OPTS")) { 617 | if (arg1 != null) { 618 | arg1 = arg1.toUpperCase(); 619 | if (arg1.equals("UTF8")) { 620 | if (arg2 != null) { 621 | arg2 = arg2.toUpperCase(); 622 | if (arg2.equals("ON")) { 623 | privateVariable.setEncode("UTF-8"); 624 | privateVariable.setEncodeLock(true); 625 | send.send(Dict.utf8(true)); 626 | } else if (arg2.equals("OFF")) { 627 | privateVariable.setEncode(Variable.defaultEncode); 628 | privateVariable.setEncodeLock(true); 629 | send.send(Dict.utf8(false)); 630 | } 631 | } 632 | } 633 | } 634 | } else if (cmd.equals("REST")) { 635 | if (arg1 != null) { 636 | send.send(Dict.rest(arg1)); 637 | try { 638 | privateVariable.setRest(Long.parseLong(arg1)); 639 | } catch (Exception E) { 640 | E.printStackTrace(); 641 | } 642 | } 643 | } else if (cmd.equals("ABOR")) { 644 | send.send(Dict.bye()); 645 | privateVariable.setInterrupted(true); 646 | } else if (cmd.equals("GB")) { 647 | privateVariable.setEncode("GB2312"); 648 | privateVariable.setEncodeLock(true); 649 | send.send(Dict.gbEncodeOK(ipAddressBind.getIPADD())); 650 | } else { 651 | unknownCommand(); 652 | } 653 | break; 654 | } 655 | } 656 | } 657 | 658 | public boolean unknownCommand() { 659 | send.send(Dict.unknownCommand()); 660 | return true; 661 | } 662 | 663 | /* 664 | 根据绝对目录和锁定目录,计算相对目录 665 | */ 666 | public String getLockPath(String absolutePath, String lockPath) { 667 | String resolve = absolutePath.replaceAll("^(" + lockPath + ")", ""); 668 | if (resolve.isEmpty()) resolve = "/"; 669 | if (!resolve.startsWith("/")) resolve = "/" + resolve; 670 | return resolve; 671 | } 672 | 673 | public void upperDirectory() { 674 | //Depart && Re-part path 675 | if (!getLockPath(currentPath, lockPath).equals("/")) { 676 | String[] dir = currentPath.split("/"); 677 | currentPath = ""; 678 | for (int i = 0; i < dir.length - 1; i++) { 679 | Logger.log(Types.SYS, Levels.DEBUG, "len: " + dir.length + " cur: " + i); 680 | if (i == dir.length - 2) { 681 | currentPath += dir[i]; 682 | } else { 683 | currentPath += dir[i] + "/"; 684 | } 685 | } 686 | if (currentPath.isEmpty()) currentPath = "/"; 687 | } 688 | Logger.log(Types.SYS, Levels.DEBUG, currentPath); 689 | } 690 | 691 | /* 692 | 根据CWD定位的目录,计算绝对目录的位置。 693 | */ 694 | @SuppressWarnings("deprecation") 695 | public String getAbsolutePath(String path) { 696 | //path = URLDecoder.decode(path); 697 | if (path.matches("^(./).*")) { 698 | path = path.replaceAll("^(./)", ""); 699 | //Not totally equals "./" 700 | if (path.isEmpty()) { 701 | path = currentPath; 702 | } else { 703 | path = currentPath + "/" + path; 704 | } 705 | } else if (path.matches("^(/).*")) { 706 | path = path.replaceAll("^(/)", ""); 707 | if (path.isEmpty()) { 708 | path = lockPath; 709 | } else { 710 | path = lockPath + "/" + path; 711 | } 712 | } else { 713 | path = currentPath + "/" + path; 714 | } 715 | path = path.replaceAll("//", "/"); 716 | Logger.log(Types.SYS, Levels.DEBUG, "Absolute path: " + path); 717 | return path; 718 | } 719 | 720 | public Map generatePort() { 721 | Map map = new HashMap(); 722 | int randomPort; 723 | int randomSub; 724 | int calcPort; 725 | int finalPort; 726 | int count = 0; 727 | do { 728 | randomPort = RandomNum.sumIntger(Variable.minPort, Variable.maxPort, false); 729 | randomSub = RandomNum.sumIntger(0, 64, false); 730 | calcPort = (randomPort - randomSub) / 256; 731 | finalPort = calcPort * 256 + randomSub; 732 | ++count; 733 | } while (finalPort < Variable.minPort || finalPort > Variable.maxPort || randomSub < 0 || randomSub > 64); 734 | Logger.log(Types.SYS, Levels.DEBUG, count + " times while generating port: " + finalPort); 735 | map.put("randomPort", randomPort); 736 | map.put("randomSub", randomSub); 737 | map.put("calcPort", calcPort); 738 | map.put("finalPort", finalPort); 739 | return map; 740 | } 741 | } 742 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/analyze/PrivateVariable.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.analyze; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

Store values when service is running to make sure every model works together.

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-09-19 09:21 9 | **/ 10 | public class PrivateVariable { 11 | public boolean interrupted = false; 12 | public String encode = "UTF-8"; 13 | public String reason = null; 14 | private String username = null; 15 | //When translating, turn the timeout on to avoid timeout & disconnect. 16 | private boolean timeoutLock = false; 17 | //If Encode Lock is on, smart encode will not working. 18 | private boolean encodeLock = false; 19 | //Rest 20 | private long rest = 0l; 21 | 22 | public String getUsername() { 23 | return username; 24 | } 25 | 26 | public void setUsername(String username) { 27 | this.username = username; 28 | } 29 | 30 | public boolean isEncodeLock() { 31 | return encodeLock; 32 | } 33 | 34 | public void setEncodeLock(boolean encodeLock) { 35 | this.encodeLock = encodeLock; 36 | } 37 | 38 | public boolean isTimeoutLock() { 39 | return timeoutLock; 40 | } 41 | 42 | public void setTimeoutLock(boolean timeoutLock) { 43 | this.timeoutLock = timeoutLock; 44 | } 45 | 46 | public boolean isInterrupted() { 47 | return interrupted; 48 | } 49 | 50 | public void setInterrupted(boolean interrupted) { 51 | this.interrupted = interrupted; 52 | } 53 | 54 | public String getEncode() { 55 | return encode; 56 | } 57 | 58 | public void setEncode(String encode) { 59 | this.encode = encode; 60 | } 61 | 62 | public long getRest() { 63 | return rest; 64 | } 65 | 66 | public void setRest(long rest) { 67 | this.rest = rest; 68 | } 69 | 70 | public void resetRest() { 71 | rest = 0l; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/dict/Dict.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.dict; 2 | 3 | import pers.adlered.liteftpd.tool.GoodXX; 4 | import pers.adlered.liteftpd.variable.Variable; 5 | 6 | /** 7 | *

LiteFTPD-UNIX

8 | *

Store status messages with response format.

9 | * 10 | * @author : https://github.com/AdlerED 11 | * @date : 2019-09-19 09:21 12 | **/ 13 | public class Dict { 14 | private static String lang = "en_us"; 15 | 16 | public static void init(String lang) { 17 | lang = lang.toLowerCase(); 18 | Dict.lang = lang; 19 | } 20 | 21 | public static String gbEncodeOK(String ip) { 22 | if (lang.equals("zh_cn")) 23 | return StatusCode.SERVICEREADY + "-LiteFTPD" + Dict.newLine 24 | + ">>> 编码已适应Windows FTP客户端,您现在看到的这条信息应是正常的简体中文。" 25 | + Dict.newLine + ">>> 你的IP地址: " + ip 26 | + "" + Dict.newLine + "220" + " :) " + Variable.welcomeMessage + "" + Dict.newLine; 27 | if (lang.equals("en_us")) 28 | return StatusCode.SERVICEREADY + "-LiteFTPD" + Dict.newLine 29 | + ">>> 编码已适应Windows FTP客户端,您现在看到的这条信息应是正常的简体中文。" 30 | + Dict.newLine + ">>> Your IP address: " + ip 31 | + "" + Dict.newLine + "220" + " :) " + Variable.welcomeMessage + "" + Dict.newLine; 32 | return null; 33 | } 34 | 35 | public static String rest(String restAt) { 36 | if (lang.equals("zh_cn")) 37 | return StatusCode.WAIT + " 断点续传已设定于 " + restAt + ". 发送 STORE 或 RETRIEVE 以继续." + Dict.newLine; 38 | if (lang.equals("en_us")) 39 | return StatusCode.WAIT + " Restarting at " + restAt + ". Send STORE or RETRIEVE." + Dict.newLine; 40 | return null; 41 | } 42 | 43 | public static String noSuchFile(String path) { 44 | if (lang.equals("zh_cn")) 45 | return StatusCode.ERRTARGET + " " + path + ": 没有这个文件." + Dict.newLine; 46 | if (lang.equals("en_us")) 47 | return StatusCode.ERRTARGET + " " + path + ": No such file." + Dict.newLine; 48 | return null; 49 | } 50 | 51 | public static String fileSize(long size) { 52 | if (lang.equals("zh_cn")) 53 | return StatusCode.FILESTATUS + " " + size + Dict.newLine; 54 | if (lang.equals("en_us")) 55 | return StatusCode.FILESTATUS + " " + size + Dict.newLine; 56 | return null; 57 | } 58 | 59 | public static String openAscii(String path) { 60 | if (lang.equals("zh_cn")) 61 | return StatusCode.STATUSOK + " 为 " + path + " 开启 ASCII 模式传输数据." + Dict.newLine; 62 | if (lang.equals("en_us")) 63 | return StatusCode.STATUSOK + " Opening ASCII mode data connection for " + path + "." + Dict.newLine; 64 | return null; 65 | } 66 | 67 | public static String openBin(String path) { 68 | if (lang.equals("zh_cn")) 69 | return StatusCode.STATUSOK + " 为 " + path + " 开启 BINARY 模式传输数据." + Dict.newLine; 70 | if (lang.equals("en_us")) 71 | return StatusCode.STATUSOK + " Opening BINARY mode data connection for " + path + "." + Dict.newLine; 72 | return null; 73 | } 74 | 75 | public static String pasvDataFailed() { 76 | if (lang.equals("zh_cn")) 77 | return StatusCode.UNAVAILABLE + " 被动模式接口无连接." + Dict.newLine; 78 | if (lang.equals("en_us")) 79 | return StatusCode.UNAVAILABLE + " Passive port is not connected." + Dict.newLine; 80 | return null; 81 | } 82 | 83 | public static String openPasvAscii(String path, long fileLength) { 84 | if (lang.equals("zh_cn")) 85 | return StatusCode.STATUSOK + " 启动 ASCII 模式数据传输: " + path + " (" + fileLength + " 字节)" + Dict.newLine; 86 | if (lang.equals("en_us")) 87 | return StatusCode.STATUSOK + " Opening ASCII mode data connection for " + path + " (" + fileLength + " Bytes)" + Dict.newLine; 88 | return null; 89 | } 90 | 91 | public static String openPasvBin(String path, long fileLength) { 92 | if (lang.equals("zh_cn")) 93 | return StatusCode.STATUSOK + " 启动 BINARY 模式数据传输: " + path + " (" + fileLength + " 字节)" + Dict.newLine; 94 | if (lang.equals("en_us")) 95 | return StatusCode.STATUSOK + " Opening BINARY mode data connection for " + path + " (" + fileLength + " Bytes)" + Dict.newLine; 96 | return null; 97 | } 98 | 99 | public static String pasvMode(String[] IPADD, int calcPort, int randomSub) { 100 | if (lang.equals("zh_cn")) 101 | return StatusCode.PASSIVE + " Entering Passive Mode " + "(" + IPADD[0] + "," + IPADD[1] + "," + IPADD[2] + "," + IPADD[3] + "," + calcPort + "," + randomSub + ")" + "" + Dict.newLine; 102 | if (lang.equals("en_us")) 103 | return StatusCode.PASSIVE + " Entering Passive Mode " + "(" + IPADD[0] + "," + IPADD[1] + "," + IPADD[2] + "," + IPADD[3] + "," + calcPort + "," + randomSub + ")" + "" + Dict.newLine; 104 | return null; 105 | } 106 | 107 | public static String portSuccess() { 108 | if (lang.equals("zh_cn")) 109 | return StatusCode.SUCCESS + " PORT 命令已执行." + Dict.newLine; 110 | if (lang.equals("en_us")) 111 | return StatusCode.SUCCESS + " PORT Command successful." + Dict.newLine; 112 | return null; 113 | } 114 | 115 | public static String rntoSuccess() { 116 | if (lang.equals("zh_cn")) 117 | return StatusCode.CORRECT + " RNTO 命令已执行." + Dict.newLine; 118 | if (lang.equals("en_us")) 119 | return StatusCode.CORRECT + " RNTO command successful." + Dict.newLine; 120 | return null; 121 | } 122 | 123 | public static String rnfrSuccess() { 124 | if (lang.equals("zh_cn")) 125 | return StatusCode.WAIT + " 文件或文件夹已选定, 请提供目标位置." + Dict.newLine; 126 | if (lang.equals("en_us")) 127 | return StatusCode.WAIT + " File or directory exists, ready for destination name." + Dict.newLine; 128 | return null; 129 | } 130 | 131 | public static String deleSuccess() { 132 | if (lang.equals("zh_cn")) 133 | return StatusCode.CORRECT + " DELE 命令已执行." + Dict.newLine; 134 | if (lang.equals("en_us")) 135 | return StatusCode.CORRECT + " DELE command successful." + Dict.newLine; 136 | return null; 137 | } 138 | 139 | public static String rmdSuccess() { 140 | if (lang.equals("zh_cn")) 141 | return StatusCode.CORRECT + " RMD 命令已执行." + Dict.newLine; 142 | if (lang.equals("en_us")) 143 | return StatusCode.CORRECT + " RMD command successful." + Dict.newLine; 144 | return null; 145 | } 146 | 147 | public static String createFailed(String path) { 148 | if (lang.equals("zh_cn")) 149 | return StatusCode.ERRTARGET + " " + path + ": 创建失败." + Dict.newLine; 150 | if (lang.equals("en_us")) 151 | return StatusCode.ERRTARGET + " " + path + ": Failed to create." + Dict.newLine; 152 | return null; 153 | } 154 | 155 | public static String dirCreated(String path) { 156 | if (lang.equals("zh_cn")) 157 | return StatusCode.CPATH + " 目录 \"" + path + "\" 已创建." + Dict.newLine; 158 | if (lang.equals("en_us")) 159 | return StatusCode.CPATH + " \"" + path + "\" directory created." + Dict.newLine; 160 | return null; 161 | } 162 | 163 | public static String commandOK() { 164 | if (lang.equals("zh_cn")) 165 | return StatusCode.SUCCESS + " 命令已执行." + Dict.newLine; 166 | if (lang.equals("en_us")) 167 | return StatusCode.SUCCESS + " Command okay." + Dict.newLine; 168 | return null; 169 | } 170 | 171 | public static String unixType() { 172 | if (lang.equals("zh_cn")) 173 | return StatusCode.NAME + " UNIX Type: L8" + Dict.newLine; 174 | if (lang.equals("en_us")) 175 | return StatusCode.NAME + " UNIX Type: L8" + Dict.newLine; 176 | return null; 177 | } 178 | 179 | public static String unknownCommand() { 180 | if (lang.equals("zh_cn")) 181 | return StatusCode.CMDUNKNOWN + " 未知命令." + Dict.newLine; 182 | if (lang.equals("en_us")) 183 | return StatusCode.CMDUNKNOWN + " Command don't understood." + Dict.newLine; 184 | return null; 185 | } 186 | 187 | public static String notFound(String path) { 188 | if (lang.equals("zh_cn")) 189 | return StatusCode.ERRTARGET + " " + path + ": 文件或文件夹不存在." + Dict.newLine; 190 | if (lang.equals("en_us")) 191 | return StatusCode.ERRTARGET + " " + path + ": No such file or directory." + Dict.newLine; 192 | return null; 193 | } 194 | 195 | public static String changeDir(String path) { 196 | if (lang.equals("zh_cn")) 197 | return StatusCode.CORRECT + " 目录已更改至 " + path + Dict.newLine; 198 | if (lang.equals("en_us")) 199 | return StatusCode.CORRECT + " Directory changed to " + path + Dict.newLine; 200 | return null; 201 | } 202 | 203 | public static String list() { 204 | if (lang.equals("zh_cn")) 205 | return StatusCode.STATUSOK + " 正在传输 ASCII 数据, 请稍候." + Dict.newLine; 206 | if (lang.equals("en_us")) 207 | return StatusCode.STATUSOK + " Opening ASCII mode data, please wait." + Dict.newLine; 208 | return null; 209 | } 210 | 211 | public static String type(String type) { 212 | if (lang.equals("zh_cn")) 213 | return StatusCode.SUCCESS + " 传输模式已设定为 " + type + "." + Dict.newLine; 214 | if (lang.equals("en_us")) 215 | return StatusCode.SUCCESS + " Type set to " + type + "." + Dict.newLine; 216 | return null; 217 | } 218 | 219 | public static String currentDir(String path) { 220 | if (lang.equals("zh_cn")) 221 | return StatusCode.CPATH + " \"" + path + "\" 是当前的目录." + "" + Dict.newLine; 222 | if (lang.equals("en_us")) 223 | return StatusCode.CPATH + " \"" + path + "\" is current directory." + "" + Dict.newLine; 224 | return null; 225 | } 226 | 227 | public static String notSupportSITE() { 228 | if (lang.equals("zh_cn")) 229 | return StatusCode.ERRSYNTAX + " SITE 选项不受支持." + Dict.newLine; 230 | if (lang.equals("en_us")) 231 | return StatusCode.ERRSYNTAX + " SITE option not supported." + Dict.newLine; 232 | return null; 233 | } 234 | 235 | public static String features() { 236 | if (lang.equals("zh_cn")) 237 | return StatusCode.STATUS + "-特性:" + Dict.newLine 238 | + "UTF8" + Dict.newLine 239 | + StatusCode.STATUS + " 结束" + Dict.newLine; if (lang.equals("en_us")) 240 | return StatusCode.STATUS + "-Features:" + Dict.newLine 241 | + "UTF8" + Dict.newLine 242 | + StatusCode.STATUS + " End" + Dict.newLine; 243 | return null; 244 | } 245 | 246 | public static String alreadyLogIn() { 247 | if (lang.equals("zh_cn")) 248 | return StatusCode.CMDUNKNOWN + " 你已经登录过了." + "" + Dict.newLine; 249 | if (lang.equals("en_us")) 250 | return StatusCode.CMDUNKNOWN + " You have already logged in." + "" + Dict.newLine; 251 | return null; 252 | } 253 | 254 | public static String wrongPassword() { 255 | if (lang.equals("zh_cn")) 256 | return StatusCode.NOTLOGIN + " 抱歉, 密码错误." + Dict.newLine; 257 | if (lang.equals("en_us")) 258 | return StatusCode.NOTLOGIN + " Sorry, the password is wrong." + Dict.newLine; 259 | return null; 260 | } 261 | 262 | public static String loggedIn(String username) { 263 | if (lang.equals("zh_cn")) 264 | return StatusCode.LOGGED + "-" + Dict.newLine + "===------===" + Dict.newLine + ">>> :) Good " + GoodXX.getTimeAsWord() + ", " + username + "!" + Dict.newLine 265 | + ">>> LiteFTPD https://github.com/AdlerED/LiteFTPD-UNIX" + Dict.newLine + "===------===" + Dict.newLine 266 | + "IS THE CHINESE ON THE RIGHT NORMAL? -> 中文 <- If not, type \"quote gb\" to change the encode type." + Dict.newLine + "230 OK" + Dict.newLine; 267 | if (lang.equals("en_us")) 268 | return StatusCode.LOGGED + "-" + "" + Dict.newLine + "===------===" + Dict.newLine + ">>> :) Good " + GoodXX.getTimeAsWord() + ", " + username + "!" + Dict.newLine 269 | + ">>> LiteFTPD https://github.com/AdlerED/LiteFTPD-UNIX" + Dict.newLine + "===------===" + Dict.newLine 270 | + "IS THE CHINESE ON THE RIGHT NORMAL? -> 中文 <- If not, type \"quote gb\" to change the encode type." + Dict.newLine + "230 OK" + Dict.newLine; 271 | return null; 272 | } 273 | 274 | public static String tooMuchLoginInUser(String username) { 275 | if (lang.equals("zh_cn")) 276 | return StatusCode.NOTLOGIN + " 抱歉, 用户 \"" + username + "\" 连接数过多, 请稍候重试." + Dict.newLine; 277 | if (lang.equals("en_us")) 278 | return StatusCode.NOTLOGIN + " Sorry, user \"" + username + "\" has too much login, please try again at later." + Dict.newLine; 279 | return null; 280 | } 281 | 282 | public static String utf8(boolean status) { 283 | if (lang.equals("zh_cn")) { 284 | if (status) 285 | return StatusCode.SUCCESS + " OPTS UTF8 命令已执行 - UTF8 编码现在已开启." + Dict.newLine; 286 | else 287 | return StatusCode.SUCCESS + " OPTS UTF8 命令已执行 - UTF8 编码现在已停用." + Dict.newLine; 288 | } 289 | if (lang.equals("en_us")) { 290 | if (status) 291 | return StatusCode.SUCCESS + " OPTS UTF8 command successful - UTF8 encoding now ON." + Dict.newLine; 292 | else 293 | return StatusCode.SUCCESS + " OPTS UTF8 command successful - UTF8 encoding now OFF." + Dict.newLine; 294 | } 295 | return null; 296 | } 297 | 298 | public static String bye() { 299 | if (lang.equals("zh_cn")) 300 | return StatusCode.SERVICESTOP + " :) 再见!" + "" + Dict.newLine; 301 | if (lang.equals("en_us")) 302 | return StatusCode.SERVICESTOP + " :) See ya!" + "" + Dict.newLine; 303 | return null; 304 | } 305 | 306 | public static String passwordRequired(String username) { 307 | if (lang.equals("zh_cn")) 308 | return StatusCode.PASSREQ + " 请输入用户 " + username + " 的密码." + Dict.newLine; 309 | if (lang.equals("en_us")) 310 | return StatusCode.PASSREQ + " Password required for " + username + "." + Dict.newLine; 311 | return null; 312 | } 313 | 314 | public static String permissionDenied() { 315 | if (lang.equals("zh_cn")) 316 | return StatusCode.ERRTARGET + " 没有执行此操作的权限." + Dict.newLine; 317 | if (lang.equals("en_us")) 318 | return StatusCode.ERRTARGET + " Permission denied." + Dict.newLine; 319 | return null; 320 | } 321 | 322 | public static String transferCompleteInAsciiMode(long fileLength, long time, float averageTime) { 323 | if (lang.equals("zh_cn")) 324 | return StatusCode.CLOSED + "-传输完毕. " + fileLength + " 字节在 " + time + " 秒内传输完成. 平均 " + averageTime + " KB/秒." + Dict.newLine 325 | + StatusCode.CLOSED + " 你正在使用 ASCII 模式传输文件. 如果你的文件是损坏的, 输入 \"binary\" 然后再重试一次." + Dict.newLine; 326 | if (lang.equals("en_us")) 327 | return StatusCode.CLOSED + "-Transfer complete. " + fileLength + " bytes saved in " + time + " second. " + averageTime + " KB/sec." + Dict.newLine 328 | + StatusCode.CLOSED + " You are using ASCII mode to transfer files. If you find that the file is corrupt, type \"binary\" and try again." + Dict.newLine; 329 | return null; 330 | } 331 | 332 | public static String transferComplete(long fileLength, long time, float averageTime) { 333 | if (lang.equals("zh_cn")) 334 | return StatusCode.CLOSED + " 传输完毕. " + fileLength + " 字节在 " + time + " 秒内传输完毕. 平均 " + averageTime + " KB/秒." + Dict.newLine; 335 | if (lang.equals("en_us")) 336 | return StatusCode.CLOSED + " Transfer complete. " + fileLength + " bytes saved in " + time + " second. " + averageTime + " KB/sec." + Dict.newLine; 337 | return null; 338 | } 339 | 340 | public static String connectedMessage(String ip) { 341 | if (lang.equals("zh_cn")) 342 | return StatusCode.SERVICEREADY + "-LiteFTPD" + Dict.newLine + ">>> 请登录." + Dict.newLine + ">>> 你的IP地址: " + ip + Dict.newLine + "220" + " :) " + Variable.welcomeMessage + "" + Dict.newLine; 343 | if (lang.equals("en_us")) 344 | return StatusCode.SERVICEREADY + "-LiteFTPD" + Dict.newLine + ">>> Please log in." + Dict.newLine + ">>> Your IP address: " + ip + Dict.newLine + "220" + " :) " + Variable.welcomeMessage + "" + Dict.newLine; 345 | return null; 346 | } 347 | 348 | public static String closedInReason(String reason) { 349 | if (lang.equals("zh_cn")) 350 | return "LiteFTPD > :( 抱歉, 服务端已经关闭了连接! 原因: " + reason + "." + Dict.newLine; 351 | if (lang.equals("en_us")) 352 | return "LiteFTPD > :( Sorry, the connection is closed from server! Reason: " + reason + "." + Dict.newLine; 353 | return null; 354 | } 355 | 356 | public static String outOfOnlineLimit() { 357 | if (lang.equals("zh_cn")) 358 | return StatusCode.NOTLOGIN + " :( 在线用户数过多." + "" + Dict.newLine; 359 | if (lang.equals("en_us")) 360 | return StatusCode.NOTLOGIN + " :( Too much users online." + "" + Dict.newLine; 361 | return null; 362 | } 363 | 364 | public static String onlineStr() { 365 | if (lang.equals("zh_cn")) 366 | return "在线"; 367 | if (lang.equals("en_us")) 368 | return "Online"; 369 | return null; 370 | } 371 | 372 | public static String clearStr() { 373 | if (lang.equals("zh_cn")) 374 | return "清除"; 375 | if (lang.equals("en_us")) 376 | return "Clear"; 377 | return null; 378 | } 379 | 380 | public static String actionStr() { 381 | if (lang.equals("zh_cn")) 382 | return "操作"; 383 | if (lang.equals("en_us")) 384 | return "Action"; 385 | return null; 386 | } 387 | 388 | public static String connectionsStr() { 389 | if (lang.equals("zh_cn")) 390 | return "连接数"; 391 | if (lang.equals("en_us")) 392 | return "Connection"; 393 | return null; 394 | } 395 | 396 | public static String t() { 397 | if (lang.equals("zh_cn")) 398 | return ""; 399 | if (lang.equals("en_us")) 400 | return ""; 401 | return null; 402 | } 403 | 404 | public static final String newLine = "\r\n"; 405 | } 406 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/dict/StatusCode.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.dict; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

Status code.

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-09-19 09:21 9 | **/ 10 | public class StatusCode { 11 | 12 | /** 13 | * Preliminary affirmative reply 14 | */ 15 | 16 | // Service is ready, will be start in ? min. 17 | public static final int READY = 120; 18 | // Data connection opened, transmission is starting. 19 | public static final int OPEN = 125; 20 | // File status OK, ready to open data connect. 21 | public static final int STATUSOK = 150; 22 | 23 | /** 24 | * A definite and complete answer 25 | */ 26 | 27 | // Command success. 28 | public static final int SUCCESS = 200; 29 | // Cannot execute command, too much command at the same time. 30 | public static final int MUCHCOMMAND = 202; 31 | // System status / System helping answer. 32 | public static final int STATUS = 211; 33 | // Directory status. 34 | public static final int DIRSTATUS = 212; 35 | // File status. 36 | public static final int FILESTATUS = 213; 37 | // Helping message. 38 | public static final int HELPING = 214; 39 | // NAME System type. 40 | public static final int NAME = 215; 41 | // Service is ready, can execute new user's request. 42 | public static final int SERVICEREADY = 220; 43 | // Service stopped control connection. 44 | public static final int SERVICESTOP = 221; 45 | // Data connection opened, no any transmission running. 46 | public static final int DATAOPEN = 225; 47 | // Data connection closed with successful. 48 | public static final int CLOSED = 226; 49 | // Enter passive mode. 50 | public static final int PASSIVE = 227; 51 | // User logged in. 52 | public static final int LOGGED = 230; 53 | // File request correct, done. 54 | public static final int CORRECT = 250; 55 | // Created "PATHNAME". 56 | public static final int CPATH = 257; 57 | 58 | /** 59 | * Intermediate affirmative response 60 | */ 61 | 62 | // Password required. 63 | public static final int PASSREQ = 331; 64 | // Need login. 65 | public static final int LOGIN = 332; 66 | // Waiting for file execution response. 67 | public static final int WAIT = 350; 68 | 69 | /** 70 | * Complete Answer to Transient Negation 71 | */ 72 | 73 | // Cannot open data connection. 74 | public static final int CANTOPEN = 425; 75 | // Connection closed; Transfer aborted. 76 | public static final int ABORTED = 426; 77 | //File unavailable。 78 | public static final int UNAVAILABLE = 450; 79 | //Processing local errors. 80 | public static final int ERRORS = 451; 81 | //Not enough storage space. 82 | public static final int OUTOFSPACE = 452; 83 | 84 | /** 85 | * Permanent negative completion reply 86 | */ 87 | 88 | //Command not understood. 89 | public static final int CMDUNKNOWN = 500; 90 | //A syntax error in the argument 91 | public static final int ERRSYNTAX = 501; 92 | //Command not executed. 93 | public static final int NOTRUN = 502; 94 | //Error command sequence. 95 | public static final int ERRSEQUENCE = 503; 96 | //Command arguments not executed. 97 | public static final int ERRARGS = 504; 98 | //Not logged in. 99 | public static final int NOTLOGIN = 530; 100 | //Need account while uploading files. 101 | public static final int NEEDACCOUNT = 532; 102 | //File not found / no permission. 103 | public static final int ERRTARGET = 550; 104 | //Unknown type of the page. 105 | public static final int UNTYPE = 551; 106 | //Out of storage space distribution. 107 | public static final int OUTSPACELIMIT = 552; 108 | //Filename not allowed. 109 | public static final int INVALIDFILENAME = 553; 110 | } 111 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/graphic/main/GraphicMain.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.graphic.main; 2 | 3 | import pers.adlered.liteftpd.dict.Dict; 4 | import pers.adlered.liteftpd.graphic.main.model.MainModels; 5 | import pers.adlered.liteftpd.graphic.update.RunningUpdate; 6 | 7 | import javax.swing.*; 8 | import javax.swing.border.Border; 9 | import javax.swing.border.EmptyBorder; 10 | import java.awt.*; 11 | import java.awt.event.ActionEvent; 12 | import java.awt.event.ActionListener; 13 | 14 | /** 15 | *

LiteFTPD-UNIX

16 | *

图形界面初始类

17 | * 18 | * @author : https://github.com/AdlerED 19 | * @date : 2019-10-06 21:55 20 | **/ 21 | public class GraphicMain extends JFrame implements Runnable, ActionListener { 22 | // Options 23 | JMenuItem clear = null; 24 | 25 | @Override 26 | public void run() { 27 | // Initialize 28 | setSize(800, 340); 29 | setLocationRelativeTo(null); 30 | setTitle("LiteFTPD-UNIX"); 31 | setLayout(new BorderLayout()); 32 | // Panel 33 | JPanel westPanel = new JPanel(new BorderLayout()); 34 | JPanel eastPanel = new JPanel(new BorderLayout()); 35 | // Console 36 | MainModels.console.setEditable(false); 37 | JScrollPane scrollPane = new JScrollPane(MainModels.console); 38 | scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 39 | scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 40 | // Menu bar 41 | JMenuBar jMenuBar = new JMenuBar(); 42 | JMenu action = new JMenu(Dict.actionStr()); 43 | 44 | clear = new JMenuItem(Dict.clearStr()); 45 | clear.addActionListener(this); 46 | action.add(clear); 47 | 48 | jMenuBar.add(action); 49 | // Data 50 | MainModels.data.setEditable(false); 51 | MainModels.console.setLineWrap(true); 52 | JScrollPane dataPane = new JScrollPane(MainModels.data); 53 | dataPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 54 | dataPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 55 | // Add Models 56 | westPanel.add(scrollPane, BorderLayout.NORTH); 57 | eastPanel.add(dataPane, BorderLayout.NORTH); 58 | // Add Panels 59 | add(jMenuBar, BorderLayout.NORTH); 60 | add(westPanel, BorderLayout.WEST); 61 | add(eastPanel, BorderLayout.EAST); 62 | // Must the end 63 | setVisible(true); 64 | MainModels.guiReady = true; 65 | // Data update 66 | Thread dataUpdateThread = new Thread(new RunningUpdate()); 67 | dataUpdateThread.start(); 68 | } 69 | 70 | @Override 71 | public void actionPerformed(ActionEvent e) { 72 | if (e.getSource() == clear) { 73 | MainModels.console.setText(""); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/graphic/main/model/MainModels.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.graphic.main.model; 2 | 3 | import javax.swing.*; 4 | 5 | /** 6 | *

LiteFTPD-UNIX

7 | *

存放主界面控件

8 | * 9 | * @author : https://github.com/AdlerED 10 | * @date : 2019-10-06 22:14 11 | **/ 12 | public class MainModels { 13 | public static boolean guiReady = false; 14 | 15 | public static JTextArea console = new JTextArea(18, 31); 16 | public static JTextArea data = new JTextArea(3, 32); 17 | } 18 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/graphic/update/RunningUpdate.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.graphic.update; 2 | 3 | import pers.adlered.liteftpd.dict.Dict; 4 | import pers.adlered.liteftpd.graphic.main.model.MainModels; 5 | import pers.adlered.liteftpd.logger.Logger; 6 | import pers.adlered.liteftpd.logger.enums.Levels; 7 | import pers.adlered.liteftpd.logger.enums.Types; 8 | import pers.adlered.liteftpd.user.status.Online; 9 | import pers.adlered.liteftpd.user.status.bind.UserLimitBind; 10 | 11 | import java.util.*; 12 | 13 | /** 14 | *

LiteFTPD-UNIX

15 | *

实时更新数据,并显示在界面上

16 | * 17 | * @author : https://github.com/AdlerED 18 | * @date : 2019-10-06 22:55 19 | **/ 20 | public class RunningUpdate implements Runnable { 21 | @Override 22 | public void run() { 23 | try { 24 | while (true) { 25 | List list = new ArrayList<>(); 26 | // ** Collect ** 27 | // Online 28 | list.add(" ● " + Dict.onlineStr() + " " + Online.userRuleOnline.size() + " ● " + Dict.connectionsStr() + " " + Online.ipRuleOnline.size()); 29 | // Users 30 | Map userMap = new HashMap<>(); 31 | for (UserLimitBind userLimitBind : Online.userRuleOnline) { 32 | String username = userLimitBind.getUsername(); 33 | if (userMap.get(username) != null) { 34 | int count = userMap.get(username); 35 | ++count; 36 | userMap.put(username, count); 37 | } else { 38 | userMap.put(username, 1); 39 | } 40 | } 41 | String users = ""; 42 | for (Map.Entry entry : userMap.entrySet()) { 43 | users += entry.getKey() + " (" + entry.getValue() + ") | "; 44 | } 45 | users = users.replaceAll("( \\| )$", ""); 46 | list.add(users); 47 | // ** Update ** 48 | String text = ""; 49 | for (String i : list) { 50 | text += i + "\n"; 51 | } 52 | MainModels.data.setText(text); 53 | Thread.sleep(1000); 54 | } 55 | } catch (InterruptedException IE) { 56 | IE.printStackTrace(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/logger/Filter.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.logger; 2 | 3 | import pers.adlered.liteftpd.logger.enums.Levels; 4 | import pers.adlered.liteftpd.variable.Variable; 5 | 6 | /** 7 | *

LiteFTPD-UNIX

8 | *

When Logger received a log, Filter will check it's level to decide display or not.

9 | * 10 | * @author : https://github.com/AdlerED 11 | * @date : 2019-09-19 15:17 12 | **/ 13 | public class Filter { 14 | public static boolean fil(Levels level) { 15 | boolean status = false; 16 | switch (level) { 17 | case DEBUG: 18 | if (Variable.debugLevel >= 4) { 19 | status = true; 20 | } 21 | break; 22 | case ERROR: 23 | if (Variable.debugLevel >= 3) { 24 | status = true; 25 | } 26 | break; 27 | case WARN: 28 | if (Variable.debugLevel >= 2) { 29 | status = true; 30 | } 31 | break; 32 | case INFO: 33 | if (Variable.debugLevel >= 1) { 34 | status = true; 35 | } 36 | break; 37 | } 38 | return status; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/logger/Logger.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.logger; 2 | 3 | import pers.adlered.liteftpd.graphic.main.model.MainModels; 4 | import pers.adlered.liteftpd.logger.enums.Levels; 5 | import pers.adlered.liteftpd.logger.enums.Types; 6 | 7 | /** 8 | *

LiteFTPD-UNIX

9 | *

All logs will through here.

10 | * 11 | * @author : https://github.com/AdlerED 12 | * @date : 2019-09-19 15:18 13 | **/ 14 | public class Logger { 15 | public static boolean log(Types type, Levels level, String log) { 16 | if (Filter.fil(level)) { 17 | // Can be logged 18 | System.out.println("[" + level + "]" + " " + "[" + type + "]" + " >> " + log); 19 | if (MainModels.guiReady) { 20 | MainModels.console.append("[" + level + "]" + " " + "[" + type + "]" + " >> " + log + "\n"); 21 | MainModels.console.setCaretPosition(MainModels.console.getDocument().getLength()); 22 | } 23 | return true; 24 | } else { 25 | // Cannot log 26 | return false; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/logger/enums/Levels.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.logger.enums; 2 | 3 | public enum Levels { 4 | ERROR, WARN, INFO, DEBUG 5 | } 6 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/logger/enums/Types.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.logger.enums; 2 | 3 | public enum Types { 4 | /* 5 | SYS: System message; 6 | RECV: Receive task status; 7 | SEND: Send task status; 8 | TRANS: Transition task status; 9 | */ 10 | SYS, RECV, SEND, TRANS 11 | } 12 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/main/Main.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.main; 2 | 3 | import pers.adlered.liteftpd.dict.Dict; 4 | import pers.adlered.liteftpd.graphic.main.GraphicMain; 5 | import pers.adlered.liteftpd.logger.enums.Levels; 6 | import pers.adlered.liteftpd.logger.Logger; 7 | import pers.adlered.liteftpd.logger.enums.Types; 8 | import pers.adlered.liteftpd.pool.handler.HandlerPool; 9 | import pers.adlered.liteftpd.tool.ConsoleTable; 10 | import pers.adlered.liteftpd.tool.LocalAddress; 11 | import pers.adlered.liteftpd.tool.Status; 12 | import pers.adlered.liteftpd.user.User; 13 | import pers.adlered.liteftpd.user.status.Online; 14 | import pers.adlered.liteftpd.user.status.bind.IpLimitBind; 15 | import pers.adlered.liteftpd.user.status.bind.SpeedLimitBind; 16 | import pers.adlered.liteftpd.user.verify.OnlineRules; 17 | import pers.adlered.liteftpd.variable.OnlineUserController; 18 | import pers.adlered.liteftpd.variable.Variable; 19 | import pers.adlered.liteftpd.wizard.config.Prop; 20 | import pers.adlered.liteftpd.wizard.init.SocketHandler; 21 | 22 | import java.io.BufferedOutputStream; 23 | import java.io.IOException; 24 | import java.net.ServerSocket; 25 | import java.net.Socket; 26 | 27 | /** 28 | *

LiteFTPD-UNIX

29 | *

Main method of LiteFTPD, listening connections and create a new thread into thread pool.

30 | * 31 | * @author : https://github.com/AdlerED 32 | * @date : 2019-09-19 09:21 33 | **/ 34 | public class Main { 35 | public static void main(String[] args) { 36 | Main.init(args); 37 | ServerSocket serverSocket = null; 38 | try { 39 | Logger.log(Types.SYS, Levels.INFO, "LiteFTPD by AdlerED <- GitHub"); 40 | // Listen socket connections, handle with SocketHandler. 41 | serverSocket = new ServerSocket(Variable.port); 42 | Logger.log(Types.SYS, Levels.INFO, "Listening " + serverSocket.getLocalSocketAddress()); 43 | Logger.log(Types.SYS, Levels.INFO, "You can connect to the FTP Server via following IP address:"); 44 | ConsoleTable consoleTable = new ConsoleTable(LocalAddress.getLocalIPList().size(), true); 45 | consoleTable.appendRow(); 46 | consoleTable.appendColum("Listening IP:Port") 47 | .appendColum("github.com/AdlerED"); 48 | consoleTable.appendRow(); 49 | if (Variable.debugLevel >= 1) { 50 | for (String i : LocalAddress.getLocalIPList()) { 51 | consoleTable.appendColum(i + ":" + serverSocket.getLocalPort()); 52 | } 53 | } 54 | Logger.log(Types.SYS, Levels.INFO, "\n" + consoleTable.toString()); 55 | } catch (IOException IOE) { 56 | // TODO 57 | IOE.printStackTrace(); 58 | } 59 | while (true) { 60 | try { 61 | Logger.log(Types.SYS, Levels.INFO, "Memory used: " + Status.memoryUsed()); 62 | Socket socket = serverSocket.accept(); 63 | // Online limit checking 64 | String hostAdd = socket.getInetAddress().getHostAddress(); 65 | IpLimitBind ipLimitBind = OnlineRules.checkIpAddress(hostAdd); 66 | if (((ipLimitBind.getIp() == null) || Variable.online >= Variable.maxUserLimit) && Variable.maxUserLimit != 0) { 67 | for (int i = 0; i < Online.ipRuleOnline.size(); i++) { 68 | if (Online.ipRuleOnline.get(i).getIp().equals(hostAdd)) { 69 | Online.ipRuleOnline.remove(i); 70 | break; 71 | } 72 | } 73 | BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); 74 | bufferedOutputStream.write(Dict.outOfOnlineLimit().getBytes()); 75 | bufferedOutputStream.flush(); 76 | bufferedOutputStream.close(); 77 | socket.close(); 78 | } else { 79 | HandlerPool.handlerPool.execute(new SocketHandler(socket, ipLimitBind)); 80 | OnlineUserController.printOnline(); 81 | } 82 | } catch (IOException IOE) { 83 | // TODO 84 | IOE.printStackTrace(); 85 | } 86 | } 87 | } 88 | 89 | public static void init(String[] args) { 90 | // 设置LiteFTPD关闭时的操作 91 | Runtime runtime = Runtime.getRuntime(); 92 | runtime.addShutdownHook(new Thread() { 93 | @Override 94 | public void run() { 95 | Logger.log(Types.SYS, Levels.INFO, "LiteFTPD stopped."); 96 | } 97 | }); 98 | // 读取配置文件 99 | Prop.getInstance(); 100 | // 检测传值,执行指定操作 101 | for (int i = 0; i < args.length; i++) { 102 | String variable = args[i]; 103 | if (variable.equals("-l")) { 104 | String value = args[i + 1]; 105 | value = value.replaceAll("-", "_"); 106 | if (value.equals("en_us")) { 107 | Logger.log(Types.SYS, Levels.INFO, "Language option detected. Init language as \"English\"."); 108 | Dict.init(value); 109 | } else if (value.equals("zh_cn")) { 110 | Logger.log(Types.SYS, Levels.INFO, "Language option detected. Init language as \"简体中文\"."); 111 | Dict.init(value); 112 | } else { 113 | Logger.log(Types.SYS, Levels.WARN, "Cannot support customize language \"" + value + "\". Using default \"English\"."); 114 | Logger.log(Types.SYS, Levels.WARN, "Supported language: zh_cn en_us"); 115 | } 116 | } else if (variable.equals("-g")) { 117 | Logger.log(Types.SYS, Levels.INFO, "Launching graphic interface..."); 118 | Thread GUI = new Thread(new GraphicMain()); 119 | GUI.run(); 120 | } 121 | } 122 | // 初始化用户信息 123 | User.initUsers(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/mode/PASV.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.mode; 2 | 3 | import pers.adlered.liteftpd.analyze.PrivateVariable; 4 | import pers.adlered.liteftpd.dict.Dict; 5 | import pers.adlered.liteftpd.logger.enums.Levels; 6 | import pers.adlered.liteftpd.logger.Logger; 7 | import pers.adlered.liteftpd.logger.enums.Types; 8 | import pers.adlered.liteftpd.user.status.bind.SpeedLimitBind; 9 | import pers.adlered.liteftpd.wizard.init.PauseListen; 10 | import pers.adlered.liteftpd.wizard.init.Send; 11 | import pers.adlered.liteftpd.variable.Variable; 12 | 13 | import java.io.*; 14 | import java.net.BindException; 15 | import java.net.ServerSocket; 16 | import java.net.Socket; 17 | import java.net.SocketException; 18 | 19 | /** 20 | *

LiteFTPD-UNIX

21 | *

Passive mode.

22 | * 23 | * @author : https://github.com/AdlerED 24 | * @date : 2019-09-19 09:21 25 | **/ 26 | public class PASV extends Thread { 27 | private ServerSocket serverSocket = null; 28 | private Socket socket = null; 29 | private Send send = null; 30 | private PrivateVariable privateVariable = null; 31 | private PauseListen pauseListen = null; 32 | 33 | private String listening = null; 34 | private File file = null; 35 | private String path = null; 36 | private SpeedLimitBind speedLimitBind = null; 37 | 38 | private boolean isASCII = true; 39 | 40 | public PASV(Send send, PrivateVariable privateVariable, PauseListen pauseListen, String type, SpeedLimitBind speedLimitBind) { 41 | this.send = send; 42 | this.privateVariable = privateVariable; 43 | this.pauseListen = pauseListen; 44 | if (type.equals("I")) { 45 | isASCII = false; 46 | } 47 | this.speedLimitBind = speedLimitBind; 48 | } 49 | 50 | public boolean listen(int port) { 51 | boolean result = true; 52 | try { 53 | ServerSocket serverSocket = new ServerSocket(port); 54 | Logger.log(Types.SYS, Levels.DEBUG, "Listening " + port + "..."); 55 | this.serverSocket = serverSocket; 56 | } catch (BindException BE) { 57 | result = false; 58 | } catch (IOException IOE) { 59 | result = false; 60 | } 61 | return result; 62 | } 63 | 64 | @Override 65 | public void run() { 66 | try { 67 | Logger.log(Types.SYS, Levels.DEBUG, "Transmitter is waiting the port " + serverSocket.getLocalPort() + " for the client."); 68 | Socket socket = serverSocket.accept(); 69 | this.socket = socket; 70 | Logger.log(Types.SYS, Levels.DEBUG, "Connected. Waiting for " + socket.getRemoteSocketAddress() + "..."); 71 | try { 72 | while (listening == null && file == null && path == null) { 73 | if (!pauseListen.isRunning()) { 74 | Logger.log(Types.SYS, Levels.WARN, "Passive mode listener paused."); 75 | break; 76 | } 77 | Thread.sleep(5); 78 | } 79 | } catch (InterruptedException IE) { 80 | } 81 | privateVariable.setTimeoutLock(true); 82 | if (pauseListen.isRunning()) { 83 | // 开始传输 84 | Logger.log(Types.SYS, Levels.DEBUG, "Service has response."); 85 | long startTime = System.currentTimeMillis(); 86 | float kb = 0; 87 | long bts = 0; 88 | if (listening != null) { 89 | // To avoid bare line feeds. 90 | BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); 91 | listening = listening.replaceAll("" + Dict.newLine, "\n"); 92 | listening = listening.replaceAll("\n", "" + Dict.newLine); 93 | if (Variable.smartEncode) { 94 | bufferedOutputStream.write(listening.getBytes(privateVariable.encode)); 95 | } else { 96 | bufferedOutputStream.write(listening.getBytes(Variable.defaultEncode)); 97 | } 98 | bufferedOutputStream.flush(); 99 | bufferedOutputStream.close(); 100 | bts = (listening.getBytes(privateVariable.encode)).length; 101 | } else if (file != null) { 102 | if (speedLimitBind.getDownloadSpeed() != 0) { 103 | FileInputStream fileInputStream = new FileInputStream(file); 104 | OutputStream outputStream = new DataOutputStream(socket.getOutputStream()); 105 | byte[] bytes = new byte[speedLimitBind.getDownloadSpeed() * 1024]; 106 | int len = -1; 107 | if (privateVariable.getRest() == 0l) { 108 | if (!isASCII) { 109 | // Not rest mode 110 | while ((len = fileInputStream.read(bytes)) != -1) { 111 | outputStream.write(bytes, 0, len); 112 | Thread.sleep(1000); 113 | } 114 | outputStream.flush(); 115 | fileInputStream.close(); 116 | outputStream.close(); 117 | bts = file.length(); 118 | } else { 119 | FileReader fileReader = new FileReader(file); 120 | BufferedReader bufferedReader = new BufferedReader(fileReader); 121 | OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); 122 | int length = -1; 123 | char[] chars = new char[speedLimitBind.getUploadSpeed() * 1024]; 124 | while ((length = bufferedReader.read(chars)) != -1) { 125 | outputStreamWriter.write(String.valueOf(chars, 0, length).replaceAll("\r", "").replaceAll("\n", "\r\n")); 126 | Thread.sleep(1000); 127 | } 128 | outputStreamWriter.flush(); 129 | fileReader.close(); 130 | outputStreamWriter.close(); 131 | outputStream.close(); 132 | bts = file.length(); 133 | } 134 | } else { 135 | // Rest mode on 136 | fileInputStream.skip(privateVariable.getRest()); 137 | while ((len = fileInputStream.read(bytes)) != -1) { 138 | outputStream.write(bytes, 0, len); 139 | Thread.sleep(1000); 140 | } 141 | outputStream.flush(); 142 | fileInputStream.close(); 143 | outputStream.close(); 144 | bts = file.length() - privateVariable.getRest(); 145 | privateVariable.resetRest(); 146 | } 147 | } else { 148 | FileInputStream fileInputStream = new FileInputStream(file); 149 | OutputStream outputStream = new DataOutputStream(socket.getOutputStream()); 150 | byte[] bytes = new byte[8192]; 151 | int len = -1; 152 | if (privateVariable.getRest() == 0l) { 153 | if (!isASCII) { 154 | // Not rest mode 155 | while ((len = fileInputStream.read(bytes)) != -1) { 156 | outputStream.write(bytes, 0, len); 157 | } 158 | outputStream.flush(); 159 | fileInputStream.close(); 160 | outputStream.close(); 161 | bts = file.length(); 162 | } else { 163 | FileReader fileReader = new FileReader(file); 164 | BufferedReader bufferedReader = new BufferedReader(fileReader); 165 | OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); 166 | String line; 167 | while ((line = bufferedReader.readLine()) != null) { 168 | outputStreamWriter.write(line + "\r\n"); 169 | } 170 | outputStreamWriter.flush(); 171 | fileReader.close(); 172 | outputStreamWriter.close(); 173 | outputStream.close(); 174 | bts = file.length(); 175 | } 176 | } else { 177 | // Rest mode on 178 | fileInputStream.skip(privateVariable.getRest()); 179 | while ((len = fileInputStream.read(bytes)) != -1) { 180 | outputStream.write(bytes, 0, len); 181 | } 182 | outputStream.flush(); 183 | fileInputStream.close(); 184 | outputStream.close(); 185 | bts = file.length() - privateVariable.getRest(); 186 | privateVariable.resetRest(); 187 | } 188 | } 189 | } else if (path != null) { 190 | Logger.log(Types.RECV, Levels.DEBUG, "Passive mode store. Path: " + path); 191 | File file = new File(path); 192 | if (!file.getParentFile().exists()) { 193 | file.getParentFile().mkdirs(); 194 | } 195 | FileOutputStream fileOutputStream = null; 196 | if (privateVariable.getRest() == 0l) { 197 | boolean deleted = file.delete(); 198 | Logger.log(Types.RECV, Levels.DEBUG, "The file is already exists but deleted: " + deleted); 199 | fileOutputStream = new FileOutputStream(file, false); 200 | } else { 201 | Logger.log(Types.RECV, Levels.DEBUG, "Continue file receive."); 202 | fileOutputStream = new FileOutputStream(file, true); 203 | } 204 | // FileOutputStream will be create a new file auto. 205 | if (speedLimitBind.getUploadSpeed() == 0) { 206 | if (!isASCII) { 207 | try { 208 | InputStream inputStream = socket.getInputStream(); 209 | byte[] bytes = new byte[8192]; 210 | int len = -1; 211 | long sTime = System.currentTimeMillis(); 212 | while ((len = inputStream.read(bytes)) != -1) { 213 | fileOutputStream.write(bytes, 0, len); 214 | } 215 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 216 | if (eTime == 0) eTime = 1; 217 | float pSecond = file.length() / eTime; 218 | fileOutputStream.flush(); 219 | send.send(Dict.transferComplete(file.length(), eTime, pSecond)); 220 | inputStream.close(); 221 | fileOutputStream.close(); 222 | } catch (FileNotFoundException FNFE) { 223 | send.send(Dict.permissionDenied()); 224 | FNFE.printStackTrace(); 225 | } 226 | } else { 227 | try { 228 | FileWriter fileWriter = new FileWriter(file); 229 | BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); 230 | InputStream inputStream = socket.getInputStream(); 231 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream); 232 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 233 | String line; 234 | long sTime = System.currentTimeMillis(); 235 | while ((line = bufferedReader.readLine()) != null) { 236 | bufferedWriter.write(line + "\n"); 237 | } 238 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 239 | if (eTime == 0) eTime = 1; 240 | float pSecond = file.length() / eTime; 241 | bufferedWriter.flush(); 242 | send.send(Dict.transferCompleteInAsciiMode(file.length(), eTime, pSecond)); 243 | bufferedReader.close(); 244 | inputStreamReader.close(); 245 | inputStream.close(); 246 | bufferedWriter.close(); 247 | fileWriter.close(); 248 | } catch (FileNotFoundException FNFE) { 249 | send.send(Dict.permissionDenied()); 250 | FNFE.printStackTrace(); 251 | } 252 | } 253 | } else { 254 | if (!isASCII) { 255 | try { 256 | InputStream inputStream = socket.getInputStream(); 257 | BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, speedLimitBind.getUploadSpeed() * 1024); 258 | int len = -1; 259 | long sTime = System.currentTimeMillis(); 260 | byte[] bytes = new byte[speedLimitBind.getUploadSpeed() * 1024]; 261 | while ((len = bufferedInputStream.read(bytes)) != -1) { 262 | fileOutputStream.write(bytes, 0, len); 263 | Thread.sleep(1000); 264 | } 265 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 266 | if (eTime == 0) eTime = 1; 267 | float pSecond = file.length() / eTime; 268 | fileOutputStream.flush(); 269 | send.send(Dict.transferComplete(file.length(), eTime, pSecond)); 270 | inputStream.close(); 271 | fileOutputStream.close(); 272 | } catch (FileNotFoundException FNFE) { 273 | send.send(Dict.permissionDenied()); 274 | FNFE.printStackTrace(); 275 | } 276 | } else { 277 | try { 278 | FileWriter fileWriter = new FileWriter(file); 279 | BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); 280 | InputStream inputStream = socket.getInputStream(); 281 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream); 282 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 283 | int len = -1; 284 | long sTime = System.currentTimeMillis(); 285 | char[] chars = new char[speedLimitBind.getUploadSpeed() * 1024]; 286 | while ((len = bufferedReader.read(chars)) != -1) { 287 | bufferedWriter.write(String.valueOf(chars, 0, len).replaceAll("\r\n", "\n")); 288 | Thread.sleep(1000); 289 | } 290 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 291 | if (eTime == 0) eTime = 1; 292 | float pSecond = file.length() / eTime; 293 | bufferedWriter.flush(); 294 | send.send(Dict.transferCompleteInAsciiMode(file.length(), eTime, pSecond)); 295 | bufferedReader.close(); 296 | inputStreamReader.close(); 297 | inputStream.close(); 298 | bufferedWriter.close(); 299 | fileWriter.close(); 300 | } catch (FileNotFoundException FNFE) { 301 | send.send(Dict.permissionDenied()); 302 | FNFE.printStackTrace(); 303 | } 304 | } 305 | } 306 | privateVariable.resetRest(); 307 | } 308 | if (path != null) { 309 | socket.close(); 310 | serverSocket.close(); 311 | } else { 312 | kb = bts / 1000; 313 | socket.close(); 314 | serverSocket.close(); 315 | long endTime = (System.currentTimeMillis() - startTime) / 1000; 316 | if (endTime == 0) endTime = 1; 317 | float perSecond = kb / endTime; 318 | if (isASCII && listening == null) { 319 | send.send(Dict.transferCompleteInAsciiMode(bts, endTime, perSecond)); 320 | } else { 321 | send.send(Dict.transferComplete(bts, endTime, perSecond)); 322 | } 323 | } 324 | } 325 | } catch (SocketException SE) { 326 | Logger.log(Types.SYS, Levels.ERROR, "Listening stopped."); 327 | } catch (IOException IOE) { 328 | // TODO 329 | IOE.printStackTrace(); 330 | } catch (Exception E) { 331 | E.printStackTrace(); 332 | } finally { 333 | if (pauseListen.isRunning()) { 334 | privateVariable.setTimeoutLock(false); 335 | } 336 | send = null; 337 | privateVariable = null; 338 | pauseListen = null; 339 | serverSocket = null; 340 | socket = null; 341 | listening = null; 342 | file = null; 343 | Logger.log(Types.SYS, Levels.DEBUG, "PASV Closed."); 344 | } 345 | } 346 | 347 | public void hello(String message) { 348 | listening = message; 349 | } 350 | 351 | public void hello(File file) { 352 | this.file = file; 353 | } 354 | 355 | public void helloSTOR(String path) { 356 | this.path = path; 357 | } 358 | 359 | public void stopSocket() { 360 | try { 361 | serverSocket.close(); 362 | socket.shutdownInput(); 363 | socket.shutdownOutput(); 364 | socket.close(); 365 | Logger.log(Types.SYS, Levels.DEBUG, "Server socket on " + serverSocket.getLocalSocketAddress() + "stopped."); 366 | } catch (IOException IOE) { 367 | IOE.printStackTrace(); 368 | } catch (NullPointerException NPE) { 369 | Logger.log(Types.SYS, Levels.WARN, "Latest passive port not connected. Closing forced."); 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/mode/PORT.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.mode; 2 | 3 | import pers.adlered.liteftpd.analyze.PrivateVariable; 4 | import pers.adlered.liteftpd.dict.Dict; 5 | import pers.adlered.liteftpd.logger.enums.Levels; 6 | import pers.adlered.liteftpd.logger.Logger; 7 | import pers.adlered.liteftpd.logger.enums.Types; 8 | import pers.adlered.liteftpd.user.status.bind.SpeedLimitBind; 9 | import pers.adlered.liteftpd.wizard.init.PauseListen; 10 | import pers.adlered.liteftpd.wizard.init.Send; 11 | import pers.adlered.liteftpd.variable.Variable; 12 | 13 | import java.io.*; 14 | import java.net.Socket; 15 | import java.net.SocketException; 16 | import java.net.UnknownHostException; 17 | 18 | /** 19 | *

LiteFTPD-UNIX

20 | *

Passive mode.

21 | * 22 | * @author : https://github.com/AdlerED 23 | * @date : 2019-09-19 09:21 24 | **/ 25 | public class PORT extends Thread { 26 | private Socket socket = null; 27 | private Send send = null; 28 | private PrivateVariable privateVariable = null; 29 | private PauseListen pauseListen = null; 30 | 31 | private String listening = null; 32 | private File file = null; 33 | private String path = null; 34 | private String ip = null; 35 | 36 | private int port = -1; 37 | private boolean isASCII = true; 38 | SpeedLimitBind speedLimitBind = null; 39 | 40 | public PORT(Send send, PrivateVariable privateVariable, PauseListen pauseListen, String type, SpeedLimitBind speedLimitBind) { 41 | this.send = send; 42 | this.privateVariable = privateVariable; 43 | this.pauseListen = pauseListen; 44 | if (type.equals("I")) { 45 | isASCII = false; 46 | } 47 | this.speedLimitBind = speedLimitBind; 48 | } 49 | 50 | public void setTarget(String ip, int port) { 51 | this.ip = ip; 52 | this.port = port; 53 | } 54 | 55 | private boolean connect() { 56 | boolean result = true; 57 | try { 58 | Logger.log(Types.SYS, Levels.DEBUG, "Connecting to " + ip + ":" + port + "..."); 59 | Socket socket = new Socket(ip, port); 60 | this.socket = socket; 61 | } catch (UnknownHostException UHE) { 62 | result = false; 63 | } catch (IllegalArgumentException IAE) { 64 | result = false; 65 | } catch (IOException IOE) { 66 | result = false; 67 | } 68 | return result; 69 | } 70 | 71 | @Override 72 | public void run() { 73 | if (connect()) { 74 | try { 75 | Logger.log(Types.SYS, Levels.DEBUG, "Connected. Translating with " + socket.getLocalSocketAddress() + " to " + socket.getRemoteSocketAddress() + "..."); 76 | privateVariable.setTimeoutLock(true); 77 | if (pauseListen.isRunning()) { 78 | Logger.log(Types.SYS, Levels.DEBUG, "Port mode is in transmission."); 79 | long startTime = System.currentTimeMillis(); 80 | float kb = 0; 81 | long bts = 0; 82 | if (listening != null) { 83 | // To avoid bare line feeds. 84 | BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); 85 | listening = listening.replaceAll("" + Dict.newLine, "\n"); 86 | listening = listening.replaceAll("\n", "" + Dict.newLine); 87 | if (Variable.smartEncode) { 88 | bufferedOutputStream.write(listening.getBytes(privateVariable.encode)); 89 | } else { 90 | bufferedOutputStream.write(listening.getBytes(Variable.defaultEncode)); 91 | } 92 | bufferedOutputStream.flush(); 93 | bufferedOutputStream.close(); 94 | bts = (listening.getBytes(privateVariable.encode)).length; 95 | } else if (file != null) { 96 | if (speedLimitBind.getDownloadSpeed() != 0) { 97 | FileInputStream fileInputStream = new FileInputStream(file); 98 | OutputStream outputStream = new DataOutputStream(socket.getOutputStream()); 99 | byte[] bytes = new byte[speedLimitBind.getDownloadSpeed() * 1024]; 100 | int len = -1; 101 | if (privateVariable.getRest() == 0l) { 102 | if (!isASCII) { 103 | // Not rest mode 104 | while ((len = fileInputStream.read(bytes)) != -1) { 105 | outputStream.write(bytes, 0, len); 106 | Thread.sleep(1000); 107 | } 108 | outputStream.flush(); 109 | fileInputStream.close(); 110 | outputStream.close(); 111 | bts = file.length(); 112 | } else { 113 | FileReader fileReader = new FileReader(file); 114 | BufferedReader bufferedReader = new BufferedReader(fileReader); 115 | OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); 116 | int length = -1; 117 | char[] chars = new char[speedLimitBind.getUploadSpeed() * 1024]; 118 | while ((length = bufferedReader.read(chars)) != -1) { 119 | outputStreamWriter.write(String.valueOf(chars, 0, length).replaceAll("\r", "").replaceAll("\n", "\r\n")); 120 | Thread.sleep(1000); 121 | } 122 | outputStreamWriter.flush(); 123 | fileReader.close(); 124 | outputStreamWriter.close(); 125 | outputStream.close(); 126 | bts = file.length(); 127 | } 128 | } else { 129 | // Rest mode on 130 | fileInputStream.skip(privateVariable.getRest()); 131 | while ((len = fileInputStream.read(bytes)) != -1) { 132 | outputStream.write(bytes, 0, len); 133 | Thread.sleep(1000); 134 | } 135 | outputStream.flush(); 136 | fileInputStream.close(); 137 | outputStream.close(); 138 | bts = file.length() - privateVariable.getRest(); 139 | privateVariable.resetRest(); 140 | } 141 | } else { 142 | FileInputStream fileInputStream = new FileInputStream(file); 143 | OutputStream outputStream = new DataOutputStream(socket.getOutputStream()); 144 | byte[] bytes = new byte[8192]; 145 | int len = -1; 146 | if (privateVariable.getRest() == 0l) { 147 | if (!isASCII) { 148 | // Not rest mode 149 | while ((len = fileInputStream.read(bytes)) != -1) { 150 | outputStream.write(bytes, 0, len); 151 | } 152 | outputStream.flush(); 153 | fileInputStream.close(); 154 | outputStream.close(); 155 | bts = file.length(); 156 | } else { 157 | FileReader fileReader = new FileReader(file); 158 | BufferedReader bufferedReader = new BufferedReader(fileReader); 159 | OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); 160 | String line; 161 | while ((line = bufferedReader.readLine()) != null) { 162 | outputStreamWriter.write(line + "\r\n"); 163 | } 164 | outputStreamWriter.flush(); 165 | fileReader.close(); 166 | outputStreamWriter.close(); 167 | outputStream.close(); 168 | bts = file.length(); 169 | } 170 | } else { 171 | // Rest mode on 172 | fileInputStream.skip(privateVariable.getRest()); 173 | while ((len = fileInputStream.read(bytes)) != -1) { 174 | outputStream.write(bytes, 0, len); 175 | } 176 | outputStream.flush(); 177 | fileInputStream.close(); 178 | outputStream.close(); 179 | bts = file.length() - privateVariable.getRest(); 180 | privateVariable.resetRest(); 181 | } 182 | } 183 | } else if (path != null) { 184 | Logger.log(Types.RECV, Levels.DEBUG, "Port mode store. Path: " + path); 185 | File file = new File(path); 186 | if (!file.getParentFile().exists()) { 187 | file.getParentFile().mkdirs(); 188 | } 189 | FileOutputStream fileOutputStream = null; 190 | if (privateVariable.getRest() == 0l) { 191 | boolean deleted = file.delete(); 192 | Logger.log(Types.RECV, Levels.DEBUG, "The file is already exists but deleted: " + deleted); 193 | fileOutputStream = new FileOutputStream(file, false); 194 | } else { 195 | Logger.log(Types.RECV, Levels.DEBUG, "Continue file receive."); 196 | fileOutputStream = new FileOutputStream(file, true); 197 | } 198 | // FileOutputStream will be create a new file auto. 199 | if (speedLimitBind.getUploadSpeed() == 0) { 200 | if (!isASCII) { 201 | try { 202 | InputStream inputStream = socket.getInputStream(); 203 | byte[] bytes = new byte[8192]; 204 | int len = -1; 205 | long sTime = System.currentTimeMillis(); 206 | while ((len = inputStream.read(bytes)) != -1) { 207 | fileOutputStream.write(bytes, 0, len); 208 | } 209 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 210 | if (eTime == 0) eTime = 1; 211 | float pSecond = file.length() / eTime; 212 | fileOutputStream.flush(); 213 | send.send(Dict.transferComplete(file.length(), eTime, pSecond)); 214 | inputStream.close(); 215 | fileOutputStream.close(); 216 | } catch (FileNotFoundException FNFE) { 217 | send.send(Dict.permissionDenied()); 218 | FNFE.printStackTrace(); 219 | } 220 | } else { 221 | try { 222 | FileWriter fileWriter = new FileWriter(file); 223 | BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); 224 | InputStream inputStream = socket.getInputStream(); 225 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream); 226 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 227 | String line; 228 | long sTime = System.currentTimeMillis(); 229 | while ((line = bufferedReader.readLine()) != null) { 230 | bufferedWriter.write(line + "\n"); 231 | } 232 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 233 | if (eTime == 0) eTime = 1; 234 | float pSecond = file.length() / eTime; 235 | bufferedWriter.flush(); 236 | send.send(Dict.transferCompleteInAsciiMode(file.length(), eTime, pSecond)); 237 | bufferedReader.close(); 238 | inputStreamReader.close(); 239 | inputStream.close(); 240 | bufferedWriter.close(); 241 | fileWriter.close(); 242 | } catch (FileNotFoundException FNFE) { 243 | send.send(Dict.permissionDenied()); 244 | FNFE.printStackTrace(); 245 | } 246 | } 247 | } else { 248 | if (!isASCII) { 249 | try { 250 | InputStream inputStream = socket.getInputStream(); 251 | BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, speedLimitBind.getUploadSpeed() * 1024); 252 | int len = -1; 253 | long sTime = System.currentTimeMillis(); 254 | byte[] bytes = new byte[speedLimitBind.getUploadSpeed() * 1024]; 255 | while ((len = bufferedInputStream.read(bytes)) != -1) { 256 | fileOutputStream.write(bytes, 0, len); 257 | Thread.sleep(1000); 258 | } 259 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 260 | if (eTime == 0) eTime = 1; 261 | float pSecond = file.length() / eTime; 262 | fileOutputStream.flush(); 263 | send.send(Dict.transferComplete(file.length(), eTime, pSecond)); 264 | inputStream.close(); 265 | fileOutputStream.close(); 266 | } catch (FileNotFoundException FNFE) { 267 | send.send(Dict.permissionDenied()); 268 | FNFE.printStackTrace(); 269 | } 270 | } else { 271 | try { 272 | FileWriter fileWriter = new FileWriter(file); 273 | BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); 274 | InputStream inputStream = socket.getInputStream(); 275 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream); 276 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 277 | int len = -1; 278 | long sTime = System.currentTimeMillis(); 279 | char[] chars = new char[speedLimitBind.getUploadSpeed() * 1024]; 280 | while ((len = bufferedReader.read(chars)) != -1) { 281 | bufferedWriter.write(String.valueOf(chars, 0, len).replaceAll("\r\n", "\n")); 282 | Thread.sleep(1000); 283 | } 284 | long eTime = (System.currentTimeMillis() - sTime) / 1000; 285 | if (eTime == 0) eTime = 1; 286 | float pSecond = file.length() / eTime; 287 | bufferedWriter.flush(); 288 | send.send(Dict.transferCompleteInAsciiMode(file.length(), eTime, pSecond)); 289 | bufferedReader.close(); 290 | inputStreamReader.close(); 291 | inputStream.close(); 292 | bufferedWriter.close(); 293 | fileWriter.close(); 294 | } catch (FileNotFoundException FNFE) { 295 | send.send(Dict.permissionDenied()); 296 | FNFE.printStackTrace(); 297 | } 298 | } 299 | } 300 | privateVariable.resetRest(); 301 | } 302 | if (path != null) { 303 | socket.close(); 304 | } else { 305 | kb = bts / 1000; 306 | socket.close(); 307 | long endTime = (System.currentTimeMillis() - startTime) / 1000; 308 | if (endTime == 0) endTime = 1; 309 | float perSecond = kb / endTime; 310 | if (isASCII && listening == null) { 311 | send.send(Dict.transferCompleteInAsciiMode(bts, endTime, perSecond)); 312 | } else { 313 | send.send(Dict.transferComplete(bts, endTime, perSecond)); 314 | } 315 | } 316 | } 317 | } catch (SocketException SE) { 318 | Logger.log(Types.SYS, Levels.ERROR, "Listening stopped."); 319 | } catch (IOException IOE) { 320 | //TODO 321 | IOE.printStackTrace(); 322 | } catch (Exception E) { 323 | E.printStackTrace(); 324 | } finally { 325 | if (pauseListen.isRunning()) { 326 | privateVariable.setTimeoutLock(false); 327 | } 328 | send = null; 329 | privateVariable = null; 330 | pauseListen = null; 331 | socket = null; 332 | listening = null; 333 | file = null; 334 | Logger.log(Types.SYS, Levels.DEBUG, "PORT Closed."); 335 | } 336 | } else { 337 | privateVariable.setInterrupted(true); 338 | } 339 | } 340 | 341 | public void hello(String message) { 342 | listening = message; 343 | } 344 | 345 | public void hello(File file) { 346 | this.file = file; 347 | } 348 | 349 | public void helloSTOR(String path) { 350 | this.path = path; 351 | } 352 | 353 | public void stopSocket() { 354 | try { 355 | socket.shutdownInput(); 356 | socket.shutdownOutput(); 357 | socket.close(); 358 | Logger.log(Types.SYS, Levels.DEBUG, "Socket on " + socket.getLocalSocketAddress() + "stopped."); 359 | } catch (IOException IOE) { 360 | IOE.printStackTrace(); 361 | } catch (NullPointerException NPE) { 362 | Logger.log(Types.SYS, Levels.WARN, "Latest port mode port not connected. Closing forced."); 363 | } 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/pool/handler/HandlerPool.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.pool.handler; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | 6 | /** 7 | *

LiteFTPD-UNIX

8 | *

Public class to store thread pools.

9 | * 10 | * @author : https://github.com/AdlerED 11 | * @date : 2019-09-19 09:21 12 | **/ 13 | public class HandlerPool { 14 | public static ExecutorService handlerPool = Executors.newCachedThreadPool(); 15 | } 16 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/tool/AutoInputStream.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.tool; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

AutoInputStream can analyze Chinese encoding from InputStream, and print it out correctly.

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-09-19 09:21 9 | **/ 10 | 11 | import pers.adlered.liteftpd.analyze.PrivateVariable; 12 | import pers.adlered.liteftpd.logger.enums.Levels; 13 | import pers.adlered.liteftpd.logger.Logger; 14 | import pers.adlered.liteftpd.logger.enums.Types; 15 | 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.nio.charset.StandardCharsets; 19 | 20 | public class AutoInputStream { 21 | private InputStream inputStream = null; 22 | private int cacheSize = 0; 23 | private PrivateVariable privateVariable = null; 24 | 25 | public AutoInputStream(InputStream inputStream, int cacheSize, PrivateVariable privateVariable) { 26 | this.inputStream = inputStream; 27 | this.cacheSize = cacheSize; 28 | this.privateVariable = privateVariable; 29 | } 30 | 31 | public String readLineAuto() throws IOException { 32 | byte[] storage = new byte[cacheSize]; 33 | int c2Cursor = 0; 34 | while (true) { 35 | int available = 0; 36 | try { 37 | while (available == 0) { 38 | available = inputStream.available(); 39 | Thread.sleep(5); 40 | } 41 | } catch (IOException IOE) { 42 | Logger.log(Types.SEND, Levels.WARN, "Auto Input Stream stopped."); 43 | break; 44 | } catch (InterruptedException IE) { 45 | } 46 | byte[] cache = new byte[available]; 47 | inputStream.read(cache); 48 | int cursor = 0; 49 | int relativeLength = c2Cursor + cache.length; 50 | if (relativeLength <= cacheSize) { 51 | for (int i = c2Cursor; i < relativeLength; i++) { 52 | storage[i] = cache[cursor]; 53 | ++cursor; 54 | ++c2Cursor; 55 | } 56 | // Check if space exists, break it. 57 | if (new String(storage, StandardCharsets.UTF_8).indexOf("\n") != -1) { 58 | break; 59 | } 60 | } else { 61 | break; 62 | } 63 | } 64 | // Clean wasted space 65 | int storageCursor = 0; 66 | int firstEmptyMark = -1; 67 | boolean marked = false; 68 | for (byte i : storage) { 69 | if (i == 0) { 70 | if (!marked) { 71 | marked = true; 72 | firstEmptyMark = storageCursor; 73 | } 74 | } 75 | ++storageCursor; 76 | } 77 | if (firstEmptyMark == -1) firstEmptyMark = storage.length; 78 | // Init a fit size bytes. 79 | byte[] cleaned = new byte[firstEmptyMark]; 80 | for (int i = 0; i < firstEmptyMark; i++) { 81 | cleaned[i] = storage[i]; 82 | } 83 | String UTF8 = new String(cleaned, StandardCharsets.UTF_8); 84 | String GB2312 = new String(cleaned, "GB2312"); 85 | String charset = CharsetSelector.getCharset(cleaned); 86 | if (!privateVariable.isEncodeLock()) { 87 | privateVariable.setEncode(charset); 88 | if (charset.equals("GB2312")) { 89 | privateVariable.setEncodeLock(true); 90 | } 91 | } 92 | String bestMatch = new String(cleaned, charset); 93 | return bestMatch; 94 | } 95 | 96 | public String readLineInUTF8() throws IOException { 97 | return ""; 98 | } 99 | 100 | public String readLineInGB2312() throws IOException { 101 | return ""; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/tool/CharsetSelector.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.tool; 2 | 3 | import pers.adlered.liteftpd.logger.enums.Levels; 4 | import pers.adlered.liteftpd.logger.Logger; 5 | import pers.adlered.liteftpd.logger.enums.Types; 6 | import pers.adlered.liteftpd.variable.Variable; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | import java.nio.charset.StandardCharsets; 10 | 11 | /** 12 | *

LiteFTPD-UNIX

13 | *

Analyze string's encode.

14 | * 15 | * @author : https://github.com/AdlerED 16 | * @date : 2019-09-19 09:21 17 | **/ 18 | public class CharsetSelector { 19 | public static String getCharset(byte[] bytes) { 20 | // return guessEncoding(bytes); 21 | int UTF8ERR = 0; 22 | int GB2312ERR = 0; 23 | String UTF8 = null; 24 | String GB2312 = null; 25 | try { 26 | UTF8 = new String(bytes, StandardCharsets.UTF_8); 27 | GB2312 = new String(bytes, "GB2312"); 28 | } catch (UnsupportedEncodingException UEE) { 29 | // TODO 30 | UEE.printStackTrace(); 31 | } 32 | if (UTF8 != null) { 33 | int fromIndex = 0; 34 | int count = 0; 35 | while (true) { 36 | int index = UTF8.indexOf("�", fromIndex); 37 | if (-1 != index) { 38 | fromIndex = index + 1; 39 | count++; 40 | } else { 41 | break; 42 | } 43 | } 44 | while (true) { 45 | int index = UTF8.indexOf("ţ", fromIndex); 46 | if (-1 != index) { 47 | fromIndex = index + 1; 48 | count++; 49 | } else { 50 | break; 51 | } 52 | } 53 | while (true) { 54 | int index = UTF8.indexOf("Ƥ", fromIndex); 55 | if (-1 != index) { 56 | fromIndex = index + 1; 57 | count++; 58 | } else { 59 | break; 60 | } 61 | } 62 | UTF8ERR = count; 63 | Logger.log(Types.SYS, Levels.DEBUG, "UTF8 " + count); 64 | } 65 | if (GB2312 != null) { 66 | int fromIndex = 0; 67 | int count = 0; 68 | while (true) { 69 | int index = GB2312.indexOf("�", fromIndex); 70 | if (-1 != index) { 71 | fromIndex = index + 1; 72 | count++; 73 | } else { 74 | break; 75 | } 76 | } 77 | GB2312ERR = count; 78 | Logger.log(Types.SYS, Levels.DEBUG, "GB2312 " + count); 79 | } 80 | if (UTF8ERR < GB2312ERR) { 81 | return "UTF-8"; 82 | } else if (UTF8ERR > GB2312ERR) { 83 | return "GB2312"; 84 | } else { 85 | return Variable.defaultEncode; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/tool/ConsoleTable.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.tool; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | *

LiteFTPD-UNIX

8 | *

生成控制台表格

9 | * 10 | * @author : https://github.com/AdlerED 11 | * @date : 2019-10-04 18:06 12 | **/ 13 | public class ConsoleTable { 14 | private static int margin = 2; 15 | private List rows = new ArrayList(); 16 | private int colum; 17 | private int[] columLen; 18 | private boolean printHeader = false; 19 | 20 | public ConsoleTable(int colum, boolean printHeader) { 21 | this.printHeader = printHeader; 22 | this.colum = colum; 23 | this.columLen = new int[colum]; 24 | } 25 | 26 | public void appendRow() { 27 | List row = new ArrayList(colum); 28 | rows.add(row); 29 | } 30 | 31 | public ConsoleTable appendColum(Object value) { 32 | if (value == null) { 33 | value = "NULL"; 34 | } 35 | List row = rows.get(rows.size() - 1); 36 | row.add(value); 37 | int len = value.toString().getBytes().length; 38 | if (columLen[row.size() - 1] < len) 39 | columLen[row.size() - 1] = len; 40 | return this; 41 | } 42 | 43 | public String toString() { 44 | StringBuilder buf = new StringBuilder(); 45 | 46 | int sumlen = 0; 47 | for (int len : columLen) { 48 | sumlen += len; 49 | } 50 | if (printHeader) 51 | buf.append("|").append(printChar('=', sumlen + margin * 2 * colum + (colum - 1))).append("|\n"); 52 | else 53 | buf.append("|").append(printChar('-', sumlen + margin * 2 * colum + (colum - 1))).append("|\n"); 54 | for (int ii = 0; ii < rows.size(); ii++) { 55 | List row = rows.get(ii); 56 | for (int i = 0; i < colum; i++) { 57 | String o = ""; 58 | if (i < row.size()) 59 | o = row.get(i).toString(); 60 | buf.append('|').append(printChar(' ', margin)).append(o); 61 | buf.append(printChar(' ', columLen[i] - o.getBytes().length + margin)); 62 | } 63 | buf.append("|\n"); 64 | if (printHeader && ii == 0) 65 | buf.append("|").append(printChar('=', sumlen + margin * 2 * colum + (colum - 1))).append("|\n"); 66 | else 67 | buf.append("|").append(printChar('-', sumlen + margin * 2 * colum + (colum - 1))).append("|\n"); 68 | } 69 | return buf.toString(); 70 | } 71 | 72 | private String printChar(char c, int len) { 73 | StringBuilder buf = new StringBuilder(); 74 | for (int i = 0; i < len; i++) { 75 | buf.append(c); 76 | } 77 | return buf.toString(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/tool/GoodXX.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.tool; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /** 7 | *

LiteFTPD-UNIX

8 | *

Greetings for users.

9 | * 10 | * @author : https://github.com/AdlerED 11 | * @date : 2019-09-19 09:21 12 | **/ 13 | public class GoodXX { 14 | public static String getTimeAsWord() { 15 | Date date = new Date(); 16 | SimpleDateFormat df = new SimpleDateFormat("HH"); 17 | String str = df.format(date); 18 | int a = Integer.parseInt(str); 19 | if (a >= 0 && a <= 12) { 20 | return "morning"; 21 | } 22 | if (a > 12 && a <= 13) { 23 | return "noon"; 24 | } 25 | if (a > 13 && a <= 17) { 26 | return "afternoon"; 27 | } 28 | if (a > 17 && a <= 19) { 29 | return "evening"; 30 | } 31 | if (a > 19 && a <= 24) { 32 | return "night"; 33 | } 34 | return ""; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/tool/LocalAddress.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.tool; 2 | 3 | import java.net.Inet4Address; 4 | import java.net.InetAddress; 5 | import java.net.NetworkInterface; 6 | import java.net.SocketException; 7 | import java.util.ArrayList; 8 | import java.util.Enumeration; 9 | import java.util.List; 10 | 11 | /** 12 | *

LiteFTPD-UNIX

13 | *

Get IP Address of the server.

14 | * 15 | * @author : https://github.com/AdlerED 16 | * @date : 2019-09-19 09:21 17 | **/ 18 | public class LocalAddress { 19 | public static List getLocalIPList() { 20 | List ipList = new ArrayList(); 21 | try { 22 | Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); 23 | NetworkInterface networkInterface; 24 | Enumeration inetAddresses; 25 | InetAddress inetAddress; 26 | String ip; 27 | while (networkInterfaces.hasMoreElements()) { 28 | networkInterface = networkInterfaces.nextElement(); 29 | inetAddresses = networkInterface.getInetAddresses(); 30 | while (inetAddresses.hasMoreElements()) { 31 | inetAddress = inetAddresses.nextElement(); 32 | if (inetAddress != null && inetAddress instanceof Inet4Address) { 33 | ip = inetAddress.getHostAddress(); 34 | ipList.add(ip); 35 | } 36 | } 37 | } 38 | } catch (SocketException e) { 39 | e.printStackTrace(); 40 | } 41 | return ipList; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/tool/RandomNum.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.tool; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | *

LiteFTPD-UNIX

7 | *

Sum random num.

8 | * 9 | * @author : https://github.com/AdlerED 10 | * @date : 2019-09-19 09:21 11 | **/ 12 | public class RandomNum { 13 | public static boolean debugMode = false; //为true 控制台会显示计算过程输出(影响取随机数性能) 14 | 15 | public static int sumIntger(int min, int max, boolean needNegative) { 16 | int result; 17 | Random random = new Random(); 18 | int sumNum = random.nextInt(max - min + 1) + min; 19 | if (needNegative == true) { 20 | int temp = random.nextInt(10) + 1; 21 | Int2String i2s = new Int2String(0); 22 | i2s.setInt(sumNum); 23 | String randomArg = i2s.returnString(); 24 | switch (temp) { 25 | case 1: 26 | if ((randomArg.contains("0")) || (randomArg.contains("9"))) { 27 | randomArg = "-" + randomArg; 28 | break; 29 | } 30 | case 2: 31 | if ((randomArg.contains("1")) || (randomArg.contains("8"))) { 32 | randomArg = "-" + randomArg; 33 | break; 34 | } 35 | case 3: 36 | if ((randomArg.contains("2")) || (randomArg.contains("7"))) { 37 | randomArg = "-" + randomArg; 38 | break; 39 | } 40 | case 4: 41 | if ((randomArg.contains("3")) || (randomArg.contains("6"))) { 42 | randomArg = "-" + randomArg; 43 | break; 44 | } 45 | case 5: 46 | if ((randomArg.contains("4")) || (randomArg.contains("5"))) { 47 | randomArg = "-" + randomArg; 48 | break; 49 | } 50 | } 51 | sumNum = Integer.parseInt(randomArg); 52 | result = sumNum; 53 | return result; 54 | } 55 | return sumNum; 56 | } 57 | 58 | public static double sumDecimal(double min, double max, boolean needNegative) { 59 | double result = 0; 60 | int minInt = (int) min; 61 | if (debugMode == true) { 62 | System.out.println("(最低) 整数位 = " + minInt); 63 | } 64 | String temp = min + ""; 65 | String temp2 = temp.substring(temp.indexOf(".")); 66 | String temp3 = temp2.replace(".", ""); //提取出小数位 67 | if (debugMode == true) { 68 | System.out.println("(最低) 小数位 = " + temp3); 69 | System.out.println("最低小数位长度: " + temp3.length()); 70 | } 71 | int maxInt = (int) max; 72 | if (debugMode == true) { 73 | System.out.println("(最高) 整数位: " + maxInt); 74 | } 75 | String decTemp = max + ""; 76 | String decTemp2 = decTemp.substring(temp.indexOf(".")); 77 | String decTemp3 = decTemp2.replace(".", ""); 78 | if (debugMode == true) { 79 | System.out.println("(最高) 小数位: " + decTemp3); 80 | System.out.println("最高小数位长度: " + decTemp3.length()); 81 | } 82 | // String结果转int 83 | int minDecimalResult; 84 | int maxDecimalResult; 85 | minDecimalResult = Integer.valueOf(temp3).intValue(); 86 | maxDecimalResult = Integer.valueOf(decTemp3).intValue(); 87 | if (debugMode == true) { 88 | System.out.println("转换后小数范围: " + minDecimalResult + " " + maxDecimalResult); 89 | } 90 | // 先生成一个整数位范围内的数字 91 | // 再生成一个小数位范围内的数字 92 | // 最后判断是否符合范围要求, 如果符合 返回 如果不符合 重来 93 | boolean isOk = false; //do while会检查isOk 如果不符合条件(isOk = true)会重来 94 | do { 95 | // 整数数字生成 96 | int getInt = sumIntger(minInt, maxInt, false); 97 | if (debugMode == true) { 98 | System.out.println("整数数字已生成: " + getInt); 99 | } 100 | // 小数数字生成 随机就好 注意小数位数 101 | // 其实不用看最低小数,取0-最高小数就可以了 102 | int getDec = 0; 103 | if (minInt == maxInt) { 104 | if (minDecimalResult < maxDecimalResult) 105 | getDec = sumIntger(minDecimalResult, maxDecimalResult, false); 106 | if (minDecimalResult > maxDecimalResult) 107 | getDec = sumIntger(maxDecimalResult, minDecimalResult, false); 108 | } else { //瞎猫碰上死耗子 109 | if (minDecimalResult < maxDecimalResult) 110 | getDec = sumIntger(0, maxDecimalResult, false); 111 | if (minDecimalResult > maxDecimalResult) 112 | getDec = sumIntger(0, minDecimalResult, false); 113 | } 114 | if (debugMode == true) { 115 | System.out.println("小数数字已生成: " + getDec); 116 | } 117 | // 开始组合 118 | String spell = getInt + "." + getDec; 119 | double getSpell = Double.valueOf(spell); 120 | if (debugMode == true) { 121 | System.out.println("随机数已生成: " + getSpell); 122 | } 123 | // if (getDec >= minDecimalResult && getDec <= maxDecimalResult) { 124 | if (minInt != maxInt) { //算法不同 不相为谋 125 | if (getInt == minInt && getDec >= minDecimalResult) { 126 | isOk = true; 127 | } else if (getInt == maxInt && getDec <= maxDecimalResult) { 128 | isOk = true; 129 | } else if (getInt != minInt && getInt != maxInt) { 130 | isOk = true; 131 | } 132 | } else { 133 | if (getDec >= minDecimalResult && getDec <= maxDecimalResult) { 134 | isOk = true; 135 | } 136 | } 137 | // 如果整数不是最小也不是最大 那小数位就可以随便生成了 138 | if (isOk == false) { 139 | if (debugMode == true) { 140 | System.out.println("随机生成的随机数不符合要求,自动重新生成..."); 141 | } 142 | } else { 143 | result = getSpell; 144 | } 145 | } while (isOk == false); 146 | // 利用判断 看看组合后的小数是否在设定的范围之内(有点影响性能...不过可以实现) 147 | if (needNegative == true) { 148 | Random random = new Random(); 149 | int tempR = random.nextInt(10) + 1; 150 | Double2String d2s = new Double2String(result); 151 | String randomArg = d2s.returnString(); 152 | switch (tempR) { 153 | case 1: 154 | if ((randomArg.contains("0")) || (randomArg.contains("9"))) { 155 | randomArg = "-" + randomArg; 156 | break; 157 | } 158 | case 2: 159 | if ((randomArg.contains("1")) || (randomArg.contains("8"))) { 160 | randomArg = "-" + randomArg; 161 | break; 162 | } 163 | case 3: 164 | if ((randomArg.contains("2")) || (randomArg.contains("7"))) { 165 | randomArg = "-" + randomArg; 166 | break; 167 | } 168 | case 4: 169 | if ((randomArg.contains("3")) || (randomArg.contains("6"))) { 170 | randomArg = "-" + randomArg; 171 | break; 172 | } 173 | case 5: 174 | if ((randomArg.contains("4")) || (randomArg.contains("5"))) { 175 | randomArg = "-" + randomArg; 176 | break; 177 | } 178 | } 179 | double getRes = Double.valueOf(randomArg); 180 | result = getRes; 181 | } 182 | if (debugMode == true) { 183 | System.out.println("最终取数: " + result); 184 | } 185 | return result; 186 | } 187 | 188 | static class Int2String { 189 | String ArgNum = ""; 190 | 191 | public Int2String(int RecivedNum) { 192 | ArgNum = RecivedNum + ""; 193 | } 194 | 195 | String returnString() { 196 | return ArgNum; 197 | } 198 | 199 | void setInt(int RecivedNum) { 200 | ArgNum = RecivedNum + ""; 201 | } 202 | } 203 | 204 | static class Double2String { 205 | String ArgNum = ""; 206 | 207 | public Double2String(double RecivedNum) { 208 | ArgNum = RecivedNum + ""; 209 | } 210 | 211 | String returnString() { 212 | return ArgNum; 213 | } 214 | 215 | void setDouble(double RecivedNum) { 216 | ArgNum = RecivedNum + ""; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/tool/Status.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.tool; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.lang.management.MemoryMXBean; 5 | import java.lang.management.MemoryUsage; 6 | 7 | /** 8 | *

LiteFTPD-UNIX

9 | *

Get information of the server.

10 | * 11 | * @author : https://github.com/AdlerED 12 | * @date : 2019-09-19 09:21 13 | **/ 14 | public class Status { 15 | public static String memoryUsed() { 16 | MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); 17 | MemoryUsage memoryUsage = memoryMXBean.getHeapMemoryUsage(); 18 | return (memoryUsage.getUsed() / 1048576) + "MB"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/User.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user; 2 | 3 | import pers.adlered.liteftpd.variable.Variable; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | *

LiteFTPD-UNIX

10 | *

Store users.

11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-09-19 09:21 14 | **/ 15 | public class User { 16 | private static Map users = null; 17 | 18 | // 读取用户配置,并写入到users中。启动时必须配置! 19 | public static void initUsers() { 20 | users = new HashMap<>(); 21 | String[] userList = Variable.user.split(";"); 22 | for (int i = 0; i < userList.length; i += 5) { 23 | UserProps userProps = new UserProps(); 24 | userProps.setPassword(userList[i + 1]); 25 | userProps.setPermission(userList[i + 2]); 26 | userProps.setPermitDir(userList[i + 3]); 27 | userProps.setDefaultDir(userList[i + 4]); 28 | users.put(userList[i], userProps); 29 | } 30 | } 31 | 32 | public static boolean checkPassword(String username, String password) { 33 | try { 34 | if (users.get(username).getPassword().equals(password)) { 35 | return true; 36 | } 37 | } catch (NullPointerException NPE) { 38 | return false; 39 | } 40 | return false; 41 | } 42 | 43 | public static UserProps getUserProps(String username) { 44 | return users.get(username); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/UserProps.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

用户信息

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-10-04 19:16 9 | **/ 10 | public class UserProps { 11 | private String password; 12 | private String permission; 13 | private String permitDir; 14 | private String defaultDir; 15 | 16 | public String getPassword() { 17 | return password; 18 | } 19 | 20 | public void setPassword(String password) { 21 | this.password = password; 22 | } 23 | 24 | public String getPermission() { 25 | return permission; 26 | } 27 | 28 | public void setPermission(String permission) { 29 | this.permission = permission; 30 | } 31 | 32 | public String getPermitDir() { 33 | return permitDir; 34 | } 35 | 36 | public void setPermitDir(String permitDir) { 37 | this.permitDir = permitDir; 38 | } 39 | 40 | public String getDefaultDir() { 41 | return defaultDir; 42 | } 43 | 44 | public void setDefaultDir(String defaultDir) { 45 | this.defaultDir = defaultDir; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/info/OnlineInfo.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.info; 2 | 3 | import pers.adlered.liteftpd.user.info.bind.UserInfoBind; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | *

LiteFTPD-UNIX

10 | *

存储所有在线用户详细信息的数组

11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-10-05 21:47 14 | **/ 15 | public class OnlineInfo { 16 | public static final List usersOnlineInfo = new ArrayList<>(); 17 | } 18 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/info/bind/UserInfoBind.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.info.bind; 2 | 3 | import pers.adlered.liteftpd.user.status.bind.IpLimitBind; 4 | import pers.adlered.liteftpd.user.status.bind.UserLimitBind; 5 | 6 | /** 7 | *

LiteFTPD-UNIX

8 | *

存储在线用户详细信息

9 | * 10 | * @author : https://github.com/AdlerED 11 | * @date : 2019-10-05 21:41 12 | **/ 13 | public class UserInfoBind { 14 | private IpLimitBind ipLimitBind; 15 | private UserLimitBind userLimitBind; 16 | 17 | public UserInfoBind(IpLimitBind ipLimitBind, UserLimitBind userLimitBind) { 18 | this.ipLimitBind = ipLimitBind; 19 | this.userLimitBind = userLimitBind; 20 | } 21 | 22 | public IpLimitBind getIpLimitBind() { 23 | return ipLimitBind; 24 | } 25 | 26 | public void setIpLimitBind(IpLimitBind ipLimitBind) { 27 | this.ipLimitBind = ipLimitBind; 28 | } 29 | 30 | public UserLimitBind getUserLimitBind() { 31 | return userLimitBind; 32 | } 33 | 34 | public void setUserLimitBind(UserLimitBind userLimitBind) { 35 | this.userLimitBind = userLimitBind; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/status/Online.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.status; 2 | 3 | import pers.adlered.liteftpd.user.status.bind.IpLimitBind; 4 | import pers.adlered.liteftpd.user.status.bind.UserLimitBind; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | *

LiteFTPD-UNIX

11 | *

记录在线的用户信息,用于限制连接数和读取信息。

12 | * 13 | * @author : https://github.com/AdlerED 14 | * @date : 2019-10-05 14:51 15 | **/ 16 | public class Online { 17 | // 存储指定规则的在线IP数量 18 | public static final List ipRuleOnline = new ArrayList<>(); 19 | // 存储制定规则的在线用户数量 20 | public static final List userRuleOnline = new ArrayList<>(); 21 | } 22 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/status/bind/IPAddressBind.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.status.bind; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

A bind of server/client IP Address.

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-09-19 09:21 9 | **/ 10 | public class IPAddressBind { 11 | private String IPADD; 12 | private String SRVIPADD; 13 | 14 | public IPAddressBind(String IPADD, String SRVIPADD) { 15 | this.IPADD = IPADD; 16 | this.SRVIPADD = SRVIPADD; 17 | } 18 | 19 | public String getIPADD() { 20 | return IPADD; 21 | } 22 | 23 | public void setIPADD(String IPADD) { 24 | this.IPADD = IPADD; 25 | } 26 | 27 | public String getSRVIPADD() { 28 | return SRVIPADD; 29 | } 30 | 31 | public void setSRVIPADD(String SRVIPADD) { 32 | this.SRVIPADD = SRVIPADD; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/status/bind/IpLimitBind.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.status.bind; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

IP地址限制

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-10-05 15:14 9 | **/ 10 | public class IpLimitBind { 11 | String ip; 12 | String rule; 13 | 14 | public IpLimitBind(String ip, String rule) { 15 | this.ip = ip; 16 | this.rule = rule; 17 | } 18 | 19 | public String getIp() { 20 | return ip; 21 | } 22 | 23 | public void setIp(String ip) { 24 | this.ip = ip; 25 | } 26 | 27 | public String getRule() { 28 | return rule; 29 | } 30 | 31 | public void setRule(String rule) { 32 | this.rule = rule; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/status/bind/SpeedLimitBind.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.status.bind; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

限速Bind

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-10-12 16:12 9 | **/ 10 | public class SpeedLimitBind { 11 | private int uploadSpeed; 12 | private int downloadSpeed; 13 | 14 | public SpeedLimitBind(int uploadSpeed, int downloadSpeed) { 15 | this.uploadSpeed = uploadSpeed; 16 | this.downloadSpeed = downloadSpeed; 17 | } 18 | 19 | public int getUploadSpeed() { 20 | return uploadSpeed; 21 | } 22 | 23 | public void setUploadSpeed(int uploadSpeed) { 24 | this.uploadSpeed = uploadSpeed; 25 | } 26 | 27 | public int getDownloadSpeed() { 28 | return downloadSpeed; 29 | } 30 | 31 | public void setDownloadSpeed(int downloadSpeed) { 32 | this.downloadSpeed = downloadSpeed; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/status/bind/UserLimitBind.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.status.bind; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

用户限制

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-10-05 21:02 9 | **/ 10 | public class UserLimitBind { 11 | String username; 12 | String rule; 13 | 14 | public UserLimitBind(String username, String rule) { 15 | this.username = username; 16 | this.rule = rule; 17 | } 18 | 19 | public String getUsername() { 20 | return username; 21 | } 22 | 23 | public void setUsername(String username) { 24 | this.username = username; 25 | } 26 | 27 | public String getRule() { 28 | return rule; 29 | } 30 | 31 | public void setRule(String rule) { 32 | this.rule = rule; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/user/verify/OnlineRules.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.user.verify; 2 | 3 | import pers.adlered.liteftpd.user.status.bind.IpLimitBind; 4 | import pers.adlered.liteftpd.user.status.Online; 5 | import pers.adlered.liteftpd.user.status.bind.SpeedLimitBind; 6 | import pers.adlered.liteftpd.user.status.bind.UserLimitBind; 7 | import pers.adlered.liteftpd.variable.Variable; 8 | 9 | /** 10 | *

LiteFTPD-UNIX

11 | *

检测连接数限制,返回True或者False决定是否接受连接。

12 | * 13 | * @author : https://github.com/AdlerED 14 | * @date : 2019-10-05 14:49 15 | **/ 16 | public class OnlineRules { 17 | /** 18 | * 检查IP地址是否可以登录 19 | * 20 | * @param ipAddress 21 | * @return 返回生成后的IP Bind,用于进一步获取登录用户信息 22 | */ 23 | public static IpLimitBind checkIpAddress(String ipAddress) { 24 | boolean onList = false; 25 | String onListRule = ""; 26 | int onListLimit = -1; 27 | String[] ipRules = Variable.ipOnlineLimit.split(";"); 28 | String[] ip = ipAddress.split("\\."); 29 | for (int i = 0; i < ipRules.length; i += 2) { 30 | if (ipRules[i].equals("0.0.0.0")) { 31 | onList = true; 32 | onListRule = ipRules[i]; 33 | onListLimit = Integer.parseInt(ipRules[i + 1]); 34 | } 35 | String[] ruleIp = ipRules[i].split("\\."); 36 | // 首先,第一位需要相等 37 | if (ruleIp[0].equals(ip[0]) || ruleIp[0].toLowerCase().equals("x")) { 38 | if (ruleIp[1].equals(ip[1]) || ruleIp[1].toLowerCase().equals("x")) { 39 | if (ruleIp[2].equals(ip[2]) || ruleIp[2].toLowerCase().equals("x")) { 40 | if (ruleIp[3].equals(ip[3]) || ruleIp[3].toLowerCase().equals("x")) { 41 | onList = true; 42 | onListRule = ipRules[i]; 43 | onListLimit = Integer.parseInt(ipRules[i + 1]); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | if (onList) { 50 | if (onListLimit == 0) { 51 | return new IpLimitBind(null, null); 52 | } else { 53 | int count = 0; 54 | for (IpLimitBind ipRule : Online.ipRuleOnline) { 55 | if (ipRule.getRule().equals(onListRule)) { 56 | ++count; 57 | } 58 | } 59 | if (count < onListLimit) { 60 | IpLimitBind ipLimitBind = new IpLimitBind(ipAddress, onListRule); 61 | Online.ipRuleOnline.add(ipLimitBind); 62 | return ipLimitBind; 63 | } else { 64 | return new IpLimitBind(null, null); 65 | } 66 | } 67 | } else { 68 | IpLimitBind ipLimitBind = new IpLimitBind(ipAddress, onListRule); 69 | Online.ipRuleOnline.add(ipLimitBind); 70 | return ipLimitBind; 71 | } 72 | } 73 | 74 | /** 75 | * 检查用户名是否达到了上限 76 | * 77 | * @param username 78 | * @return 禁止登录返回null 79 | */ 80 | public static UserLimitBind checkUsername(String username) { 81 | boolean onList = false; 82 | String onListRule = ""; 83 | int onListLimit = -1; 84 | String[] userRules = Variable.userOnlineLimit.split(";"); 85 | // 先检测,再执行,通配符优先级最高 86 | for (int i = 0; i < userRules.length; i += 2) { 87 | if (userRules[i].equals("%")) { 88 | onList = true; 89 | onListRule = userRules[i]; 90 | onListLimit = Integer.parseInt(userRules[i + 1]); 91 | } 92 | if (userRules[i].equals(username)) { 93 | onList = true; 94 | onListRule = userRules[i]; 95 | onListLimit = Integer.parseInt(userRules[i + 1]); 96 | } 97 | } 98 | if (onList) { 99 | if (onListLimit == 0) { 100 | return new UserLimitBind(null, null); 101 | } else { 102 | int count = 0; 103 | for (UserLimitBind userRule : Online.userRuleOnline) { 104 | if (userRule.getRule().equals(onListRule)) { 105 | ++count; 106 | } 107 | } 108 | if (count < onListLimit) { 109 | UserLimitBind userLimitBind = new UserLimitBind(username, onListRule); 110 | Online.userRuleOnline.add(userLimitBind); 111 | return userLimitBind; 112 | } else { 113 | return new UserLimitBind(null, null); 114 | } 115 | } 116 | } else { 117 | UserLimitBind userLimitBind = new UserLimitBind(username, onListRule); 118 | Online.userRuleOnline.add(userLimitBind); 119 | return userLimitBind; 120 | } 121 | } 122 | 123 | public static SpeedLimitBind getSpeedLimit(String username) { 124 | String[] speedRules = Variable.speedLimit.split(";"); 125 | for (int i = 0; i < speedRules.length; i += 3) { 126 | String currentUser = speedRules[i]; 127 | if (currentUser.equals(username)) { 128 | int uploadSpeed; 129 | if (speedRules[i + 1].isEmpty() || speedRules[i + 1].equals("0")) { 130 | uploadSpeed = 0; 131 | } else { 132 | uploadSpeed = Integer.parseInt(speedRules[i + 1]); 133 | } 134 | int downloadSpeed; 135 | if (speedRules[i + 2].isEmpty() || speedRules[i + 2].equals("0")) { 136 | downloadSpeed = 0; 137 | } else { 138 | downloadSpeed = Integer.parseInt(speedRules[i + 2]); 139 | } 140 | return new SpeedLimitBind(uploadSpeed, downloadSpeed); 141 | } 142 | } 143 | return new SpeedLimitBind(0, 0); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/variable/OnlineUserController.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.variable; 2 | 3 | import pers.adlered.liteftpd.logger.enums.Levels; 4 | import pers.adlered.liteftpd.logger.Logger; 5 | import pers.adlered.liteftpd.logger.enums.Types; 6 | import pers.adlered.liteftpd.user.status.Online; 7 | 8 | /** 9 | *

LiteFTPD-UNIX

10 | *

Variable bean.

11 | * 12 | * @author : https://github.com/AdlerED 13 | * @date : 2019-09-19 09:21 14 | **/ 15 | public class OnlineUserController { 16 | public static void printOnline() { 17 | int ipSize = Online.ipRuleOnline.size(); 18 | int userSize = Online.userRuleOnline.size(); 19 | Logger.log(Types.SYS, Levels.INFO, "Online Users: " + userSize + " Connections: " + ipSize); 20 | } 21 | 22 | public static void reduceOnline(String ipAddress, String username) { ; 23 | for (int i = 0; i < Online.ipRuleOnline.size(); i++) { 24 | if (Online.ipRuleOnline.get(i).getIp().equals(ipAddress)) { 25 | Online.ipRuleOnline.remove(i); 26 | break; 27 | } 28 | } 29 | for (int i = 0; i < Online.userRuleOnline.size(); i++) { 30 | if (Online.userRuleOnline.get(i).getUsername().equals(username)) { 31 | Online.userRuleOnline.remove(i); 32 | break; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/variable/Variable.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.variable; 2 | 3 | /** 4 | *

LiteFTPD-UNIX

5 | *

User interface settings.

6 | * 7 | * @author : https://github.com/AdlerED 8 | * @date : 2019-09-19 09:21 9 | **/ 10 | public class Variable { 11 | public static String user = "1;anonymous;;r;2;admin;123456;r"; 12 | public static String ipOnlineLimit = ""; 13 | public static String userOnlineLimit = ""; 14 | public static String speedLimit=""; 15 | public static int debugLevel = 4; 16 | public static int online = 0; 17 | public static long maxUserLimit = 100; 18 | public static int timeout = 100; 19 | public static int maxTimeout = 21600; 20 | public static boolean smartEncode = true; 21 | public static String defaultEncode = "UTF-8"; 22 | public static int port = 21; 23 | public static String welcomeMessage = ""; 24 | public static int minPort = 10240; 25 | public static int maxPort = 20480; 26 | } 27 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/wizard/config/Prop.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.wizard.config; 2 | 3 | import pers.adlered.liteftpd.logger.enums.Levels; 4 | import pers.adlered.liteftpd.logger.Logger; 5 | import pers.adlered.liteftpd.logger.enums.Types; 6 | import pers.adlered.liteftpd.variable.Variable; 7 | 8 | import java.io.*; 9 | import java.lang.reflect.Field; 10 | import java.util.*; 11 | 12 | /** 13 | *

LiteFTPD-UNIX

14 | *

配置文件读写操作类

15 | * 16 | * @author : https://github.com/AdlerED 17 | * @date : 2019-10-03 23:45 18 | **/ 19 | public class Prop { 20 | private static Properties properties = new Properties(); 21 | 22 | private static Prop prop = null; 23 | 24 | private Prop() { 25 | try { 26 | BufferedReader bufferedReader = new BufferedReader(new FileReader("config.prop")); 27 | properties.load(bufferedReader); 28 | Logger.log(Types.SYS, Levels.INFO, "Profile \"config.prop\" loaded successfully."); 29 | } catch (FileNotFoundException FNFE) { 30 | Logger.log(Types.SYS, Levels.WARN, "Cannot found properties file \"config.prop\" at the root path, re-generating default..."); 31 | try { 32 | File file = new File("config.prop"); 33 | file.createNewFile(); 34 | // Set default props 35 | addAnnotation("# ================================================================================================") 36 | .addAnnotation("# ================================================================================================") 37 | .addAnnotation("# >>> LiteFTPD-UNIX Configure File") 38 | .addAnnotation("# ") 39 | .addAnnotation("# >> debugLevel") 40 | .addAnnotation("# ") 41 | .addAnnotation("# Too high level can affect performance!") 42 | .addAnnotation("# 0: NONE;") 43 | .addAnnotation("# 1: INFO;") 44 | .addAnnotation("# 2: WARN && INFO;") 45 | .addAnnotation("# 3: ERROR && WARN && INFO;") 46 | .addAnnotation("# 4: DEBUG && ERROR && WARN && INFO.") 47 | .addAnnotation("# ") 48 | .addAnnotation("# Debug等级,调整过高可能会影响性能!") 49 | .addAnnotation("# 0:无输出;") 50 | .addAnnotation("# 1:输出 INFO 信息;") 51 | .addAnnotation("# 2:输出 WARN 及 INFO 信息;") 52 | .addAnnotation("# 3:输出 ERROR 、 WARN 及 INFO 信息;") 53 | .addAnnotation("# 4:输出 DEBUG 、 ERROR 、 WARN 及 INFO 信息。") 54 | .addAnnotation("# ") 55 | .addAnnotation("# >> maxUserLimit") 56 | .addAnnotation("# ") 57 | .addAnnotation("# Set to 0, will be ignore the limit. Too small value may make multi-thread ftp client not working.") 58 | .addAnnotation("# ") 59 | .addAnnotation("# 同时连接数限制。设置至0代表不限制。过小的值可能会导致多线程的FTP客户端无法正常工作。") 60 | .addAnnotation("# ") 61 | .addAnnotation("# >> timeout") 62 | .addAnnotation("# ") 63 | .addAnnotation("# Timeout in second.") 64 | .addAnnotation("# ") 65 | .addAnnotation("# 连接空闲超时时间。") 66 | .addAnnotation("# ") 67 | .addAnnotation("# >> maxTimeout") 68 | .addAnnotation("# ") 69 | .addAnnotation("# On mode timeout when client is on passive or initiative mode. (default: 21600 sec = 6 hrs)") 70 | .addAnnotation("# ") 71 | .addAnnotation("# 传输时最高的超时时间。(默认:21600秒 = 6小时)") 72 | .addAnnotation("# ") 73 | .addAnnotation("# >> smartEncode") 74 | .addAnnotation("# ") 75 | .addAnnotation("# Smart choose transmission encode.") 76 | .addAnnotation("# ") 77 | .addAnnotation("# 开启后,LiteFTPD会自动检测编码,以兼容各种系统的FTP客户端。") 78 | .addAnnotation("# ") 79 | .addAnnotation("# >> defaultEncode") 80 | .addAnnotation("# ") 81 | .addAnnotation("# Set the default translating encode. Unix is UTF-8, Windows is GB2312.") 82 | .addAnnotation("# ") 83 | .addAnnotation("# 设置默认的传输编码。 Unix系统为UTF-8,Windows为GB2312。") 84 | .addAnnotation("# ") 85 | .addAnnotation("# >> port") 86 | .addAnnotation("# ") 87 | .addAnnotation("# FTP Server listening tcp port.") 88 | .addAnnotation("# ") 89 | .addAnnotation("# FTP服务监听的TCP端口号。") 90 | .addAnnotation("# ") 91 | .addAnnotation("# >> welcomeMessage") 92 | .addAnnotation("# ") 93 | .addAnnotation("# Customize welcome message when user visited.") 94 | .addAnnotation("# ") 95 | .addAnnotation("# 自定义用户连接时的欢迎信息。") 96 | .addAnnotation("# ") 97 | .addAnnotation("# >> minPort && maxPort") 98 | .addAnnotation("# ") 99 | .addAnnotation("# Appoint passive mode port range.") 100 | .addAnnotation("# Recommend 100+ ports in the range to make sure generation have high-performance!") 101 | .addAnnotation("# ") 102 | .addAnnotation("# 自定义被动模式使用的端口范围。") 103 | .addAnnotation("# 建议在范围中有100个端口以上,以确保FTP服务端的性能。") 104 | .addAnnotation("# ") 105 | .addAnnotation("# >> user") 106 | .addAnnotation("# ") 107 | .addAnnotation("# Multi users. Format:") 108 | .addAnnotation("# user=[username];[password];[permission];[permitDir];[defaultDir]; ...") 109 | .addAnnotation("# username: User's login name.") 110 | .addAnnotation("# password: User's password.") 111 | .addAnnotation("# permission:") 112 | .addAnnotation("# r = read") 113 | .addAnnotation("# w = write") 114 | .addAnnotation("# d = delete") 115 | .addAnnotation("# c = create") 116 | .addAnnotation("# m = move") 117 | .addAnnotation("# Example: rw means read and write permission.") 118 | .addAnnotation("# permitDir: Set dir that user can access.") 119 | .addAnnotation("# Example: \"/\" means user can access the hole disk; \"/home\" means user can access folder/subFolder/files under \"/home\" directory.") 120 | .addAnnotation("# defaultDir: The default dir will be re-directed when user logged.") 121 | .addAnnotation("# ") 122 | .addAnnotation("# 多用户管理。格式:") 123 | .addAnnotation("# user=[用户名];[密码];[权限];[允许访问的目录];[登录时的默认目录]; ...") 124 | .addAnnotation("# 权限:") 125 | .addAnnotation("# r = 读") 126 | .addAnnotation("# w = 写") 127 | .addAnnotation("# d = 删除") 128 | .addAnnotation("# c = 创建") 129 | .addAnnotation("# m = 移动") 130 | .addAnnotation("# 举例:rw 代表读和写的权限。") 131 | .addAnnotation("# 允许访问的目录:设置用户可以访问的目录。") 132 | .addAnnotation("# 举例:“/”代表用户可以访问整个硬盘;“/home”代表用户可以访问在“/home”目录下的所有子目录、目录和文件。") 133 | .addAnnotation("# 登录时的默认目录:登录成功后,用户所在的默认目录。") 134 | .addAnnotation("# ") 135 | .addAnnotation("# >> ipOnlineLimit") 136 | .addAnnotation("# ") 137 | .addAnnotation("# Max connections limit for specify IP Address.") 138 | .addAnnotation("# ipOnlineLimit=[IP];[Limit];[IP];[Limit]; ...") 139 | .addAnnotation("# If you defined IP Address as \"0.0.0.0\", priority will be given to limiting the number of connections per IP address to a specified number (Except for IP Address that have been set up separately).") 140 | .addAnnotation("# \"x\" means \"All\". If you defined \"192.168.x.x\", that connections from range \"192.168.0-255.0-255\" all will be refused.") 141 | .addAnnotation("# BlackList for Ip Address? Set limit to \"0\"!") 142 | .addAnnotation("# !!! Please note! The higher the configuration, the lower the weight of the connection limit (meaning that the more forward, the less likely it is to match). It is recommended to write the configuration of the specified IP at the end, and write the IP configuration using the wildcard in the front. !!!") 143 | .addAnnotation("# For example, =127.0.0.1; 1; 0.0.0.0; 100; When 127.0.0.1 is connected to the server, the maximum number of simultaneous connections allowed is 100! The configuration should be modified to =0.0.0.0;100;127.0.0.1;1;") 144 | .addAnnotation("# ") 145 | .addAnnotation("# 限制指定IP地址的最大同时连接数。") 146 | .addAnnotation("# ipOnlineLimit=[IP地址];[最大连接数];[IP地址];[最大连接数]; ...") 147 | .addAnnotation("# 如果你将IP地址定义为“0.0.0.0”,服务端将把最大连接数规则应用到所有的IP地址中(除非指定IP地址也被单独定义了)。") 148 | .addAnnotation("# “x”代表“所有”。如果你定义为“192.168.x.x”,那么来自“192.168.0-255.0-255”范围的所有IP地址都将受到该规则的限制。") 149 | .addAnnotation("# 想将指定IP地址拉黑?把最大连接数限制为“0”!") 150 | .addAnnotation("# !!! 请注意!配置越往前,连接数限制的权重越低(意味着越往前,匹配到的可能性越小)。建议将指定IP的配置写在最后面,将使用通配符的IP配置写在前面。 !!!") 151 | .addAnnotation("# 例如 =127.0.0.1;1;0.0.0.0;100; 当127.0.0.1连接服务端时,最终获取到允许同时的连接数最大为100!应将配置修改为 =0.0.0.0;100;127.0.0.1;1;") 152 | .addAnnotation("# ") 153 | .addAnnotation("# >> userOnlineLimit") 154 | .addAnnotation("# ") 155 | .addAnnotation("# Max connections limit for specify User.") 156 | .addAnnotation("# userOnlineLimit=[username];[Limit];[username];[Limit]; ...") 157 | .addAnnotation("# If you defined User as \"%\", priority will be given to limiting the number of connections per User to a specified number (Except for users that have been set up separately).") 158 | .addAnnotation("# BlackList for User? Set limit to \"0\"!") 159 | .addAnnotation("# !!! Please note! The higher the configuration, the lower the weight of the connection limit (meaning that the more forward, the less likely it is to match). It is recommended to write the configuration of the specified user to the end, and write the user configuration using the wildcard to the front. !!!") 160 | .addAnnotation("# For example, =admin;1;%;100; When logging in to the user admin, the maximum number of connections allowed to log in at the same time is 100! The configuration should be modified to =%;100;admin;1;") 161 | .addAnnotation("# ") 162 | .addAnnotation("# 限制指定用户的最大同时连接数。") 163 | .addAnnotation("# userOnlineLimit=[用户名];[最大连接数];[用户名];[最大连接数]; ...") 164 | .addAnnotation("# 如果你将用户名定义为“%”,服务端讲把最大连接数规则应用到所有的用户中(除非指定用户也被单独定义了)。") 165 | .addAnnotation("# 想将指定用户拉黑?把最大连接数限制为“0”!") 166 | .addAnnotation("# !!! 请注意!配置越往前,连接数限制的权重越低(意味着越往前,匹配到的可能性越小)。建议将指定用户的配置写在最后面,将使用通配符的用户配置写在前面。 !!!") 167 | .addAnnotation("# 例如 =admin;1;%;100; 当登录用户admin时,最终获取到允许同时登录的连接数最大为100!应将配置修改为 =%;100;admin;1;") 168 | .addAnnotation("# ") 169 | .addAnnotation("# >> speedLimit") 170 | .addAnnotation("# ") 171 | .addAnnotation("# Limit user's upload & download speed. There is no limit") 172 | .addAnnotation("# format: speedLimit=[username];[upload(kb/s)];[download(kb/s)]; ...") 173 | .addAnnotation("# ") 174 | .addAnnotation("# 限制用户的上传和下载速度。数值为0或不填时不限制。") 175 | .addAnnotation("# 格式:speedList=[用户名];[上传速度(kb/秒)];[下载速度(kb/秒)]; ...") 176 | .addAnnotation("# ") 177 | .addAnnotation("# ================================================================================================") 178 | .addAnnotation("# = ↓ CONFIG ↓ =") 179 | .addAnnotation("# ================================================================================================") 180 | .addAnnotation("# "); 181 | addProperty("user", "anonymous;;r;/;/;admin;123456;r;/;/root;") 182 | .addProperty("ipOnlineLimit", "0.0.0.0;100;127.x.x.x;100;192.168.1.x;100;") 183 | .addProperty("userOnlineLimit", "%;100;anonymous;100;admin;100;") 184 | .addProperty("speedLimit", "anonymous;0;10240;admin;;20480;") 185 | .addProperty("debugLevel", "3") 186 | .addProperty("maxUserLimit", "100") 187 | .addProperty("timeout", "100") 188 | .addProperty("maxTimeout", "21600") 189 | .addProperty("smartEncode", "true") 190 | .addProperty("defaultEncode", "UTF-8") 191 | .addProperty("port", "21") 192 | .addProperty("welcomeMessage", "オレは ルフィ、海賊王になる男だ") 193 | .addProperty("minPort", "10240") 194 | .addProperty("maxPort", "20480"); 195 | BufferedReader bufferedReader = new BufferedReader(new FileReader("config.prop")); 196 | properties.load(bufferedReader); 197 | } catch (IOException IOE) { 198 | IOE.printStackTrace(); 199 | } 200 | } catch (IOException IOE) { 201 | IOE.printStackTrace(); 202 | } 203 | // 反射并应用配置 204 | try { 205 | Class clazz = Variable.class; 206 | Set keys = properties.keySet(); 207 | for (Object key : keys) { 208 | Field field = clazz.getDeclaredField(key.toString()); 209 | switch (field.getType().toString()) { 210 | case "int": 211 | field.set(clazz, Integer.parseInt(getProperty(key.toString()))); 212 | break; 213 | case "long": 214 | field.set(clazz, Long.parseLong(getProperty(key.toString()))); 215 | break; 216 | case "boolean": 217 | field.set(clazz, Boolean.parseBoolean(getProperty(key.toString()))); 218 | break; 219 | case "class java.lang.String": 220 | field.set(clazz, getProperty(key.toString())); 221 | break; 222 | } 223 | } 224 | } catch (NoSuchFieldException | IllegalAccessException e) { 225 | e.printStackTrace(); 226 | } 227 | } 228 | 229 | public static Prop getInstance() { 230 | if (prop == null) { 231 | prop = new Prop(); 232 | } 233 | return prop; 234 | } 235 | 236 | private Prop addAnnotation(String annotation) { 237 | try { 238 | FileOutputStream fileOutputStream = new FileOutputStream(new File("config.prop"), true); 239 | fileOutputStream.write((annotation + "\n").getBytes()); 240 | fileOutputStream.flush(); 241 | fileOutputStream.close(); 242 | } catch (FileNotFoundException FNFE) { 243 | FNFE.printStackTrace(); 244 | } catch (IOException IOE) { 245 | IOE.printStackTrace(); 246 | } 247 | return this; 248 | } 249 | 250 | private Prop addProperty(String key, String value) { 251 | try { 252 | FileOutputStream fileOutputStream = new FileOutputStream(new File("config.prop"), true); 253 | fileOutputStream.write((key + "=" + value + "\n").getBytes()); 254 | fileOutputStream.flush(); 255 | fileOutputStream.close(); 256 | } catch (FileNotFoundException FNFE) { 257 | FNFE.printStackTrace(); 258 | } catch (IOException IOE) { 259 | IOE.printStackTrace(); 260 | } 261 | return this; 262 | } 263 | 264 | private String getProperty(String key) { 265 | return properties.getProperty(key); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/wizard/init/PauseListen.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.wizard.init; 2 | 3 | import pers.adlered.liteftpd.analyze.CommandAnalyze; 4 | import pers.adlered.liteftpd.analyze.PrivateVariable; 5 | import pers.adlered.liteftpd.user.status.bind.IPAddressBind; 6 | import pers.adlered.liteftpd.dict.Dict; 7 | import pers.adlered.liteftpd.logger.enums.Levels; 8 | import pers.adlered.liteftpd.logger.Logger; 9 | import pers.adlered.liteftpd.logger.enums.Types; 10 | import pers.adlered.liteftpd.tool.Status; 11 | import pers.adlered.liteftpd.user.status.bind.IpLimitBind; 12 | import pers.adlered.liteftpd.variable.OnlineUserController; 13 | import pers.adlered.liteftpd.variable.Variable; 14 | 15 | import java.io.BufferedInputStream; 16 | import java.io.BufferedOutputStream; 17 | import java.io.InputStream; 18 | import java.io.OutputStream; 19 | import java.net.Socket; 20 | 21 | /** 22 | *

LiteFTPD-UNIX

23 | *

Listening thread shutdown signal, and recycle the threads of this connection.

24 | * 25 | * @author : https://github.com/AdlerED 26 | * @date : 2019-09-19 09:21 27 | **/ 28 | public class PauseListen extends Thread { 29 | private PrivateVariable privateVariable = null; 30 | private Socket socket = null; 31 | private BufferedOutputStream bufferedOutputStream = null; 32 | private OutputStream outputStream = null; 33 | private BufferedInputStream bufferedInputStream = null; 34 | private InputStream inputStream = null; 35 | private IPAddressBind ipAddressBind = null; 36 | 37 | private Send send = null; 38 | private CommandAnalyze commandAnalyze = null; 39 | private Receive receive = null; 40 | private IpLimitBind ipLimitBind = null; 41 | 42 | private int timeout = 0; 43 | 44 | private boolean running = true; 45 | 46 | public PauseListen(PrivateVariable privateVariable, Socket socket, 47 | BufferedOutputStream bufferedOutputStream, 48 | OutputStream outputStream, BufferedInputStream bufferedInputStream, 49 | InputStream inputStream, IPAddressBind ipAddressBind, 50 | CommandAnalyze commandAnalyze, Receive receive, IpLimitBind ipLimitBind) { 51 | this.privateVariable = privateVariable; 52 | this.socket = socket; 53 | this.bufferedOutputStream = bufferedOutputStream; 54 | this.outputStream = outputStream; 55 | this.bufferedInputStream = bufferedInputStream; 56 | this.inputStream = inputStream; 57 | this.ipAddressBind = ipAddressBind; 58 | this.send = send; 59 | this.commandAnalyze = commandAnalyze; 60 | this.receive = receive; 61 | this.ipLimitBind = ipLimitBind; 62 | } 63 | 64 | public void setSend(Send send) { 65 | this.send = send; 66 | } 67 | 68 | @Override 69 | public void run() { 70 | String reason = "User quit manually"; 71 | int skipCount = 0; 72 | while (!privateVariable.interrupted) { 73 | if (privateVariable.isTimeoutLock()) { 74 | ++skipCount; 75 | resetTimeout(); 76 | } 77 | if (skipCount % 10 == 0 && skipCount != 0) { 78 | Logger.log(Types.SYS, Levels.DEBUG, ipAddressBind.getIPADD() + " skipped timeout: " + skipCount); 79 | if (skipCount > Variable.maxTimeout) { 80 | reason = "Time is out while in transmission."; 81 | break; 82 | } 83 | } 84 | // Display log every 10 seconds. 85 | if (timeout % 10 == 0 && timeout != 0) { 86 | Logger.log(Types.SYS, Levels.DEBUG, ipAddressBind.getIPADD() + " timeout: " + timeout + "=>" + Variable.timeout); 87 | } 88 | if (timeout >= Variable.timeout) { 89 | reason = "Time is out"; 90 | break; 91 | } 92 | try { 93 | ++timeout; 94 | Thread.sleep(1000); 95 | } catch (InterruptedException IE) { 96 | break; 97 | } 98 | } 99 | if (privateVariable.reason != null) { 100 | reason = privateVariable.reason; 101 | privateVariable.reason = null; 102 | } 103 | send.send(Dict.closedInReason(reason)); 104 | Logger.log(Types.SYS, Levels.INFO, "Shutting down " + ipAddressBind.getIPADD() + ", reason: " + reason); 105 | // Shutdown this hole connection. 106 | running = false; 107 | OnlineUserController.reduceOnline(socket.getInetAddress().getHostAddress(), privateVariable.getUsername()); 108 | OnlineUserController.printOnline(); 109 | try { 110 | // BufferedStream 111 | bufferedInputStream.close(); 112 | bufferedOutputStream.close(); 113 | // Stream 114 | inputStream.close(); 115 | outputStream.close(); 116 | // Socket 117 | socket.close(); 118 | } catch (Exception E) { 119 | E.getCause(); 120 | Logger.log(Types.SYS, Levels.WARN, "Shutting " + ipAddressBind.getIPADD() + " with errors."); 121 | } finally { 122 | // Variables 123 | bufferedInputStream = null; 124 | bufferedOutputStream = null; 125 | inputStream = null; 126 | outputStream = null; 127 | ipAddressBind = null; 128 | socket = null; 129 | privateVariable = null; 130 | send = null; 131 | commandAnalyze = null; 132 | receive = null; 133 | ipLimitBind = null; 134 | Logger.log(Types.SYS, Levels.DEBUG, "Called Garbage Collection."); 135 | System.gc(); 136 | Logger.log(Types.SYS, Levels.INFO, "Memory used: " + Status.memoryUsed()); 137 | } 138 | } 139 | 140 | public void resetTimeout() { 141 | timeout = 0; 142 | } 143 | 144 | public boolean isRunning() { 145 | return running; 146 | } 147 | } -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/wizard/init/Receive.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.wizard.init; 2 | 3 | import pers.adlered.liteftpd.analyze.CommandAnalyze; 4 | import pers.adlered.liteftpd.analyze.PrivateVariable; 5 | import pers.adlered.liteftpd.user.status.bind.IPAddressBind; 6 | import pers.adlered.liteftpd.logger.enums.Levels; 7 | import pers.adlered.liteftpd.logger.Logger; 8 | import pers.adlered.liteftpd.logger.enums.Types; 9 | import pers.adlered.liteftpd.tool.AutoInputStream; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | 14 | /** 15 | *

LiteFTPD-UNIX

16 | *

To receive commands.

17 | * 18 | * @author : https://github.com/AdlerED 19 | * @date : 2019-09-19 09:21 20 | **/ 21 | public class Receive extends Thread { 22 | private InputStream inputStream = null; 23 | private CommandAnalyze commandAnalyze = null; 24 | private PauseListen pauseListen = null; 25 | private PrivateVariable privateVariable = null; 26 | private IPAddressBind ipAddressBind = null; 27 | 28 | public Receive(InputStream inputStream, CommandAnalyze commandAnalyze, PauseListen pauseListen, PrivateVariable privateVariable, IPAddressBind ipAddressBind) { 29 | this.inputStream = inputStream; 30 | this.commandAnalyze = commandAnalyze; 31 | this.pauseListen = pauseListen; 32 | this.privateVariable = privateVariable; 33 | this.ipAddressBind = ipAddressBind; 34 | } 35 | 36 | @Override 37 | public void run() { 38 | try { 39 | // READ 40 | AutoInputStream autoInputStream = new AutoInputStream(inputStream, 1024, privateVariable); 41 | while (true) { 42 | String autoLine = autoInputStream.readLineAuto(); 43 | if (!pauseListen.isRunning()) { 44 | Logger.log(Types.RECV, Levels.WARN, "Receive stopped."); 45 | break; 46 | } 47 | Logger.log(Types.RECV, Levels.INFO, ipAddressBind.getIPADD() + " ==> " + ipAddressBind.getSRVIPADD() + ": " + autoLine.replaceAll("\r|\n", "")); 48 | commandAnalyze.analyze(autoLine); 49 | } 50 | } catch (IOException IOE) { 51 | // TODO 52 | IOE.printStackTrace(); 53 | } 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/wizard/init/Send.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.wizard.init; 2 | 3 | import pers.adlered.liteftpd.analyze.PrivateVariable; 4 | import pers.adlered.liteftpd.user.status.bind.IPAddressBind; 5 | import pers.adlered.liteftpd.dict.Dict; 6 | import pers.adlered.liteftpd.logger.enums.Levels; 7 | import pers.adlered.liteftpd.logger.Logger; 8 | import pers.adlered.liteftpd.logger.enums.Types; 9 | 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.net.SocketException; 13 | 14 | /** 15 | *

LiteFTPD-UNIX

16 | *

Send method can be called by other threads in the same thread pool, and send a message to client.

17 | * 18 | * @author : https://github.com/AdlerED 19 | * @date : 2019-09-19 09:21 20 | **/ 21 | public class Send { 22 | private OutputStream outputStream = null; 23 | 24 | private PauseListen pauseListen = null; 25 | private PrivateVariable privateVariable = null; 26 | private IPAddressBind ipAddressBind = null; 27 | 28 | public Send(OutputStream outputStream, PauseListen pauseListen, PrivateVariable privateVariable, IPAddressBind ipAddressBind) { 29 | this.outputStream = outputStream; 30 | this.pauseListen = pauseListen; 31 | this.privateVariable = privateVariable; 32 | this.ipAddressBind = ipAddressBind; 33 | send(Dict.connectedMessage(ipAddressBind.getIPADD())); 34 | } 35 | 36 | public boolean send(String message) { 37 | if (!privateVariable.isInterrupted()) { 38 | Logger.log(Types.SEND, Levels.DEBUG, "Encode is: " + privateVariable.getEncode()); 39 | try { 40 | Logger.log(Types.SEND, Levels.INFO, ipAddressBind.getIPADD() + " <== [ " + privateVariable.encode + " ] " + ipAddressBind.getSRVIPADD() + ": " + message.replaceAll("\r|\n", "")); 41 | pauseListen.resetTimeout(); 42 | // WELCOME MESSAGE 43 | try { 44 | outputStream.write(message.getBytes(privateVariable.encode)); 45 | } catch (SocketException SE) { 46 | Logger.log(Types.SYS, Levels.ERROR, "Client " + ipAddressBind.getIPADD() + " socket write failed. This fake client may running port scan (such as \"nmap\"), please attention."); 47 | privateVariable.reason = "Anti-scanner?"; 48 | privateVariable.setInterrupted(true); 49 | } 50 | outputStream.flush(); 51 | return true; 52 | } catch (IOException IOE) { 53 | // TODOx 54 | IOE.printStackTrace(); 55 | return false; 56 | } 57 | } else { 58 | return false; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/pers/adlered/liteftpd/wizard/init/SocketHandler.java: -------------------------------------------------------------------------------- 1 | package pers.adlered.liteftpd.wizard.init; 2 | 3 | import pers.adlered.liteftpd.analyze.CommandAnalyze; 4 | import pers.adlered.liteftpd.analyze.PrivateVariable; 5 | import pers.adlered.liteftpd.user.status.bind.IPAddressBind; 6 | import pers.adlered.liteftpd.logger.enums.Levels; 7 | import pers.adlered.liteftpd.logger.Logger; 8 | import pers.adlered.liteftpd.logger.enums.Types; 9 | import pers.adlered.liteftpd.user.status.bind.IpLimitBind; 10 | 11 | import java.io.*; 12 | import java.net.Socket; 13 | 14 | /** 15 | *

LiteFTPD-UNIX

16 | *

Split Socket to a few models, and execute the functions of new thread.

17 | * 18 | * @author : https://github.com/AdlerED 19 | * @date : 2019-09-19 09:21 20 | **/ 21 | public class SocketHandler extends Thread { 22 | Send send = null; 23 | CommandAnalyze commandAnalyze = null; 24 | Receive receive = null; 25 | PrivateVariable privateVariable = null; 26 | private InputStream inputStream = null; 27 | private OutputStream outputStream = null; 28 | private BufferedInputStream bufferedInputStream = null; 29 | private BufferedOutputStream bufferedOutputStream = null; 30 | private Socket socket = null; 31 | private IPAddressBind ipAddressBind = null; 32 | private String IPADD = null; 33 | private String SRVIPADD = null; 34 | private IpLimitBind ipLimitBind = null; 35 | 36 | public SocketHandler(Socket socket, IpLimitBind ipLimitBind) { 37 | try { 38 | IPADD = socket.getInetAddress().getHostAddress() + ":" + socket.getPort(); 39 | SRVIPADD = socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort(); 40 | // Import data streams. 41 | inputStream = socket.getInputStream(); 42 | outputStream = socket.getOutputStream(); 43 | // Input stream use buffer, but output is not because of trans situations. 44 | bufferedInputStream = new BufferedInputStream(inputStream); 45 | bufferedOutputStream = new BufferedOutputStream(outputStream); 46 | this.socket = socket; 47 | privateVariable = new PrivateVariable(); 48 | this.ipLimitBind = ipLimitBind; 49 | } catch (IOException IOE) { 50 | // TODO 51 | IOE.printStackTrace(); 52 | } 53 | } 54 | 55 | @Override 56 | public void run() { 57 | Logger.log(Types.SYS, Levels.INFO, IPADD + " has been mounted into " + Thread.currentThread()); 58 | ipAddressBind = new IPAddressBind(IPADD, SRVIPADD); 59 | // Process while user quit forced or manually. 60 | PauseListen pauseListen = new PauseListen( 61 | privateVariable, socket, 62 | bufferedOutputStream, outputStream, 63 | bufferedInputStream, inputStream, 64 | ipAddressBind, commandAnalyze, 65 | receive, ipLimitBind 66 | ); 67 | // Start model 68 | send = new Send(outputStream, pauseListen, privateVariable, ipAddressBind); 69 | pauseListen.setSend(send); 70 | commandAnalyze = new CommandAnalyze(send, SRVIPADD, privateVariable, pauseListen, ipAddressBind, ipLimitBind); 71 | receive = new Receive(inputStream, commandAnalyze, pauseListen, privateVariable, ipAddressBind); 72 | receive.start(); 73 | pauseListen.start(); 74 | } 75 | } 76 | --------------------------------------------------------------------------------