├── .gitignore ├── CosmosFtpServer.csproj ├── LICENSE.txt ├── README.md ├── resources └── icon.png └── source ├── FtpClient.cs ├── FtpCommandManager.cs └── FtpServer.cs /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .vs -------------------------------------------------------------------------------- /CosmosFtpServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | True 6 | Cosmos 7 | Cosmos 8 | FTP Server for CosmosOS. 9 | LICENSE.TXT 10 | 1.0.9 11 | 12 | 13 | 14 | CosmosFtpServer 15 | icon.png 16 | LICENSE.txt 17 | https://github.com/CosmosOS/CosmosFtp 18 | https://github.com/CosmosOS/CosmosFtp 19 | README.md 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | True 34 | \ 35 | 36 | 37 | True 38 | \ 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, CosmosOS, COSMOS Project 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 

CosmosFTP Server 🚀

2 |

3 | 4 | Version 5 | 6 | 7 | License: BSD Clause 3 License 8 | 9 |

10 | 11 | > CosmosFTP is a FTP server made in C# for the Cosmos operating system construction kit. 12 | 13 | ## Usage 14 | 15 | Install the Nuget Package from [Nuget](https://www.nuget.org/packages/CosmosFtpServer/) or [Github](https://github.com/CosmosOS/CosmosFtp/packages/1467237): 16 | 17 | ```PM 18 | Install-Package CosmosFtpServer -Version 1.0.9 19 | ``` 20 | 21 | ```PM 22 | dotnet add PROJECT package CosmosFtpServer --version 1.0.9 23 | ``` 24 | 25 | Or add these lines to your Cosmos kernel .csproj: 26 | 27 | ``` 28 | 29 | 30 | 31 | ``` 32 | 33 | You can find more information about the FTP server and how to connect from a remote computer in the [Cosmos Documentation](https://cosmosos.github.io/articles/Kernel/Network.html#ftp). 34 | 35 | ##### Port of a C written Epitech project: [NWP_myftp_2019](https://github.com/valentinbreiz/NWP_myftp_2019) 36 | 37 | ## Authors 38 | 39 | 👤 **[@valentinbreiz](https://github.com/valentinbreiz)** 40 | 41 | ## 🤝 Contributing 42 | 43 | Contributions, issues and feature requests are welcome! 44 | 45 | Feel free to check [issues page](https://github.com/CosmosOS/CosmosFtp/issues). 46 | 47 | ## Show your support 48 | 49 | Give a ⭐️ if this project helped you! 50 | 51 | ## 📝 License 52 | 53 | Copyright © 2022 [CosmosOS](https://github.com/CosmosOS). 54 | 55 | This project is [BSD Clause 3](https://github.com/CosmosOS/CosmosFtp/blob/main/LICENSE.txt) licensed. 56 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmosOS/CosmosFtp/2888f792b48acbf362cdcae8134f9c6e1ba5c60b/resources/icon.png -------------------------------------------------------------------------------- /source/FtpClient.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * PROJECT: Cosmos Operating System Development 3 | * CONTENT: FTP Client 4 | * PROGRAMMERS: Valentin Charbonnier 5 | */ 6 | 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Text; 10 | 11 | namespace CosmosFtpServer 12 | { 13 | /// 14 | /// FtpClient class. 15 | /// 16 | internal class FtpClient 17 | { 18 | /// 19 | /// Client IP Address. 20 | /// 21 | internal IPAddress Address { get; set; } 22 | 23 | /// 24 | /// Client TCP Port. 25 | /// 26 | internal int Port { get; set; } 27 | 28 | /// 29 | /// TCP Control Client. Used to send commands. 30 | /// 31 | internal TcpClient Control { get; set; } 32 | 33 | /// 34 | /// TCP Control Client. Used to send and receive commands. 35 | /// 36 | internal NetworkStream ControlStream { get; set; } 37 | 38 | /// 39 | /// TCP Data Transfer Client. Used to transfer data. 40 | /// 41 | internal TcpClient Data { get; set; } 42 | 43 | /// 44 | /// TCP Control Client. Used to send and receive commands. 45 | /// 46 | internal NetworkStream DataStream { get; set; } 47 | 48 | /// 49 | /// TCP Data Transfer Listener. Used in PASV mode. 50 | /// 51 | internal TcpListener DataListener { get; set; } 52 | 53 | /// 54 | /// FTP client data transfer mode. 55 | /// 56 | internal TransferMode Mode { get; set; } 57 | 58 | /// 59 | /// FTP client username. 60 | /// 61 | internal string Username { get; set; } 62 | 63 | /// 64 | /// FTP client password. 65 | /// 66 | internal string Password { get; set; } 67 | 68 | /// 69 | /// Is user connected. 70 | /// 71 | internal bool Connected { get; set; } 72 | 73 | /// 74 | /// Create new instance of the class. 75 | /// 76 | /// TcpClient used for control connection. 77 | internal FtpClient(TcpClient client) 78 | { 79 | Control = client; 80 | Connected = false; 81 | Mode = TransferMode.NONE; 82 | ControlStream = client.GetStream(); 83 | } 84 | 85 | /// 86 | /// Is user connected. 87 | /// 88 | /// Boolean value. 89 | internal bool IsConnected() 90 | { 91 | if (Connected == false) 92 | { 93 | SendReply(530, "Login incorrect."); 94 | return Connected; 95 | } 96 | else 97 | { 98 | return Connected; 99 | } 100 | } 101 | 102 | /// 103 | /// Send text to control socket (usually port 21) 104 | /// 105 | /// Reply code. 106 | /// Reply content. 107 | internal void SendReply(int code, string message) 108 | { 109 | message = message.Replace('\\', '/'); 110 | byte[] response = Encoding.ASCII.GetBytes(code + " " + message + "\r\n"); 111 | ControlStream.Write(response, 0, response.Length); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /source/FtpCommandManager.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * PROJECT: Cosmos Operating System Development 3 | * CONTENT: FTP Command Manager 4 | * PROGRAMMERS: Valentin Charbonnier 5 | */ 6 | 7 | using System.IO; 8 | using System.Net; 9 | using System.Net.Sockets; 10 | using System.Text; 11 | using Cosmos.System.FileSystem; 12 | using Cosmos.System.FileSystem.Listing; 13 | 14 | namespace CosmosFtpServer 15 | { 16 | /// 17 | /// FtpCommandManager class. Used to handle incoming FTP commands. 18 | /// 19 | internal class FtpCommandManager 20 | { 21 | /// 22 | /// Cosmos Virtual Filesystem. 23 | /// 24 | internal CosmosVFS FileSystem { get; set; } 25 | 26 | /// 27 | /// Base path. 28 | /// 29 | internal string BaseDirectory { get; set; } 30 | 31 | /// 32 | /// Current path. 33 | /// 34 | internal string CurrentDirectory { get; set; } 35 | 36 | /// 37 | /// Create new instance of the class. 38 | /// 39 | /// Cosmos Virtual Filesystem. 40 | /// Base directory used by the FTP server. 41 | internal FtpCommandManager(CosmosVFS fs, string directory) 42 | { 43 | FileSystem = fs; 44 | CurrentDirectory = directory; 45 | BaseDirectory = directory; 46 | } 47 | 48 | /// 49 | /// Process incoming FTP command. 50 | /// 51 | /// FTP Client. 52 | /// FTP Command. 53 | internal void ProcessRequest(FtpClient ftpClient, FtpCommand command) 54 | { 55 | if (command.Command == "USER") 56 | { 57 | ProcessUser(ftpClient, command); 58 | } 59 | else if (command.Command == "PASS") 60 | { 61 | ProcessPass(ftpClient, command); 62 | } 63 | else 64 | { 65 | if (ftpClient.IsConnected()) 66 | { 67 | switch (command.Command) 68 | { 69 | case "CWD": 70 | ProcessCwd(ftpClient, command); 71 | break; 72 | case "SYST": 73 | ftpClient.SendReply(215, "CosmosOS"); 74 | break; 75 | case "CDUP": 76 | ProcessCdup(ftpClient, command); 77 | break; 78 | case "QUIT": 79 | ProcessQuit(ftpClient, command); 80 | break; 81 | case "DELE": 82 | ProcessDele(ftpClient, command); 83 | break; 84 | case "PWD": 85 | ProcessPwd(ftpClient, command); 86 | break; 87 | case "PASV": 88 | ProcessPasv(ftpClient, command); 89 | break; 90 | case "PORT": 91 | ProcessPort(ftpClient, command); 92 | break; 93 | case "HELP": 94 | ftpClient.SendReply(200, "Help done."); 95 | break; 96 | case "NOOP": 97 | ftpClient.SendReply(200, "Command okay."); 98 | break; 99 | case "RETR": 100 | ProcessRetr(ftpClient, command); 101 | break; 102 | case "STOR": 103 | ProcessStor(ftpClient, command); 104 | break; 105 | case "RMD": 106 | ProcessRmd(ftpClient, command); 107 | break; 108 | case "MKD": 109 | ProcessMkd(ftpClient, command); 110 | break; 111 | case "LIST": 112 | ProcessList(ftpClient, command); 113 | break; 114 | case "TYPE": 115 | ftpClient.SendReply(200, "Command okay."); 116 | break; 117 | default: 118 | ftpClient.SendReply(500, "Unknown command."); 119 | break; 120 | } 121 | } 122 | } 123 | } 124 | 125 | /// 126 | /// Process USER command. 127 | /// 128 | /// FTP Client. 129 | /// FTP Command. 130 | internal void ProcessUser(FtpClient ftpClient, FtpCommand command) 131 | { 132 | if (string.IsNullOrEmpty(command.Content)) 133 | { 134 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 135 | return; 136 | } 137 | if (command.Content == "anonymous") 138 | { 139 | ftpClient.Username = command.Content; 140 | ftpClient.Connected = true; 141 | ftpClient.SendReply(230, "User logged in, proceed."); 142 | } 143 | else if (string.IsNullOrEmpty(ftpClient.Username)) 144 | { 145 | ftpClient.Username = command.Content; 146 | ftpClient.SendReply(331, "User name okay, need password."); 147 | } 148 | else 149 | { 150 | ftpClient.SendReply(550, "Requested action not taken."); 151 | } 152 | } 153 | 154 | /// 155 | /// Process PASS command. 156 | /// 157 | /// FTP Client. 158 | /// FTP Command. 159 | internal void ProcessPass(FtpClient ftpClient, FtpCommand command) 160 | { 161 | if (string.IsNullOrEmpty(command.Content)) 162 | { 163 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 164 | return; 165 | } 166 | if (ftpClient.Username == "anonymous") 167 | { 168 | ftpClient.SendReply(530, "Login incorrect."); 169 | } 170 | else if (string.IsNullOrEmpty(ftpClient.Username)) 171 | { 172 | ftpClient.SendReply(332, "Need account for login."); 173 | } 174 | else 175 | { 176 | ftpClient.Password = command.Content; 177 | ftpClient.Connected = true; 178 | ftpClient.SendReply(230, "User logged in, proceed."); 179 | } 180 | } 181 | 182 | /// 183 | /// Process CWD command. 184 | /// 185 | /// FTP Client. 186 | /// FTP Command. 187 | internal void ProcessCwd(FtpClient ftpClient, FtpCommand command) 188 | { 189 | if (string.IsNullOrEmpty(command.Content)) 190 | { 191 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 192 | return; 193 | } 194 | try 195 | { 196 | if (command.Content.StartsWith("\\")) 197 | { 198 | //Client asking for a path 199 | if (command.Content == "\\") 200 | { 201 | CurrentDirectory = BaseDirectory; 202 | } 203 | else 204 | { 205 | CurrentDirectory = BaseDirectory + command.Content; 206 | } 207 | } 208 | else 209 | { 210 | //Client asking for a folder in current directory 211 | if (CurrentDirectory == BaseDirectory) 212 | { 213 | CurrentDirectory += command.Content; 214 | } 215 | else 216 | { 217 | CurrentDirectory += "\\" + command.Content; 218 | } 219 | } 220 | 221 | if (Directory.Exists(CurrentDirectory)) 222 | { 223 | ftpClient.SendReply(250, "Requested file action okay."); 224 | } 225 | else 226 | { 227 | ftpClient.SendReply(550, "Requested action not taken."); 228 | } 229 | } 230 | catch 231 | { 232 | ftpClient.SendReply(550, "Requested action not taken."); 233 | } 234 | } 235 | 236 | /// 237 | /// Process PWD command. 238 | /// 239 | /// FTP Client. 240 | /// FTP Command. 241 | internal void ProcessPwd(FtpClient ftpClient, FtpCommand command) 242 | { 243 | //Replace 0:/ by /Cosmos/ for FTP client 244 | int i = CurrentDirectory.IndexOf(":") + 2; 245 | var tmp = CurrentDirectory.Substring(i); 246 | 247 | if (tmp.Length == 0) 248 | { 249 | ftpClient.SendReply(257, "/ created."); 250 | } 251 | else 252 | { 253 | ftpClient.SendReply(257, "\"/" + tmp + "\" created."); 254 | } 255 | } 256 | 257 | /// 258 | /// Process PASV command. 259 | /// 260 | /// FTP Client. 261 | /// FTP Command. 262 | internal void ProcessPasv(FtpClient ftpClient, FtpCommand command) 263 | { 264 | ushort port = Cosmos.System.Network.IPv4.TCP.Tcp.GetDynamicPort(); 265 | var address = ftpClient.Control.Client.LocalEndPoint.ToString(); 266 | 267 | ftpClient.DataListener = new TcpListener(IPAddress.Any, port); 268 | ftpClient.DataListener.Start(); 269 | 270 | ftpClient.SendReply(200, $"Entering Passive Mode ({address},{port / 256},{port % 256})"); 271 | 272 | ftpClient.Mode = TransferMode.PASV; 273 | } 274 | 275 | /// 276 | /// Process PORT command. 277 | /// 278 | /// FTP Client. 279 | /// FTP Command. 280 | internal void ProcessPort(FtpClient ftpClient, FtpCommand command) 281 | { 282 | string[] splitted = command.Content.Split(','); 283 | byte[] array = new byte[] { 284 | (byte)int.Parse(splitted[0]), (byte)int.Parse(splitted[1]), (byte)int.Parse(splitted[2]), (byte)int.Parse(splitted[3]) 285 | }; 286 | IPAddress address = new IPAddress(array); 287 | 288 | ftpClient.Data = new TcpClient(); 289 | 290 | ftpClient.Address = address; 291 | ftpClient.Port = int.Parse(splitted[4]) * 256 + int.Parse(splitted[5]); 292 | 293 | ftpClient.SendReply(200, "Entering Active Mode."); 294 | 295 | ftpClient.Mode = TransferMode.ACTV; 296 | } 297 | 298 | /// 299 | /// Process LIST command. 300 | /// 301 | /// FTP Client. 302 | /// FTP Command. 303 | internal void ProcessList(FtpClient ftpClient, FtpCommand command) 304 | { 305 | try 306 | { 307 | if (ftpClient.Mode == TransferMode.NONE) 308 | { 309 | ftpClient.SendReply(425, "Can't open data connection."); 310 | } 311 | else if (ftpClient.Mode == TransferMode.ACTV) 312 | { 313 | ftpClient.Data.Connect(ftpClient.Address, ftpClient.Port); 314 | ftpClient.DataStream = ftpClient.Data.GetStream(); 315 | 316 | DoList(ftpClient, command); 317 | 318 | return; 319 | } 320 | else if (ftpClient.Mode == TransferMode.PASV) 321 | { 322 | ftpClient.Data = ftpClient.DataListener.AcceptTcpClient(); 323 | ftpClient.DataStream = ftpClient.Data.GetStream(); 324 | 325 | DoList(ftpClient, command); 326 | 327 | ftpClient.DataListener.Stop(); 328 | 329 | return; 330 | } 331 | } 332 | catch 333 | { 334 | ftpClient.SendReply(425, "Can't open data connection."); 335 | } 336 | 337 | ftpClient.SendReply(425, "Can't open data connection."); 338 | } 339 | 340 | /// 341 | /// Make a file/directory listing and send it to FTP data connection. 342 | /// 343 | /// FTP Client. 344 | /// FTP Command. 345 | private void DoList(FtpClient ftpClient, FtpCommand command) 346 | { 347 | var directory_list = FileSystem.GetDirectoryListing(CurrentDirectory + "\\" + command.Content); 348 | 349 | var sb = new StringBuilder(); 350 | foreach (var directoryEntry in directory_list) 351 | { 352 | if (directoryEntry.mEntryType == DirectoryEntryTypeEnum.Directory) 353 | { 354 | sb.Append("d"); 355 | } 356 | else 357 | { 358 | sb.Append("-"); 359 | } 360 | sb.Append("rwxrwxrwx 1 unknown unknown "); 361 | sb.Append(directoryEntry.mSize); 362 | sb.Append(" Jan 1 09:00 "); 363 | sb.AppendLine(directoryEntry.mName); 364 | } 365 | 366 | ftpClient.DataStream.Write(Encoding.ASCII.GetBytes(sb.ToString())); 367 | 368 | ftpClient.Data.Close(); 369 | 370 | ftpClient.SendReply(226, "Transfer complete."); 371 | } 372 | 373 | /// 374 | /// Process DELE command. 375 | /// 376 | /// FTP Client. 377 | /// FTP Command. 378 | internal void ProcessDele(FtpClient ftpClient, FtpCommand command) 379 | { 380 | if (string.IsNullOrEmpty(command.Content)) 381 | { 382 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 383 | return; 384 | } 385 | try 386 | { 387 | if (File.Exists(CurrentDirectory + "\\" + command.Content)) 388 | { 389 | File.Delete(CurrentDirectory + "\\" + command.Content); 390 | ftpClient.SendReply(250, "Requested file action okay, completed."); 391 | } 392 | else 393 | { 394 | ftpClient.SendReply(550, "Requested action not taken."); 395 | } 396 | } 397 | catch 398 | { 399 | ftpClient.SendReply(550, "Requested action not taken."); 400 | } 401 | } 402 | 403 | /// 404 | /// Process RMD command. 405 | /// 406 | /// FTP Client. 407 | /// FTP Command. 408 | internal void ProcessRmd(FtpClient ftpClient, FtpCommand command) 409 | { 410 | if (string.IsNullOrEmpty(command.Content)) 411 | { 412 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 413 | return; 414 | } 415 | try 416 | { 417 | if (Directory.Exists(CurrentDirectory + "\\" + command.Content)) 418 | { 419 | Directory.Delete(CurrentDirectory + "\\" + command.Content, true); 420 | ftpClient.SendReply(200, "Command okay."); 421 | } 422 | else 423 | { 424 | ftpClient.SendReply(550, "Requested action not taken."); 425 | } 426 | } 427 | catch 428 | { 429 | ftpClient.SendReply(550, "Requested action not taken."); 430 | } 431 | } 432 | 433 | /// 434 | /// Process MKD command. 435 | /// 436 | /// FTP Client. 437 | /// FTP Command. 438 | internal void ProcessMkd(FtpClient ftpClient, FtpCommand command) 439 | { 440 | if (string.IsNullOrEmpty(command.Content)) 441 | { 442 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 443 | return; 444 | } 445 | try 446 | { 447 | if (Directory.Exists(CurrentDirectory + "\\" + command.Content)) 448 | { 449 | ftpClient.SendReply(550, "Requested action not taken."); 450 | } 451 | else 452 | { 453 | Directory.CreateDirectory(CurrentDirectory + "\\" + command.Content); 454 | ftpClient.SendReply(200, "Command okay."); 455 | } 456 | } 457 | catch 458 | { 459 | ftpClient.SendReply(550, "Requested action not taken."); 460 | } 461 | } 462 | 463 | /// 464 | /// Process CDUP command. 465 | /// 466 | /// FTP Client. 467 | /// FTP Command. 468 | internal void ProcessCdup(FtpClient ftpClient, FtpCommand command) 469 | { 470 | try 471 | { 472 | var root = FileSystem.GetDirectory(CurrentDirectory); 473 | 474 | if (CurrentDirectory.Length > 3) 475 | { 476 | CurrentDirectory = root.mParent.mFullPath; 477 | ftpClient.SendReply(250, "Requested file action okay."); 478 | } 479 | else 480 | { 481 | ftpClient.SendReply(550, "Requested action not taken."); 482 | } 483 | } 484 | catch 485 | { 486 | ftpClient.SendReply(550, "Requested action not taken."); 487 | } 488 | } 489 | 490 | /// 491 | /// Process STOR command. 492 | /// 493 | /// FTP Client. 494 | /// FTP Command. 495 | internal void ProcessStor(FtpClient ftpClient, FtpCommand command) 496 | { 497 | if (string.IsNullOrEmpty(command.Content)) 498 | { 499 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 500 | return; 501 | } 502 | try 503 | { 504 | if (ftpClient.Mode == TransferMode.NONE) 505 | { 506 | ftpClient.SendReply(425, "Can't open data connection."); 507 | } 508 | else if (ftpClient.Mode == TransferMode.ACTV) 509 | { 510 | ftpClient.Data.Connect(ftpClient.Address, ftpClient.Port); 511 | ftpClient.DataStream = ftpClient.Data.GetStream(); 512 | 513 | DoStor(ftpClient, command); 514 | 515 | return; 516 | } 517 | else if (ftpClient.Mode == TransferMode.PASV) 518 | { 519 | ftpClient.Data = ftpClient.DataListener.AcceptTcpClient(); 520 | ftpClient.DataStream = ftpClient.Data.GetStream(); 521 | 522 | DoStor(ftpClient, command); 523 | 524 | ftpClient.DataListener.Stop(); 525 | 526 | return; 527 | } 528 | } 529 | catch 530 | { 531 | ftpClient.SendReply(425, "Can't open data connection."); 532 | } 533 | 534 | ftpClient.SendReply(425, "Can't open data connection."); 535 | } 536 | 537 | /// 538 | /// Receive file from FTP data connection and write it to filesystem. 539 | /// 540 | /// FTP Client. 541 | /// FTP Command. 542 | private void DoStor(FtpClient ftpClient, FtpCommand command) 543 | { 544 | try 545 | { 546 | string filePath = Path.Combine(CurrentDirectory, command.Content); 547 | 548 | using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) 549 | { 550 | byte[] buffer = new byte[ftpClient.Data.ReceiveBufferSize]; 551 | int count; 552 | 553 | while ((count = ftpClient.DataStream.Read(buffer, 0, buffer.Length)) > 0) 554 | { 555 | fileStream.Write(buffer, 0, count); 556 | } 557 | } 558 | } 559 | catch 560 | { 561 | ftpClient.SendReply(550, "Requested action not taken."); 562 | } 563 | finally 564 | { 565 | ftpClient.Data.Close(); 566 | 567 | ftpClient.SendReply(226, "Transfer complete."); 568 | } 569 | } 570 | 571 | /// 572 | /// Process RETR command. 573 | /// 574 | /// FTP Client. 575 | /// FTP Command. 576 | internal void ProcessRetr(FtpClient ftpClient, FtpCommand command) 577 | { 578 | if (string.IsNullOrEmpty(command.Content)) 579 | { 580 | ftpClient.SendReply(501, "Syntax error in parameters or arguments."); 581 | return; 582 | } 583 | try 584 | { 585 | if (ftpClient.Mode == TransferMode.NONE) 586 | { 587 | ftpClient.SendReply(425, "Can't open data connection."); 588 | } 589 | else if (ftpClient.Mode == TransferMode.ACTV) 590 | { 591 | ftpClient.Data.Connect(ftpClient.Address, ftpClient.Port); 592 | ftpClient.DataStream = ftpClient.Data.GetStream(); 593 | 594 | DoRetr(ftpClient, command); 595 | 596 | return; 597 | } 598 | else if (ftpClient.Mode == TransferMode.PASV) 599 | { 600 | ftpClient.Data = ftpClient.DataListener.AcceptTcpClient(); 601 | ftpClient.DataStream = ftpClient.Data.GetStream(); 602 | 603 | DoRetr(ftpClient, command); 604 | 605 | ftpClient.DataListener.Stop(); 606 | 607 | return; 608 | } 609 | } 610 | catch 611 | { 612 | ftpClient.SendReply(425, "Can't open data connection."); 613 | } 614 | 615 | ftpClient.SendReply(425, "Can't open data connection."); 616 | } 617 | 618 | /// 619 | /// Read file from filesystem and send it to FTP data connection. 620 | /// 621 | private void DoRetr(FtpClient ftpClient, FtpCommand command) 622 | { 623 | try 624 | { 625 | string filePath = Path.Combine(CurrentDirectory, command.Content); 626 | byte[] data = File.ReadAllBytes(filePath); 627 | 628 | ftpClient.DataStream.Write(data, 0, data.Length); 629 | } 630 | catch 631 | { 632 | ftpClient.SendReply(550, "Requested action not taken."); 633 | } 634 | finally 635 | { 636 | ftpClient.Data.Close(); 637 | 638 | ftpClient.SendReply(226, "Transfer complete."); 639 | } 640 | } 641 | 642 | /// 643 | /// Process QUIT command. 644 | /// 645 | /// FTP Client. 646 | /// FTP Command. 647 | internal void ProcessQuit(FtpClient ftpClient, FtpCommand command) 648 | { 649 | ftpClient.SendReply(221, "Service closing control connection."); 650 | 651 | ftpClient.ControlStream.Close(); 652 | } 653 | } 654 | } 655 | -------------------------------------------------------------------------------- /source/FtpServer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * PROJECT: Cosmos Operating System Development 3 | * CONTENT: FTP Server 4 | * PROGRAMMERS: Valentin Charbonnier 5 | */ 6 | 7 | using System; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Net; 11 | using System.Net.Sockets; 12 | using System.Text; 13 | using Cosmos.System.FileSystem; 14 | 15 | namespace CosmosFtpServer 16 | { 17 | /// 18 | /// FTP data transfer mode. 19 | /// 20 | public enum TransferMode 21 | { 22 | /// 23 | /// No mode set. 24 | /// 25 | NONE, 26 | 27 | /// 28 | /// Active mode. 29 | /// 30 | ACTV, 31 | 32 | /// 33 | /// Passive Mode. 34 | /// 35 | PASV 36 | } 37 | 38 | /// 39 | /// FTPCommand class. 40 | /// 41 | internal class FtpCommand 42 | { 43 | /// 44 | /// FTP Command Type. 45 | /// 46 | public string Command { get; set; } 47 | 48 | /// 49 | /// FTP Command Content. 50 | /// 51 | public string Content { get; set; } 52 | } 53 | 54 | /// 55 | /// FtpServer class. Used to handle FTP client connections. 56 | /// 57 | public class FtpServer : IDisposable 58 | { 59 | /// 60 | /// Command Manager. 61 | /// 62 | internal FtpCommandManager CommandManager { get; set; } 63 | 64 | /// 65 | /// TCP Listener used to handle new FTP client connection. 66 | /// 67 | internal TcpListener tcpListener; 68 | 69 | /// 70 | /// Is FTP server listening for new FTP clients. 71 | /// 72 | internal bool Listening; 73 | 74 | /// 75 | /// Are debug logs enabled. 76 | /// 77 | internal bool Debug; 78 | 79 | /// 80 | /// Create new instance of the class. 81 | /// 82 | /// Thrown if directory does not exists. 83 | /// Initialized Cosmos Virtual Filesystem. 84 | /// FTP Server root directory path. 85 | /// Is debug logging enabled. 86 | public FtpServer(CosmosVFS fs, string directory, bool debug = false) 87 | { 88 | if (Directory.Exists(directory) == false) 89 | { 90 | throw new Exception("FTP server can't open specified directory."); 91 | } 92 | 93 | IPAddress address = IPAddress.Any; 94 | tcpListener = new TcpListener(address, 21); 95 | 96 | CommandManager = new FtpCommandManager(fs, directory); 97 | 98 | Listening = true; 99 | Debug = debug; 100 | } 101 | 102 | /// 103 | /// Listen for new FTP clients. 104 | /// 105 | public void Listen() 106 | { 107 | while (Listening) 108 | { 109 | tcpListener.Start(); 110 | 111 | TcpClient client = tcpListener.AcceptTcpClient(); 112 | 113 | IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint; 114 | 115 | Log("Client : New connection from " + endpoint.Address.ToString()); 116 | 117 | ReceiveNewClient(client); 118 | } 119 | } 120 | 121 | /// 122 | /// Handle new FTP client. 123 | /// 124 | /// FTP Client. 125 | private void ReceiveNewClient(TcpClient client) 126 | { 127 | var ftpClient = new FtpClient(client); 128 | 129 | ftpClient.SendReply(220, "Service ready for new user."); 130 | 131 | while (ftpClient.Control.Connected) 132 | { 133 | ReceiveRequest(ftpClient); 134 | } 135 | 136 | ftpClient.Control.Close(); 137 | 138 | //TODO: Support multiple FTP client connection 139 | Close(); 140 | } 141 | 142 | /// 143 | /// Parse and execute FTP command. 144 | /// 145 | /// FTP Client. 146 | private void ReceiveRequest(FtpClient ftpClient) 147 | { 148 | int bytesRead = 0; 149 | 150 | try 151 | { 152 | byte[] buffer = new byte[ftpClient.Control.ReceiveBufferSize]; 153 | bytesRead = ftpClient.ControlStream.Read(buffer, 0, buffer.Length); 154 | 155 | string data = Encoding.ASCII.GetString(buffer, 0, bytesRead).Trim(); 156 | 157 | data = data.TrimEnd(new char[] { '\r', '\n' }); 158 | string[] splitted = data.Split(' '); 159 | FtpCommand command = new FtpCommand 160 | { 161 | Command = splitted[0], 162 | Content = splitted.Length > 1 ? string.Join(" ", splitted.Skip(1)).Replace('/', '\\') : string.Empty 163 | }; 164 | 165 | Log("Client : '" + command.Command + "' " + command.Content); 166 | CommandManager.ProcessRequest(ftpClient, command); 167 | } 168 | catch (Exception ex) 169 | { 170 | Console.WriteLine("Exception: " + ex.Message); 171 | } 172 | } 173 | 174 | /// 175 | /// Write logs to console 176 | /// 177 | /// String to write. 178 | private void Log(string str) 179 | { 180 | if (Debug) 181 | { 182 | Console.WriteLine(str); 183 | Cosmos.System.Global.Debugger.Send(str); 184 | } 185 | } 186 | 187 | /// 188 | /// Close FTP server. 189 | /// 190 | public void Close() 191 | { 192 | Listening = false; 193 | tcpListener.Stop(); 194 | } 195 | 196 | /// 197 | /// Dispose 198 | /// 199 | public void Dispose() 200 | { 201 | Close(); 202 | } 203 | } 204 | } 205 | --------------------------------------------------------------------------------