5using System.Runtime.ExceptionServices;
7using System.Security.Authentication;
10using System.Threading.Tasks;
26 private const int MaxLineLength = 1024;
29 private string user =
string.Empty;
30 private readonly MemoryStream incoming =
new MemoryStream();
31 private int incomingSize = 0;
32 private string rootFolder =
string.Empty;
33 private string localFolder =
string.Empty;
34 private string renameFrom =
string.Empty;
35 private string selectedHostName =
string.Empty;
36 private long maxStorage = 0;
37 private long currentStorage = 0;
42 private IPEndPoint ipExtension =
null;
43 private CancellationTokenSource cancelToken =
new CancellationTokenSource();
44 private TaskCompletionSource<bool> cancelled =
new TaskCompletionSource<bool>();
45 private readonly IPEndPoint localEndpoint;
46 private readonly IPEndPoint remoteEndpoint;
48 private readonly
bool trustCertificates;
49 private long? restartPoint =
null;
50 private bool utf8 =
false;
51 private bool onlyPassive =
false;
68 this.localEndpoint = (IPEndPoint)
Client.
Client.Client.LocalEndPoint;
69 this.remoteEndpoint = (IPEndPoint)
Client.
Client.Client.RemoteEndPoint;
71 this.trustCertificates = TrustCertificates;
84 if (this.state != NewState && !this.
Disposed)
86 this.state = NewState;
88 this.
Information(
"State changed to " + NewState.ToString());
97 public event EventHandlerAsync<FtpControlConnectionState>
OnStateChanged =
null;
109 this.cancelToken?.Cancel();
110 this.cancelToken?.Dispose();
111 this.cancelToken =
null;
113 this.passiveStreams?.
Dispose();
114 this.passiveStreams =
null;
116 await base.DisposeAsync();
126 await base.ErrorAndClose();
137 protected async
override Task<bool>
ParseIncoming(
bool ConstantBuffer,
byte[] Data,
int Offset,
int NrRead)
139 int End = Offset + NrRead;
148 string Row = Encoding.UTF8.GetString(this.incoming.ToArray());
149 this.incoming.Position = 0;
150 this.incoming.SetLength(0);
151 this.incomingSize = 0;
153 if (Row.StartsWith(
"PASS ", StringComparison.CurrentCultureIgnoreCase))
158 if (!await this.ParseIncomingRow(Row))
163 this.incoming.WriteByte(b);
166 if (this.incomingSize > MaxLineLength)
168 await this.
BeginWrite(
"552 line too long.\r\n", async (Sender, e) =>
181 private static void GetCommand(
string s, out
string Cmd, out
string Value)
183 int i = s.IndexOf(
' ');
188 Value =
string.Empty;
193 Value = s[(i + 1)..];
197 private async Task<bool> ParseIncomingRow(
string s)
203 GetCommand(s, out
string Cmd, out
string Value);
210 switch (Cmd.ToUpper())
215 this.user = Value.Trim();
220 (
this.Client.RemoteCertificateValid ||
this.trustCertificates) &&
235 string Msg = await this.CanLogin();
236 if (!
string.IsNullOrEmpty(Msg))
237 return await this.
BeginWrite(Msg,
null,
null);
240 if (
string.IsNullOrEmpty(this.rootFolder))
248 if (this.rootFolder.EndsWith(Path.DirectorySeparatorChar))
249 this.rootFolder = this.rootFolder[..^1];
250 this.localFolder =
new string(Path.DirectorySeparatorChar, 1);
260 if (!await this.
BeginWrite(
"331 User name okay, need password.\r\n",
null,
null))
268 if (
string.IsNullOrEmpty(this.user))
269 return await this.
BeginWrite(
"503 User name not provided.\r\n",
null,
null);
284 string Msg = await this.CanLogin();
285 if (!
string.IsNullOrEmpty(Msg))
286 return await this.
BeginWrite(Msg,
null,
null);
289 return await this.
BeginWrite(
"534 Policy requires encryption.\r\n",
null,
null);
298 if (
string.IsNullOrEmpty(this.rootFolder))
306 if (this.rootFolder.EndsWith(Path.DirectorySeparatorChar))
307 this.rootFolder = this.rootFolder[..^1];
308 this.localFolder =
new string(Path.DirectorySeparatorChar, 1);
318 GetCommand(Value, out
string Option, out
string OptionValue);
319 switch (Option.ToLower())
322 if (
string.IsNullOrEmpty(OptionValue))
326 switch (OptionValue.ToLower())
341 return await this.
BeginWrite(
"451 Invalid type.\r\n",
null,
null);
347 if (
string.IsNullOrEmpty(OptionValue))
351 switch (OptionValue.ToLower())
366 return await this.
BeginWrite(
"451 Invalid type.\r\n",
null,
null);
372 if (
string.IsNullOrEmpty(OptionValue))
375 return await this.
BeginWrite(
"451 Invalid type.\r\n",
null,
null);
379 if (!
int.TryParse(OptionValue, out i))
380 return await this.
BeginWrite(
"451 Invalid type.\r\n",
null,
null);
382 return await this.
BeginWrite(
"504 Unhandled option.\r\n",
null,
null);
388 return await this.
BeginWrite(
"504 Unrecognized option.\r\n",
null,
null);
393 switch (Value.ToLower())
408 return await this.
BeginWrite(
"504 Unrecognized option.\r\n",
null,
null);
413 if (!await this.
BeginWrite(
"200 OK\r\n",
null,
null))
416 this.
Server?.Closed(
this);
422 if (
string.IsNullOrEmpty(Value))
423 return await this.
BeginWrite(
"501 Missing parameter.\r\n",
null,
null);
425 string[] P = Value.Split(
',');
428 !
byte.TryParse(P[0], out
byte h1) ||
429 !
byte.TryParse(P[1], out
byte h2) ||
430 !
byte.TryParse(P[2], out
byte h3) ||
431 !
byte.TryParse(P[3], out
byte h4) ||
432 !
byte.TryParse(P[4], out
byte p1) ||
433 !
byte.TryParse(P[5], out
byte p2))
435 return await this.
BeginWrite(
"501 Syntax error.\r\n",
null,
null);
438 if (this.onlyPassive)
439 return await this.
BeginWrite(
"522 Passive mode only.\r\n",
null,
null);
441 this.ipExtension =
new IPEndPoint(
442 new IPAddress(
new byte[] { h1, h2, h3, h4 }),
448 DataPort DataPort = await this.BeginListen(
null, 10000);
450 if (DataPort is
null)
451 return await this.
BeginWrite(
"522 Unable to open port listening for incoming connections.\r\n",
null,
null);
453 p1 = (byte)(DataPort.Port >> 8);
454 p2 = (byte)(DataPort.Port & 255);
456 sb =
new StringBuilder();
457 sb.Append(
"227 Entering Passive Mode (");
458 sb.Append(this.localEndpoint.Address.ToString().Replace(
'.',
','));
460 sb.Append(p1.ToString());
462 sb.Append(p2.ToString());
465 return await this.
BeginWrite(sb.ToString(),
null,
null);
468 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
469 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
471 if (!
long.TryParse(Value, out
long l))
472 return await this.
BeginWrite(
"501 Invalid file position.\r\n",
null,
null);
474 return await this.
BeginWrite(
"501 File positions must be positive.\r\n",
null,
null);
476 this.restartPoint = l;
477 return await this.
BeginWrite(
"350 Restarting at " + l.ToString() +
" for next REST, STOR or APPE.\r\n",
null,
null);
480 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
481 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
483 if (
string.IsNullOrEmpty(Value))
484 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
485 else if (HasForbiddenCharacters(Value))
486 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
490 s2 = this.GetFullPath(s2);
491 if (
string.IsNullOrEmpty(s2))
492 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
494 if (!File.Exists(s2))
495 return await this.
BeginWrite(
"550 File not found.\r\n",
null,
null);
497 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
503 f = File.OpenRead(s2);
507 return await this.
BeginWrite(
"550 Access denied: " + FirstRow(ex) +
"\r\n",
null,
null);
510 return await this.SendBinaryFile(f,
null,
true);
513 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
514 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
516 if (
string.IsNullOrEmpty(Value))
517 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
518 else if (HasForbiddenCharacters(Value))
519 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
523 s2 = this.GetFullPath(s2);
524 if (
string.IsNullOrEmpty(s2))
525 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
528 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
534 if (this.restartPoint.HasValue)
536 f = File.OpenWrite(s2);
537 InitialSize = f.Length;
547 return await this.
BeginWrite(
"550 Access denied: " + FirstRow(ex) +
"\r\n",
null,
null);
550 return await this.ReceiveBinaryFile(f,
true, InitialSize);
553 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
554 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
556 if (
string.IsNullOrEmpty(Value))
557 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
558 else if (HasForbiddenCharacters(Value))
559 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
563 s2 = this.GetFullPath(s2);
564 if (
string.IsNullOrEmpty(s2))
565 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
568 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
574 f = File.OpenWrite(s2);
575 InitialSize = f.Length;
585 return await this.
BeginWrite(
"550 Access denied: " + FirstRow(ex) +
"\r\n",
null,
null);
588 return await this.ReceiveBinaryFile(f,
true, InitialSize);
591 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
592 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
594 if (
string.IsNullOrEmpty(Value))
595 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
596 else if (HasForbiddenCharacters(Value))
597 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
601 s2 = this.GetFullPath(s2);
602 if (
string.IsNullOrEmpty(s2))
603 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
605 if (!File.Exists(s2))
606 return await this.
BeginWrite(
"550 File not found.\r\n",
null,
null);
607 else if (!this.Account.
HasPrivilege(WritePrivilege(s2)))
608 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
612 FileInfo Info =
new FileInfo(s2);
613 long Len = Info.Length;
615 this.restartPoint =
null;
618 this.currentStorage -= Len;
622 return await this.
BeginWrite(
"550 Access denied: " + FirstRow(ex) +
"\r\n",
null,
null);
625 return await this.
BeginWrite(
"250 File deleted.\r\n",
null,
null);
628 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
629 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
631 if (
string.IsNullOrEmpty(Value))
632 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
633 else if (HasForbiddenCharacters(Value))
634 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
638 s2 = this.GetFullPath(s2);
639 if (
string.IsNullOrEmpty(s2))
640 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
642 if (!File.Exists(s2) && !Directory.Exists(s2))
643 return await this.
BeginWrite(
"550 File not found.\r\n",
null,
null);
644 else if (!this.Account.
HasPrivilege(WritePrivilege(s2)))
645 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
647 this.renameFrom = s2;
649 return await this.
BeginWrite(
"350 File or folder exists, ready for destination name.\r\n",
null,
null);
652 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
653 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
655 if (
string.IsNullOrEmpty(Value))
656 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
657 else if (HasForbiddenCharacters(Value))
658 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
662 s2 = this.GetFullPath(s2);
663 if (
string.IsNullOrEmpty(s2))
664 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
666 if (
string.IsNullOrEmpty(this.renameFrom))
667 return await this.
BeginWrite(
"503 Need RNFR first.\r\n",
null,
null);
669 if (!this.Account.
HasPrivilege(WritePrivilege(this.renameFrom)))
670 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
674 if (File.Exists(
this.renameFrom))
676 File.Move(this.renameFrom, s2);
677 this.renameFrom =
null;
678 return await this.
BeginWrite(
"250 File renamed.\r\n",
null,
null);
680 else if (Directory.Exists(
this.renameFrom))
682 Directory.Move(this.renameFrom, s2);
683 this.renameFrom =
null;
684 return await this.
BeginWrite(
"250 Folder renamed.\r\n",
null,
null);
688 this.renameFrom =
null;
689 return await this.
BeginWrite(
"550 File or folder no longer found.\r\n",
null,
null);
692 catch (IOException ex)
694 return await this.
BeginWrite(
"450 Unable to rename file: " + FirstRow(ex) +
"\r\n",
null,
null);
698 this.cancelToken?.Cancel();
700 TaskCompletionSource<bool> Cancelled = this.cancelled;
701 Task
_ = Task.Delay(1000).ContinueWith((
_) => Cancelled.TrySetResult(
false));
703 await Cancelled.Task;
705 return await this.
BeginWrite(
"226 Abort successful.\r\n",
null,
null);
712 if (
string.IsNullOrEmpty(Value))
713 return await this.
BeginWrite(
"200 File Structure (F) used.\r\n",
null,
null);
715 if (Value.ToLower() ==
"f")
718 return await this.
BeginWrite(
"504 Unsupported option.\r\n",
null,
null);
721 return await this.
BeginWrite(
"202 Account not used.\r\n",
null,
null);
724 return await this.
BeginWrite(
"202 No need to allocated space for files.\r\n",
null,
null);
727 return await this.
BeginWrite(
"202 No site-specific parameters.\r\n",
null,
null);
730 return await this.
BeginWrite(
"202 No status to report.\r\n",
null,
null);
736 return await this.
BeginWrite(
"214 No help available.\r\n",
null,
null);
741 if (
string.IsNullOrEmpty(this.localFolder))
742 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
746 this.localFolder.Replace(Path.DirectorySeparatorChar,
'/') +
747 "\" is current folder.\r\n",
null,
null);
751 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
752 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
754 if (
string.IsNullOrEmpty(Value))
755 s2 = this.localFolder;
756 else if (HasForbiddenCharacters(Value))
757 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
761 s2 = this.GetFullPath(s2);
762 if (
string.IsNullOrEmpty(s2))
763 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
765 if (!Directory.Exists(s2))
766 return await this.
BeginWrite(
"550 Folder not found.\r\n",
null,
null);
768 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
770 this.localFolder = s2[this.rootFolder.Length..];
771 if (!this.localFolder.EndsWith(Path.DirectorySeparatorChar))
772 this.localFolder += Path.DirectorySeparatorChar;
776 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
777 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
779 s2 = this.GetFullPath(
"..");
780 if (
string.IsNullOrEmpty(s2))
781 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
783 if (s2.Length <
this.rootFolder.Length)
784 return await this.
BeginWrite(
"200 Already at root.\r\n",
null,
null);
787 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
789 this.localFolder = s2[this.rootFolder.Length..];
790 if (!this.localFolder.EndsWith(Path.DirectorySeparatorChar))
791 this.localFolder += Path.DirectorySeparatorChar;
795 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
796 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
798 if (
string.IsNullOrEmpty(Value))
799 s2 = this.localFolder;
800 else if (HasForbiddenCharacters(Value))
801 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
805 s2 = this.GetFullPath(s2);
806 if (
string.IsNullOrEmpty(s2))
807 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
809 sb =
new StringBuilder();
811 if (Directory.Exists(s2))
814 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
816 foreach (
string SubFolder
in Directory.GetDirectories(s2,
"*.*", SearchOption.TopDirectoryOnly))
818 if (!this.Account.
HasPrivilege(ReadPrivilege(SubFolder)))
821 string Name = SubFolder[s2.Length..];
827 foreach (
string FileName
in Directory.GetFiles(s2,
"*.*", SearchOption.TopDirectoryOnly))
829 if (!this.Account.
HasPrivilege(ReadPrivilege(FileName)))
832 string Name = FileName[s2.Length..];
838 else if (File.Exists(s2))
841 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
843 sb.Append(Path.GetFileName(s2));
847 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
849 return await this.SendTextFile(sb.ToString());
852 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
853 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
855 if (
string.IsNullOrEmpty(Value))
856 s2 = this.localFolder;
857 else if (HasForbiddenCharacters(Value))
858 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
862 s2 = this.GetFullPath(s2);
863 if (
string.IsNullOrEmpty(s2))
864 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
866 sb =
new StringBuilder();
868 if (Directory.Exists(s2))
871 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
873 foreach (
string SubFolder
in Directory.GetDirectories(s2,
"*.*", SearchOption.TopDirectoryOnly))
875 if (!this.Account.
HasPrivilege(ReadPrivilege(SubFolder)))
878 AppendListInfo(sb,
new DirectoryInfo(SubFolder));
881 foreach (
string FileName
in Directory.GetFiles(s2,
"*.*", SearchOption.TopDirectoryOnly))
883 if (!this.Account.
HasPrivilege(ReadPrivilege(FileName)))
886 AppendListInfo(sb,
new FileInfo(FileName));
889 else if (File.Exists(s2))
892 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
894 AppendListInfo(sb,
new FileInfo(s2));
897 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
899 return await this.SendTextFile(sb.ToString());
902 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
904 else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
906 else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
911 return await this.
BeginWrite(
"215 " + s2 +
" Type: L8\r\n",
null,
null);
914 return await this.
BeginWrite(
"502 Mounting file data structures not supported.\r\n",
null,
null);
917 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
918 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
920 if (
string.IsNullOrEmpty(Value))
921 s2 = this.localFolder;
922 else if (HasForbiddenCharacters(Value))
923 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
927 s2 = this.GetFullPath(s2);
928 if (
string.IsNullOrEmpty(s2))
929 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
931 if (Directory.Exists(s2))
932 return await this.
BeginWrite(
"550 Folder already exists.\r\n",
null,
null);
933 else if (!this.Account.
HasPrivilege(WritePrivilege(s2)))
934 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
937 Directory.CreateDirectory(s2);
938 return await this.
BeginWrite(
"257 Folder created.\r\n",
null,
null);
942 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
943 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
945 if (
string.IsNullOrEmpty(Value))
946 s2 = this.localFolder;
947 else if (HasForbiddenCharacters(Value))
948 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
952 s2 = this.GetFullPath(s2);
953 if (
string.IsNullOrEmpty(s2))
954 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
956 if (!Directory.Exists(s2))
957 return await this.
BeginWrite(
"550 No such folder.\r\n",
null,
null);
958 else if (!this.Account.
HasPrivilege(WritePrivilege(s2)))
959 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
962 Directory.Delete(s2,
false);
963 return await this.
BeginWrite(
"250 Folder deleted.\r\n",
null,
null);
967 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
968 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
970 if (
string.IsNullOrEmpty(Value))
972 else if (HasForbiddenCharacters(Value))
973 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
977 s2 = this.GetFullPath(s2);
978 if (
string.IsNullOrEmpty(s2))
979 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
983 while ((File.Exists(s2) || Directory.Exists(s2)) && i < 65536)
986 int j = s2.LastIndexOf(
'.');
990 s2 = s2[0..j] + i.ToString() + s2[j..];
994 return await this.
BeginWrite(
"550 Unable to find unique name.\r\n",
null,
null);
998 if (this.restartPoint.HasValue)
999 f = File.OpenWrite(s2);
1001 f = File.Create(s2);
1003 catch (Exception ex)
1005 return await this.
BeginWrite(
"550 Access denied: " + FirstRow(ex) +
"\r\n",
null,
null);
1008 return await this.ReceiveBinaryFile(f,
true, 0);
1019 return await this.
BeginWrite(
"502 Command not implemented.\r\n",
null,
null);
1024 GetCommand(Value, out Option, out OptionValue);
1026 switch (Option.ToLower())
1032 return await this.
BeginWrite(
"451 Invalid Boolean value.\r\n",
null,
null);
1036 return await this.
BeginWrite(
"504 Unrecognized option.\r\n",
null,
null);
1041 if (
string.IsNullOrEmpty(Value))
1042 return await this.
BeginWrite(
"501 Missing parameter.\r\n",
null,
null);
1044 char Delimiter = Value[0];
1045 P = Value.Split(Delimiter);
1047 if (P.Length != 5 || !
string.IsNullOrEmpty(P[0]) || !
string.IsNullOrEmpty(P[4]))
1048 return await this.
BeginWrite(
"501 Syntax error.\r\n",
null,
null);
1050 if (!
int.TryParse(P[1], out
int AddressType) ||
1051 AddressType < 1 || AddressType > 2)
1053 return await this.
BeginWrite(
"504 Unsupported address family.\r\n",
null,
null);
1056 if (!IPAddress.TryParse(P[2], out IPAddress Address))
1057 return await this.
BeginWrite(
"501 Invalid IP Address.\r\n",
null,
null);
1059 AddressFamily Expected =
AddressType == 1 ? AddressFamily.InterNetwork : AddressFamily.InterNetworkV6;
1060 if (Address.AddressFamily != Expected)
1061 return await this.
BeginWrite(
"501 IP Address does not match address family.\r\n",
null,
null);
1063 if (!
int.TryParse(P[3], out
int Port) || Port < 1 || Port > 65535)
1064 return await this.
BeginWrite(
"501 Invalid port number.\r\n",
null,
null);
1066 if (this.onlyPassive)
1067 return await this.
BeginWrite(
"522 Passive mode only.\r\n",
null,
null);
1069 this.ipExtension =
new IPEndPoint(Address, Port);
1075 if (
string.IsNullOrEmpty(Value))
1076 RequestedPort =
null;
1077 else if (
int.TryParse(Value, out i))
1079 if (i < 1 || i > 65535)
1080 return await this.
BeginWrite(
"501 Invalid port number.\r\n",
null,
null);
1084 else if (
string.Compare(Value,
"ALL",
true) == 0)
1086 RequestedPort =
null;
1087 this.onlyPassive =
true;
1090 return await this.
BeginWrite(
"501 Unrecognized parameter.\r\n",
null,
null);
1092 DataPort = await this.BeginListen(RequestedPort, 10000);
1094 if (DataPort is
null)
1095 return await this.
BeginWrite(
"522 Unable to open port listening for incoming connections.\r\n",
null,
null);
1097 return await this.
BeginWrite(
"229 Listening for incoming connections (|||" + DataPort.Port.ToString() +
"|).\r\n",
null,
null);
1100 switch (Value.ToLower())
1104 return await this.
BeginWrite(
"503 Already encrypted.\r\n",
null,
null);
1107 if (await this.
BeginWrite(
"234 Proceed.\r\n",
null,
null))
1108 this.UpgradeToTls =
true;
1112 return await this.
BeginWrite(
"504 Unrecognized mechanism.\r\n",
null,
null);
1116 if (!
int.TryParse(Value, out i) || i < 0)
1117 return await this.
BeginWrite(
"501 protection buffer size.\r\n",
null,
null);
1120 return await this.
BeginWrite(
"503 connection not encrypted.\r\n",
null,
null);
1123 return await this.
BeginWrite(
"200 PBSZ=0.\r\n",
null,
null);
1127 switch (Value.ToLower())
1131 return await this.
BeginWrite(
"534 Protection level not allowed.\r\n",
null,
null);
1138 return await this.
BeginWrite(
"534 Protection level not allowed.\r\n",
null,
null);
1145 return await this.
BeginWrite(
"534 Protection level not allowed.\r\n",
null,
null);
1152 return await this.
BeginWrite(
"534 Protection level not allowed.\r\n",
null,
null);
1158 return await this.
BeginWrite(
"504 Unrecognized mechanism.\r\n",
null,
null);
1163 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1164 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1166 if (
string.IsNullOrEmpty(Value))
1167 s2 = this.localFolder;
1168 else if (HasForbiddenCharacters(Value))
1169 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1173 s2 = this.GetFullPath(s2);
1174 if (
string.IsNullOrEmpty(s2))
1175 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1177 sb =
new StringBuilder();
1179 if (File.Exists(s2))
1182 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1184 sb.Append(
"250-File information:\r\n ");
1185 AppendListEntryInfo(sb,
new FileInfo(s2));
1187 else if (Directory.Exists(s2))
1190 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1192 sb.Append(
"250-Directory information:\r\n ");
1193 AppendListEntryInfo(sb,
new DirectoryInfo(s2));
1196 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
1198 sb.Append(
"250 OK\r\n");
1200 return await this.
BeginWrite(sb.ToString(),
null,
null);
1203 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1204 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1206 if (
string.IsNullOrEmpty(Value))
1207 s2 = this.localFolder;
1208 else if (HasForbiddenCharacters(Value))
1209 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1213 s2 = this.GetFullPath(s2);
1214 if (
string.IsNullOrEmpty(s2))
1215 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1217 sb =
new StringBuilder();
1219 if (Directory.Exists(s2))
1222 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1224 foreach (
string SubFolder
in Directory.GetDirectories(s2,
"*.*", SearchOption.TopDirectoryOnly))
1226 if (!this.Account.
HasPrivilege(ReadPrivilege(SubFolder)))
1229 AppendListEntryInfo(sb,
new DirectoryInfo(SubFolder));
1232 foreach (
string FileName
in Directory.GetFiles(s2,
"*.*", SearchOption.TopDirectoryOnly))
1234 if (!this.Account.
HasPrivilege(ReadPrivilege(FileName)))
1237 AppendListEntryInfo(sb,
new FileInfo(FileName));
1240 return await this.SendTextFile(sb.ToString());
1243 return await this.
BeginWrite(
"501 Folder not found.\r\n",
null,
null);
1246 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1247 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1249 if (
string.IsNullOrEmpty(Value))
1250 s2 = this.localFolder;
1251 else if (HasForbiddenCharacters(Value))
1252 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1256 s2 = this.GetFullPath(s2);
1257 if (
string.IsNullOrEmpty(s2))
1258 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1260 sb =
new StringBuilder();
1262 if (File.Exists(s2))
1265 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1267 FileInfo Info =
new FileInfo(s2);
1270 Info.LastWriteTimeUtc.ToString(dateTimeFormatRfc3659) +
1271 "\r\n",
null,
null);
1274 return await this.
BeginWrite(
"501 File not found.\r\n",
null,
null);
1277 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1278 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1280 if (
string.IsNullOrEmpty(Value))
1281 s2 = this.localFolder;
1282 else if (HasForbiddenCharacters(Value))
1283 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1287 s2 = this.GetFullPath(s2);
1288 if (
string.IsNullOrEmpty(s2))
1289 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1291 sb =
new StringBuilder();
1293 if (File.Exists(s2))
1296 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1298 FileInfo Info =
new FileInfo(s2);
1300 return await this.
BeginWrite(
"213 " + Info.Length.ToString() +
1301 "\r\n",
null,
null);
1304 return await this.
BeginWrite(
"501 File not found.\r\n",
null,
null);
1307 return await this.
BeginWrite(
"502 Virtual File Stores not supported.\r\n",
null,
null);
1310 sb =
new StringBuilder();
1312 sb.Append(
"211-Features:\r\n");
1315 sb.Append(
" AUTH TLS\r\n");
1327 sb.Append(
" TYPE I;L8\r\n");
1328 sb.Append(
" MDTM\r\n");
1329 sb.Append(
" MLST Type*;Size*;Modify*;Create*;Media-Type*;\r\n");
1330 sb.Append(
" SIZE\r\n");
1331 sb.Append(
" REST STREAM\r\n");
1332 sb.Append(
" XSHA256\r\n");
1333 sb.Append(
" XSHA512\r\n");
1334 sb.Append(
" HOST\r\n");
1335 sb.Append(
"211 End\r\n");
1337 return await this.
BeginWrite(sb.ToString(),
null,
null);
1340 this.selectedHostName = Value;
1341 return await this.
BeginWrite(
"220 Host received.\r\n",
null,
null);
1346 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1347 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1349 if (
string.IsNullOrEmpty(Value))
1350 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
1351 else if (HasForbiddenCharacters(Value))
1352 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1356 s2 = this.GetFullPath(s2);
1357 if (
string.IsNullOrEmpty(s2))
1358 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1360 if (!File.Exists(s2))
1361 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
1362 else if (!this.Account.
HasPrivilege(ReadPrivilege(s2)))
1363 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1365 using (FileStream fs = File.OpenRead(s2))
1371 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1372 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1374 if (
string.IsNullOrEmpty(Value))
1375 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
1376 else if (HasForbiddenCharacters(Value))
1377 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1381 s2 = this.GetFullPath(s2);
1382 if (
string.IsNullOrEmpty(s2))
1383 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1385 if (!File.Exists(s2))
1386 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
1387 else if (!this.Account.
HasPrivilege(ReadPrivilege(s2)))
1388 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1390 using (FileStream fs = File.OpenRead(s2))
1396 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1397 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1399 if (
string.IsNullOrEmpty(Value))
1400 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
1401 else if (HasForbiddenCharacters(Value))
1402 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1406 s2 = this.GetFullPath(s2);
1407 if (
string.IsNullOrEmpty(s2))
1408 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1410 if (!File.Exists(s2))
1411 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
1412 else if (!this.Account.
HasPrivilege(ReadPrivilege(s2)))
1413 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1415 using (FileStream fs = File.OpenRead(s2))
1421 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1422 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1424 if (
string.IsNullOrEmpty(Value))
1425 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
1426 else if (HasForbiddenCharacters(Value))
1427 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1431 s2 = this.GetFullPath(s2);
1432 if (
string.IsNullOrEmpty(s2))
1433 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1435 if (!File.Exists(s2))
1436 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
1437 else if (!this.Account.
HasPrivilege(ReadPrivilege(s2)))
1438 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1440 using (FileStream fs = File.OpenRead(s2))
1446 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1447 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1449 if (
string.IsNullOrEmpty(Value))
1450 return await this.
BeginWrite(
"501 File path missing.\r\n",
null,
null);
1451 else if (HasForbiddenCharacters(Value))
1452 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1456 s2 = this.GetFullPath(s2);
1457 if (
string.IsNullOrEmpty(s2))
1458 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1460 if (!File.Exists(s2))
1461 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
1462 else if (!this.Account.
HasPrivilege(ReadPrivilege(s2)))
1463 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1465 using (FileStream fs = File.OpenRead(s2))
1467 return await this.
BeginWrite(
"213 " + ComputeCrc32(fs).
ToString(
"x8") +
"\r\n",
null,
null);
1471 if (
string.IsNullOrEmpty(this.localFolder) || this.Account is
null)
1472 return await this.
BeginWrite(
"530 User not logged in.\r\n",
null,
null);
1474 GetCommand(Value, out Option, out OptionValue);
1476 if (
string.IsNullOrEmpty(OptionValue))
1478 OptionValue = Option;
1482 if (HasForbiddenCharacters(OptionValue))
1483 return await this.
BeginWrite(
"550 Forbidden characters.\r\n",
null,
null);
1487 s2 = this.GetFullPath(s2);
1488 if (
string.IsNullOrEmpty(s2))
1489 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1491 if (!File.Exists(s2))
1492 return await this.
BeginWrite(
"501 File or folder not found.\r\n",
null,
null);
1493 else if (!this.Account.
HasPrivilege(ReadPrivilege(s2)))
1494 return await this.
BeginWrite(
"550 Access denied.\r\n",
null,
null);
1496 using (FileStream fs = File.OpenRead(s2))
1498 return Option.ToLower()
switch
1504 _ => await this.
BeginWrite(
"504 Unrecognized hash algorithm.\r\n",
null,
null),
1509 return await this.
BeginWrite(
"200 OK\r\n",
null,
null);
1511 catch (Exception ex)
1513 return await this.
BeginWrite(
"550 " + FirstRow(ex) +
"\r\n",
null,
null);
1517 private string GetFullPath(
string LocalPath)
1521 LocalPath = LocalPath.Replace(
'/', Path.DirectorySeparatorChar);
1523 if (LocalPath.StartsWith(Path.DirectorySeparatorChar))
1524 s = this.rootFolder + LocalPath;
1526 s = this.rootFolder + this.localFolder + LocalPath;
1528 if (s.EndsWith(Path.DirectorySeparatorChar))
1531 s = Path.GetFullPath(s);
1533 if (s == this.rootFolder || s.StartsWith(
this.rootFolder + Path.DirectorySeparatorChar))
1539 private static bool HasForbiddenCharacters(
string Path)
1541 char LastChar = (char)0;
1543 foreach (
char ch
in Path)
1565 private static void AppendListInfo(StringBuilder sb, DirectoryInfo Info)
1568 sb.Append(
"rwxr-xr-x 1 owner group ");
1569 AppendListInfo(sb, 4096, Info);
1572 private static void AppendListInfo(StringBuilder sb, FileInfo Info)
1575 sb.Append(
"rw-r--r-- 1 owner group ");
1576 AppendListInfo(sb, Info.Length, Info);
1579 private static void AppendListInfo(StringBuilder sb,
long Size, FileSystemInfo Info)
1581 Append(sb, Size, 13);
1583 sb.Append(Month(Info.LastWriteTime.Month));
1584 Append(sb, Info.LastWriteTime.Day, 3);
1586 if (DateTime.Now.AddMonths(-6) < Info.LastWriteTime)
1589 sb.Append(Info.LastWriteTime.Hour.ToString(
"D2"));
1591 sb.Append(Info.LastWriteTime.Minute.ToString(
"D2"));
1594 Append(sb, Info.LastWriteTime.Year, 6);
1597 sb.Append(Info.Name);
1601 private static void Append(StringBuilder sb,
long Nr,
int Len)
1603 string s = Nr.ToString();
1605 sb.Append(
new string(
' ', Len - s.Length));
1610 private static string Month(
int Month)
1630 private const string dateTimeFormatRfc3659 =
"yyyyMMddHHmmss";
1632 private static string AppendListEntryInfo(StringBuilder sb, FileInfo Info)
1634 sb.Append(
"Type=file;Size=");
1635 sb.Append(Info.Length.ToString());
1636 sb.Append(
";Modify=");
1637 sb.Append(Info.LastWriteTimeUtc.ToString(dateTimeFormatRfc3659));
1638 sb.Append(
";Create=");
1639 sb.Append(Info.CreationTimeUtc.ToString(dateTimeFormatRfc3659));
1640 sb.Append(
";Media-Type=");
1643 sb.Append(Info.Name);
1646 return sb.ToString();
1649 private static string AppendListEntryInfo(StringBuilder sb, DirectoryInfo Info)
1651 sb.Append(
"Type=dir;Size=4096;Modify=");
1652 sb.Append(Info.LastWriteTimeUtc.ToString(dateTimeFormatRfc3659));
1653 sb.Append(
";Create=");
1654 sb.Append(Info.CreationTimeUtc.ToString(dateTimeFormatRfc3659));
1656 sb.Append(Info.Name);
1659 return sb.ToString();
1662 private static uint ComputeCrc32(Stream stream)
1664 const uint Polynomial = 0xedb88320;
1665 uint[] Table =
new uint[256];
1668 for (uint i = 0; i < Table.Length; ++i)
1671 for (uint j = 8; j > 0; --j)
1674 Crc = (Crc >> 1) ^ Polynomial;
1685 while ((b = stream.ReadByte()) != -1)
1687 byte index = (byte)(((Crc) & 0xFF) ^ b);
1688 Crc = ((Crc >> 8) ^ Table[index]);
1694 private async Task<DataPort> BeginListen(
int? Port,
int Timeout)
1696 bool ReleaseAfterUse =
false;
1706 ReleaseAfterUse =
true;
1712 DataPort Result = this.
Server.OpenDataListener(
1713 this.AcceptTcpClientDataCallback, this.localEndpoint.Address,
1714 Port ?? 0, ReleaseAfterUse);
1716 if (Result.LocalEndpoint is
null)
1718 Result.Listener.Stop();
1719 this.
Server.Remove(Result);
1728 catch (Exception ex)
1730 if (ReleaseAfterUse)
1733 ExceptionDispatchInfo.Capture(ex).Throw();
1738 private class PassiveRecord
1740 public TaskCompletionSource<bool> PassiveChannel =
new TaskCompletionSource<bool>();
1741 public DataPort PassivePort;
1742 public TcpClient PassiveClient;
1743 public bool DataConnectionEstablished =
false;
1746 private void AcceptTcpClientDataCallback(IAsyncResult ar)
1753 DataPort Port = (DataPort)ar.AsyncState;
1754 TcpListener Listener = Port.Listener;
1760 Client = Listener.EndAcceptTcpClient(ar);
1763 this.
Information(
"Connection received on port " + Port.Port.ToString() +
1769 this.
Error(
"Connection discarded. Not from same remote endpoint.");
1774 Task
_ = Task.Run(() => this.PassiveChannelEstablished(Port,
Client));
1790 catch (Exception ex)
1796 private async Task PassiveChannelEstablished(DataPort Port, TcpClient Client)
1800 PassiveRecord Rec = await this.passiveStreams.
Wait(this.cancelToken.Token, 10000);
1802 this.Warning(
"No passive stream available.");
1805 Rec.PassivePort = Port;
1806 Rec.PassiveClient = Client;
1807 Rec.PassiveChannel.TrySetResult(
true);
1813 catch (Exception ex)
1824 private Task<bool> SendTextFile(
string Content)
1829 byte[] Bin = Encoding.UTF8.GetBytes(Content);
1831 return this.SendBinaryFile(Bin, Content);
1834 private Task<bool> SendBinaryFile(
byte[] Content)
1836 return this.SendBinaryFile(Content,
null);
1839 private Task<bool> SendBinaryFile(
byte[] Content,
string TextContent)
1841 MemoryStream ms =
new MemoryStream(Content);
1842 return this.SendBinaryFile(ms, TextContent,
true);
1845 private Task<bool> SendBinaryFile(Stream Content,
string TextContent,
bool DisposeStream)
1847 return this.TransferBinaryFile(Content, TextContent, DisposeStream,
true, 0);
1850 private Task<bool> ReceiveBinaryFile(Stream Content,
bool DisposeStream,
long InitialSize)
1852 return this.TransferBinaryFile(Content,
null, DisposeStream,
false, InitialSize);
1855 private async Task<bool> TransferBinaryFile(Stream Content,
string TextContent,
1856 bool DisposeStream,
bool Send,
long InitialSize)
1859 bool Connecting =
false;
1860 TcpClient Client =
null;
1861 DataPort PassivePort =
null;
1867 CancellationTokenSource Prev = this.cancelToken;
1868 this.cancelToken =
new CancellationTokenSource();
1869 this.cancelled =
new TaskCompletionSource<bool>();
1872 if (this.ipExtension is
null)
1874 if (this.passiveStreams is
null)
1875 return await this.BeginWrite(
"425 Unable to open data connection.\r\n",
null,
null);
1878 Message =
"150 Awaiting passive data connection.\r\n";
1880 Message =
"150 Awaiting passive encrypted data connection.\r\n";
1882 if (!await this.BeginWrite(Message,
null,
null))
1885 PassiveRecord Rec =
new PassiveRecord();
1887 this.passiveStreams.
Queue(Rec);
1889 Task
_ = Task.Delay(10000).ContinueWith((
_) =>
1891 if (!Rec.DataConnectionEstablished)
1892 Rec.PassiveChannel.TrySetResult(
false);
1894 return Task.CompletedTask;
1897 this.cancelToken.Token.Register(() =>
1899 Rec.PassiveChannel.TrySetResult(
false);
1902 if (!await Rec.PassiveChannel.Task)
1904 if (this.cancelToken.IsCancellationRequested)
1906 this.cancelled.TrySetResult(
true);
1907 return await this.BeginWrite(
"426 Transfer aborted.\r\n",
null,
null);
1911 this.cancelled.TrySetResult(
false);
1912 return await this.BeginWrite(
"425 No data connection received.\r\n",
null,
null);
1916 this.Information(
"Starting transfer on passive connection.");
1918 PassivePort = Rec.PassivePort;
1919 Client = Rec.PassiveClient;
1923 EP = this.ipExtension;
1926 Message =
"150 Opening data connection.\r\n";
1928 Message =
"150 Opening encrypted data connection.\r\n";
1930 if (!await this.BeginWrite(Message,
null,
null))
1934 Client =
new TcpClient();
1935 await Client.ConnectAsync(EP.Address, EP.Port);
1941 if (this.IsEncrypted)
1943 Client2.
Information(
"Switching to TLS. (Client Certificates: " +
1944 this.clientCertificates.ToString() +
1945 ", Trust Certificates: " +
this.trustCertificates.ToString() +
")");
1948 Crypto.
SecureTls,
this.clientCertificates,
null,
this.trustCertificates,
1951 if (!(this.Client.RemoteCertificate is
null) &&
1953 this.Client.RemoteCertificate.GetPublicKeyString() !=
1956 return await this.BeginWrite(
"425 Certificate on data channel did not match certificate on control channel.\r\n",
null,
null);
1962 if (!
string.IsNullOrEmpty(TextContent))
1965 this.TransmitText(TextContent);
1967 this.ReceiveText(TextContent);
1970 if (!this.restartPoint.HasValue)
1971 Content.Position = Send ? 0 : Content.Length;
1974 long Pos = this.restartPoint.Value;
1975 this.restartPoint =
null;
1977 if (Pos > Content.Length)
1979 this.restartPoint =
null;
1980 return await this.BeginWrite(
"426 Restart point beyond EOF.\r\n",
null,
null);
1983 Content.Position = Pos;
1989 Result = await this.CopyAsync(Content, Client2.
Stream, TextContent is
null,
false);
1992 Result = await this.CopyAsync(Client2.
Stream, Content,
true,
true);
1994 if (Result == CopyResult.Ok)
1996 long NewSize = this.currentStorage + Content.Length - InitialSize;
1998 if (this.maxStorage >= 0 && NewSize > this.maxStorage)
1999 Result = CopyResult.Overflow;
2001 this.currentStorage = NewSize;
2005 if (Result != CopyResult.Ok && DisposeStream && !Send && Content is FileStream fs)
2007 string Name = fs.Name;
2010 DisposeStream =
false;
2012 if (File.Exists(Name))
2015 this.currentStorage -= InitialSize;
2021 case CopyResult.Failed:
2022 return await this.BeginWrite(
"426 Transfer failed.\r\n",
null,
null);
2024 case CopyResult.Aborted:
2025 return await this.BeginWrite(
"426 Transfer aborted.\r\n",
null,
null);
2027 case CopyResult.UnexpectedEnd:
2028 return await this.BeginWrite(
"426 Unexpected end of file.\r\n",
null,
null);
2030 case CopyResult.Overflow:
2031 return await this.BeginWrite(
"552 Exceeded storage allocation.\r\n",
null,
null);
2035 await Client2.
Stream.FlushAsync();
2036 return await this.BeginWrite(
"226 Transfer complete.\r\n",
null,
null);
2039 catch (Exception ex)
2043 if (!(Client2 is
null) && ex is AuthenticationException)
2044 await this.Server.LoginFailure(ex, Client2, Client2.
RemoteEndPoint);
2047 return await this.BeginWrite(
"425 Unable to open connection: " + FirstRow(ex) +
".\r\n",
null,
null);
2049 return await this.BeginWrite(
"425 Unable to transfer file: " + FirstRow(ex) +
".\r\n",
null,
null);
2055 PassivePort?.Dispose();
2059 await Content.FlushAsync();
2063 if (Client2 is
null)
2071 catch (Exception ex)
2078 private enum CopyResult
2087 private async Task<CopyResult> CopyAsync(Stream From, Stream To,
bool SniffBinary,
2092 CancellationTokenSource Cancel = this.cancelToken;
2093 long BytesLeft = UntilClose ? long.MaxValue : From.Length - From.Position;
2094 int BufSize = (int)Math.Min(65536, BytesLeft);
2095 byte[] Bin =
new byte[BufSize];
2098 while (UntilClose || BytesLeft > 0)
2100 NrRead = await From.ReadAsync(Bin, 0, (
int)Math.Min(BufSize, BytesLeft));
2104 return CopyResult.Ok;
2107 this.Error(
"Unexpected end of file.");
2108 return CopyResult.UnexpectedEnd;
2115 this.ReceiveBinary(NrRead);
2117 this.TransmitBinary(NrRead);
2120 await To.WriteAsync(Bin, 0, NrRead, Cancel.Token);
2123 BytesLeft -= NrRead;
2125 if (Cancel.IsCancellationRequested)
2127 this.cancelled.TrySetResult(
true);
2128 return CopyResult.Aborted;
2132 return CopyResult.Ok;
2134 catch (Exception ex)
2137 return CopyResult.Failed;
2141 private static string FirstRow(Exception ex)
2145 string s = ex.Message;
2147 int i = s.IndexOfAny(CRLF);
2154 private static readonly
char[] CRLF =
new char[] {
'\r',
'\n' };
2156 private static string ReadPrivilege(
string PathName)
2158 return FtpPrivilege(PathName, FtpServer.FtpReadPrivilegePrefix);
2161 private static string FtpPrivilege(
string PathName,
string Prefix)
2163 PathName = Path.GetFullPath(PathName);
2164 string Result = PathName.Replace(
':',
'.').Replace(
'/',
'.').Replace(
'\\',
'.');
2166 while (Result.Contains(
".."))
2167 Result = Result.Replace(
"..",
".");
2172 private static string WritePrivilege(
string PathName)
2174 return FtpPrivilege(PathName, FtpServer.FtpWritePrivilegePrefix);
2177 private async Task<string> CanLogin()
2179 LoginAuditor Auditor = this.Server.PersistenceLayer.Auditor;
2182 if (Auditor is
null)
2189 StringBuilder sb =
new StringBuilder();
2190 DateTime TP = Next.Value;
2191 DateTime Today = DateTime.Today;
2195 if (Next.Value == DateTime.MaxValue)
2197 sb.Append(
"This endpoint (");
2198 sb.Append(this.RemoteEndPoint);
2199 sb.Append(
") has been blocked from the system");
2203 sb.Append(
"Too many failed login attempts in a row registered. Try again after ");
2204 sb.Append(TP.ToLongTimeString());
2206 if (TP.Date != Today)
2208 if (TP.Date == Today.AddDays(1))
2209 sb.Append(
" tomorrow");
2213 sb.Append(TP.ToShortDateString());
2218 sb.Append(
". Remote Endpoint: ");
2219 sb.Append(this.RemoteEndPoint);
2222 return sb.ToString();
2246 await this.SetState(State);
2255 await base.SetAccount(Account);
2258 this.maxStorage = await this.Server.PersistenceLayer.GetMaxStorage(Account.
UserName);
2259 this.currentStorage = CalcStorage(this.rootFolder);
2262 private static long CalcStorage(
string Folder)
2266 if (Directory.Exists(Folder))
2268 foreach (
string FileName
in Directory.GetFiles(Folder))
2270 FileInfo Info =
new FileInfo(FileName);
2271 Result += Info.Length;
2274 foreach (
string SubFolder
in Directory.GetDirectories(Folder))
2275 Result += CalcStorage(SubFolder);
2290 return base.CheckLive();
2299 this.user =
string.Empty;
2300 this.rootFolder =
string.Empty;
2301 this.localFolder =
string.Empty;
2304 this.ipExtension =
null;
2306 this.onlyPassive =
false;
2308 base.ResetState(Authenticated);
2317 return this.BeginWrite(
"530 Not logged in.\r\n",
null,
null);
2326 return this.BeginWrite(
"530 Account disabled.\r\n",
null,
null);
2335 return this.BeginWrite(
"530 Malformed request.\r\n",
null,
null);
2346 if (
string.IsNullOrEmpty(ChallengeBase64))
2347 return this.BeginWrite(
"334\r\n",
null,
null);
2349 return this.BeginWrite(
"334 " + ChallengeBase64 +
"\r\n",
null,
null);
2359 return this.BeginWrite(
"230 User logged in, proceed.\r\n",
null,
null);
Helps with parsing of commong data types.
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Static class managing encoding and decoding of internet content.
static string GetContentType(string FileExtension)
Gets the content type of an item, given its file extension. It uses the TryGetContentType to see if a...
Static class managing the application event log. Applications and services log events on this static ...
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
void Bind()
Binds to a TcpClient that was already connected when provided to the constructor.
TcpClient Client
Underlying TcpClient object.
string RemoteEndPoint
Remote End-point of connection. This corresponds to the IP Endpoint of the remote party in normal cas...
Stream Stream
Stream object currently being used.
virtual Task DisposeAsync()
Disposes of the object asynchronously. The underlying TcpClient is either disposed directly,...
void Dispose()
Disposes of the object. The underlying TcpClient is either disposed directly, or when asynchronous op...
static string GetDomainFromSubject(string Subject)
Extracts the domain name from a certificate subject string.
X509Certificate RemoteCertificate
Certificate used by the remote endpoint.
Task UpgradeToTlsAsServer(X509Certificate ServerCertificate)
Upgrades a server connection to TLS.
bool IsEncrypted
If connection is encrypted or not.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
void Error(string Error)
Called to inform the viewer of an error state.
void Information(string Comment)
Called to inform the viewer of something.
Task RemoveRange(IEnumerable< ISniffer > Sniffers)
Removes a set of sniffers, if registered.
Abstract base class for FTP client connections.
bool IsEncrypted
If the connection is encrypted.
IAccount Account
Account of authenticated user.
Task< bool > BeginWrite(string Text, EventHandlerAsync< DeliveryEventArgs > Callback, object State)
Starts sending a text command to the client.
void ResetState()
Resets the state of the connection.
string RemoteEndPoint
Remote endpoint.
BinaryTcpClient Client
Underlying TCP connection.
FtpServer Server
FTP Server serving the client.
bool Disposed
If object has been disposed.
Class managing an FTP control connection.
EventHandlerAsync< FtpControlConnectionState > OnStateChanged
Event raised whenever the internal state of the connection changes.
FtpControlConnectionState State
Current state of connection.
FtpClientControlConnection(BinaryTcpClient Client, FtpServer Server, IFtpServerPersistenceLayer Persistence, ClientCertificates ClientCertificates, bool TrustCertificates, params ISniffer[] Sniffers)
Class managing an FTP control connection.
override Task< bool > SaslErrorMalformedRequest()
Is called when a an authentication attempt has been made using a malformed request.
override async Task ErrorAndClose()
Closes the connection due to an error.
override void ResetState(bool Authenticated)
Resets the state of the connection.
override bool CheckLive()
Checks if the connection is live.
override Task< bool > SaslErrorNotAuthorized()
Is called when a failed authentication attempt has been made.
override async Task SetAccount(IAccount Account)
Sets the account for the connection.
override async Task AfterUpgradeToTls(object Item)
Called after upgrading to TLS.
async override Task< bool > ParseIncoming(bool ConstantBuffer, byte[] Data, int Offset, int NrRead)
Parses incoming binary data.
override Task< bool > SaslChallenge(string ChallengeBase64)
Is called when a an authentication challenge has been received.
override Task< bool > SaslSuccess(string ProofBase64)
Is called when a a successful authentication response has been received.
override Task< bool > SaslErrorAccountDisabled()
Is called when a an authentication attempt has been made using a disabled account.
override async Task< object > BeforeUpgradeToTls()
Called before upgrading to TLS.
async override Task DisposeAsync()
Closes the connection and disposes of all resources.
Implements a simple FTP Server, as defined in:
bool AllowSafeDataChannel
If data can be transferred in the clear, with integrity protection
bool AllowConfidentialDataChannel
If data can be transferred encrypted.
bool AllowPrivateDataChannel
If data can be transferred encrypted, with integrity protection.
bool HasDataPorts
If FTP Server has data ports defined.
bool Disposed
If the class is disposed.
bool AllowClearDataChannel
If data can be transferred in the clear
void ReleaseDataPort(int Port)
Releases a port back to the pool of available data ports.
async Task< int?> GetDataPort(int Timeout)
Gets a free data port for use in a passive-mode data connection.
bool EncryptionRequired
If C2S encryption is requried.
Module that controls the life cycle of communication.
static bool Stopping
If the system is stopping.
Asynchronous First-in-First-out (FIFO) Queue, for use when transporting items of type T between task...
Task< T > Wait()
Waits indefinitely (or until queue is disposed) for an item to be available. If Queue is disposed,...
void Queue(T Item)
Queues an item for processing by adding it last in the queue. No information is returned wether the i...
void Dispose()
IDisposable.Dispose
Helper methods for encrypting and decrypting streams of data.
const SslProtocols SecureTls
TLS 1.2 & 1.3
Contains methods for simple hash calculations.
static string ComputeSHA512HashString(byte[] Data)
Computes the SHA-512 hash of a block of binary data.
static string ComputeSHA256HashString(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
static string ComputeSHA1HashString(byte[] Data)
Computes the SHA-1 hash of a block of binary data.
static string ComputeMD5HashString(byte[] Data)
Computes the MD5 hash of a block of binary data.
Class that monitors login events, and help applications determine malicious intent....
async Task< DateTime?> GetEarliestLoginOpportunity(string RemoteEndPoint, string Protocol)
Checks when a remote endpoint can login.
static async void Success(string Message, string UserName, string RemoteEndPoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a successful login attempt.
static void Fail(string Message, string UserName, string RemoteEndPoint, string Protocol)
Handles a failed login attempt.
Login state information relating to a remote endpoint
Persistence layer for FTP servers.
Task< string > GetRootFolder(string UserName)
Gets the root folder of a user.
Interface for SMTP user accounts.
CaseInsensitiveString UserName
User Name
bool Enabled
If the account is enabled.
bool HasPrivilege(string PrivilegeID)
If the account has a given privilege.
Task< IAccount > GetAccount(CaseInsensitiveString UserName)
Method to call to fetch account information.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
Overflow
Overflow handling.
FtpControlConnectionState
State of FTP connection.
ProtectionLevel
Data Channel Protection Level
TransferMode
Data Transfer Mode
RepresentationType
Representation type of files.
ClientCertificates
Client Certificate Options
Prefix
SI prefixes. http://physics.nist.gov/cuu/Units/prefixes.html
AddressType
NCAP address type.