├── .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 |
5 |
6 |
7 |
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 |
--------------------------------------------------------------------------------