Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f81d09d540 | |||
| 351030ebcd | |||
| bf42471fe5 | |||
| a28bff79fb | |||
| 594e7eb1dd | |||
| e8baee2d1e | |||
| 2d517ea090 | |||
| 3f80eef90b | |||
| d77bff4531 | |||
| eb0d9c8847 | |||
| e174f81fcc | |||
| 5a03dbf2a3 | |||
| 94431aebb8 | |||
| ffb62b7737 | |||
| f47100e940 | |||
| bbd83492a9 | |||
| 01fbd0b74e | |||
| b2189e63df | |||
| 23dfbe8788 | |||
| c78181a1de | |||
| f39dd7b764 | |||
| bac6728971 | |||
| 8c2b32d6b1 | |||
| a2f867480a | |||
| 8ce87a3cfc | |||
| 5face5379c |
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using DearFTP.Utils;
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Net;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace DearFTP.Configurations
|
namespace DearFTP.Configurations
|
||||||
@@ -31,6 +32,7 @@ namespace DearFTP.Configurations
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfiguration Server { get; set; } = new ServerConfiguration();
|
public ServerConfiguration Server { get; set; } = new ServerConfiguration();
|
||||||
|
public TlsConfiguration Tls { get; set; } = new TlsConfiguration();
|
||||||
public Share[] Shares { get; set; } = Array.Empty<Share>();
|
public Share[] Shares { get; set; } = Array.Empty<Share>();
|
||||||
public User[] Users { get; set; } = Array.Empty<User>();
|
public User[] Users { get; set; } = Array.Empty<User>();
|
||||||
|
|
||||||
@@ -41,6 +43,16 @@ namespace DearFTP.Configurations
|
|||||||
|
|
||||||
public bool Check()
|
public bool Check()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Server.Host))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Host address is missing.");
|
||||||
|
}
|
||||||
|
if (!IPAddress.TryParse(Server.Host, out var _))
|
||||||
|
{
|
||||||
|
Server.Host = Dns.GetHostEntry(Server.Host).AddressList.First().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var share in Shares)
|
foreach (var share in Shares)
|
||||||
{
|
{
|
||||||
share.Name = Path.GetFileName(share.Path);
|
share.Name = Path.GetFileName(share.Path);
|
||||||
@@ -58,12 +70,61 @@ namespace DearFTP.Configurations
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Tls.ForceTls && !Tls.AllowTls)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Tls is forced but not allowed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Server.ImplicitFtpsPort != 0 && !Tls.AllowTls)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Implicit FTPS is activated but TLS is not allowed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Tls.AllowTls)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(Tls.CertificatePath))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Tls is activated, but no certificate is specified.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var certificate = new X509Certificate2(Tls.CertificatePath);
|
||||||
|
|
||||||
|
if (!certificate.HasPrivateKey)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(Tls.PrivateKeyPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine("No private key loaded and no path is specified.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateKeyBytes = OpenSslKey.DecodePkcs8PrivateKey(File.ReadAllText(Tls.PrivateKeyPath));
|
||||||
|
var privateKey = OpenSslKey.DecodePrivateKeyInfo(privateKeyBytes);
|
||||||
|
|
||||||
|
certificate = certificate.CopyWithPrivateKey(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tls.X509Certificate = certificate;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Can't load certificate: {e.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
var serializer = new SerializerBuilder().EmitDefaults().Build();
|
var serializer = new SerializerBuilder().Build();
|
||||||
|
|
||||||
File.WriteAllText(ConfigurationPath, serializer.Serialize(this));
|
File.WriteAllText(ConfigurationPath, serializer.Serialize(this));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using System;
|
namespace DearFTP.Configurations
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Configurations
|
|
||||||
{
|
{
|
||||||
class ServerConfiguration
|
class ServerConfiguration
|
||||||
{
|
{
|
||||||
|
public string Host { get; set; } = "127.0.0.1";
|
||||||
public ushort Port { get; set; } = 21;
|
public ushort Port { get; set; } = 21;
|
||||||
|
public ushort ForceDataPort { get; set; } = 0;
|
||||||
|
public ushort ImplicitFtpsPort { get; set; } = 0;
|
||||||
public string MOTD { get; set; } = "DearFTP v0.1";
|
public string MOTD { get; set; } = "DearFTP v0.1";
|
||||||
public string LoginMessage { get; set; } = "Logged in as %user%";
|
public string LoginMessage { get; set; } = "Logged in as %user%";
|
||||||
|
|
||||||
@@ -14,5 +13,6 @@ namespace DearFTP.Configurations
|
|||||||
public string LogFilePath { get; set; } = "log.txt";
|
public string LogFilePath { get; set; } = "log.txt";
|
||||||
|
|
||||||
public string Guest { get; set; } = "";
|
public string Guest { get; set; } = "";
|
||||||
|
public bool BypassGuestPassword { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System;
|
using YamlDotNet.Serialization;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using YamlDotNet.Serialization;
|
|
||||||
|
|
||||||
namespace DearFTP.Configurations
|
namespace DearFTP.Configurations
|
||||||
{
|
{
|
||||||
|
|||||||
16
DearFTP/Configurations/TlsConfiguration.cs
Normal file
16
DearFTP/Configurations/TlsConfiguration.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
|
namespace DearFTP.Configurations
|
||||||
|
{
|
||||||
|
class TlsConfiguration
|
||||||
|
{
|
||||||
|
public bool AllowTls { get; set; } = false;
|
||||||
|
public bool ForceTls { get; set; } = false;
|
||||||
|
public string CertificatePath { get; set; } = "";
|
||||||
|
public string PrivateKeyPath { get; set; } = "";
|
||||||
|
|
||||||
|
[YamlIgnore()]
|
||||||
|
public X509Certificate2 X509Certificate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Configurations
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Configurations
|
|
||||||
{
|
{
|
||||||
class User
|
class User
|
||||||
{
|
{
|
||||||
|
|||||||
21
DearFTP/Connection/Commands/AbortCommand.cs
Normal file
21
DearFTP/Connection/Commands/AbortCommand.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DearFTP.Connection.Commands
|
||||||
|
{
|
||||||
|
class AbortCommand : ICommand
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; } = new string[]
|
||||||
|
{
|
||||||
|
"ABOR"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
|
{
|
||||||
|
session.DataConnection.Close();
|
||||||
|
|
||||||
|
stream.Send(ResponseCode.CloseDataConnection, "Data connection closed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
DearFTP/Connection/Commands/AuthCommand.cs
Normal file
34
DearFTP/Connection/Commands/AuthCommand.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
namespace DearFTP.Connection.Commands
|
||||||
|
{
|
||||||
|
class AuthCommand : ICommand
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; } = new string[]
|
||||||
|
{
|
||||||
|
"AUTH"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
|
{
|
||||||
|
string protocol = argument.ToUpper();
|
||||||
|
|
||||||
|
if (protocol != "TLS" && protocol != "TLS-C" && protocol != "SSL")
|
||||||
|
{
|
||||||
|
stream.Send(ResponseCode.ArgumentNotImplemented, "Invalid argument, expected 'TLS'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsConfiguration = session.Configuration.Tls;
|
||||||
|
|
||||||
|
if (!tlsConfiguration.AllowTls)
|
||||||
|
{
|
||||||
|
session.LogError("Tls", "Client tried to use Tls but Tls is desactivated");
|
||||||
|
stream.Send(ResponseCode.NotImplemented, "Tls is not enabled on this server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Send(ResponseCode.AcceptAuthenticationMechanism, "Tls activated.");
|
||||||
|
|
||||||
|
session.ActivateTls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class ClntCommand : ICommand
|
class ClntCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
@@ -9,21 +7,28 @@ namespace DearFTP.Connection.Commands
|
|||||||
{
|
{
|
||||||
public ICommand[] Commands { get; } = new ICommand[]
|
public ICommand[] Commands { get; } = new ICommand[]
|
||||||
{
|
{
|
||||||
|
new AbortCommand(),
|
||||||
|
new AuthCommand(),
|
||||||
new ClntCommand(),
|
new ClntCommand(),
|
||||||
new CwdCommand(),
|
new CwdCommand(),
|
||||||
new DeleteCommand(),
|
new DeleteCommand(),
|
||||||
|
new ExtendedPassiveCommand(),
|
||||||
new HelpCommand(),
|
new HelpCommand(),
|
||||||
new FeaturesCommand(),
|
new FeaturesCommand(),
|
||||||
new FileModificationTimeCommand(),
|
new FileModificationTimeCommand(),
|
||||||
new ListCommand(),
|
new ListCommand(),
|
||||||
new ListMachineCommand(),
|
new ListMachineCommand(),
|
||||||
new MakeDirectoryCommand(),
|
new MakeDirectoryCommand(),
|
||||||
|
new OptionsCommand(),
|
||||||
new ParentDirectoryCommand(),
|
new ParentDirectoryCommand(),
|
||||||
new PassiveCommand(),
|
new PassiveCommand(),
|
||||||
|
new ProtectionBufferSizeCommand(),
|
||||||
|
new ProtectionCommand(),
|
||||||
new PwdCommand(),
|
new PwdCommand(),
|
||||||
new QuitCommand(),
|
new QuitCommand(),
|
||||||
new RenameCommand(),
|
new RenameCommand(),
|
||||||
new RetrieveCommand(),
|
new RetrieveCommand(),
|
||||||
|
new RestartCommand(),
|
||||||
new SiteCommand(),
|
new SiteCommand(),
|
||||||
new SizeCommand(),
|
new SizeCommand(),
|
||||||
new StoreCommand(),
|
new StoreCommand(),
|
||||||
@@ -41,15 +46,21 @@ namespace DearFTP.Connection.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
var commandExecutor = Commands.FirstOrDefault(x => x.Aliases.Contains(command, StringComparer.OrdinalIgnoreCase));
|
var commandExecutor = Commands.FirstOrDefault(x => x.Aliases.Contains(command, StringComparer.OrdinalIgnoreCase));
|
||||||
|
var stream = session.FtpStream;
|
||||||
|
|
||||||
if (commandExecutor == null)
|
if (commandExecutor == null)
|
||||||
{
|
{
|
||||||
session.FtpStream.Send(ResponseCode.NotImplemented, $"Command '{command}' not implemented or invalid");
|
stream.Send(ResponseCode.NotImplemented, $"Command '{command}' not implemented or invalid");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
commandExecutor.Execute(session, session.FtpStream, command, argument);
|
if (session.Configuration.Tls.ForceTls && !session.IsTlsProtected && command.ToUpper() != "AUTH")
|
||||||
|
{
|
||||||
|
stream.Send(ResponseCode.InsufficientProtection, "Not protected connection is not allowed on this server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandExecutor.Execute(session, stream, command, argument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class CwdCommand : ICommand
|
class CwdCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
using DearFTP.Configurations;
|
using DearFTP.Utils;
|
||||||
using DearFTP.Utils;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
26
DearFTP/Connection/Commands/ExtendedPassiveCommand.cs
Normal file
26
DearFTP/Connection/Commands/ExtendedPassiveCommand.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DearFTP.Connection.Commands
|
||||||
|
{
|
||||||
|
class ExtendedPassiveCommand : ICommand
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; } = new string[]
|
||||||
|
{
|
||||||
|
"EPSV"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
|
{
|
||||||
|
session.DataConnection.Create();
|
||||||
|
|
||||||
|
int port = session.DataConnection.Port;
|
||||||
|
|
||||||
|
stream.Send(ResponseCode.ExtendedPassiveMode, $"Entering Extended Passive Mode (|||{port}|)");
|
||||||
|
|
||||||
|
session.DataConnection.AcceptClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class FeaturesCommand : ICommand
|
class FeaturesCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -18,9 +14,12 @@ namespace DearFTP.Connection.Commands
|
|||||||
(
|
(
|
||||||
ResponseCode.SystemStatusOrHelpReply,
|
ResponseCode.SystemStatusOrHelpReply,
|
||||||
"Features:",
|
"Features:",
|
||||||
|
"AUTH TLS",
|
||||||
"MDTM",
|
"MDTM",
|
||||||
"MLST",
|
"MLST",
|
||||||
"PASV",
|
"PASV",
|
||||||
|
"PBSZ",
|
||||||
|
"PROT",
|
||||||
"REST STREAM",
|
"REST STREAM",
|
||||||
"SIZE",
|
"SIZE",
|
||||||
"TVFS",
|
"TVFS",
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class HelpCommand : ICommand
|
class HelpCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -22,9 +18,11 @@ namespace DearFTP.Connection.Commands
|
|||||||
"ABOR",
|
"ABOR",
|
||||||
"ALLO",
|
"ALLO",
|
||||||
"APPE",
|
"APPE",
|
||||||
|
"AUTH",
|
||||||
"CDUP",
|
"CDUP",
|
||||||
"CWD",
|
"CWD",
|
||||||
"DELE",
|
"DELE",
|
||||||
|
"EPSV",
|
||||||
"FEAT",
|
"FEAT",
|
||||||
"HELP",
|
"HELP",
|
||||||
"LIST",
|
"LIST",
|
||||||
@@ -38,7 +36,9 @@ namespace DearFTP.Connection.Commands
|
|||||||
"OPTS",
|
"OPTS",
|
||||||
"PASS",
|
"PASS",
|
||||||
"PASV",
|
"PASV",
|
||||||
|
"PBSZ",
|
||||||
"PORT",
|
"PORT",
|
||||||
|
"PROT",
|
||||||
"PWD",
|
"PWD",
|
||||||
"QUIT",
|
"QUIT",
|
||||||
"REIN",
|
"REIN",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
interface ICommand
|
interface ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
@@ -31,6 +32,8 @@ namespace DearFTP.Connection.Commands
|
|||||||
|
|
||||||
stream.Send(ResponseCode.FileStatusOK, "Listing coming.");
|
stream.Send(ResponseCode.FileStatusOK, "Listing coming.");
|
||||||
|
|
||||||
|
dataConnection.Authenticate();
|
||||||
|
|
||||||
string path = null;
|
string path = null;
|
||||||
bool humanReadable = false;
|
bool humanReadable = false;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Net.Security;
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
@@ -72,6 +72,8 @@ namespace DearFTP.Connection.Commands
|
|||||||
|
|
||||||
stream.Send(ResponseCode.FileStatusOK, "Listing coming.");
|
stream.Send(ResponseCode.FileStatusOK, "Listing coming.");
|
||||||
|
|
||||||
|
dataConnection.Authenticate();
|
||||||
|
|
||||||
var dataStream = new FtpStream(dataConnection.Stream);
|
var dataStream = new FtpStream(dataConnection.Stream);
|
||||||
|
|
||||||
foreach (var info in infos)
|
foreach (var info in infos)
|
||||||
@@ -114,7 +116,7 @@ namespace DearFTP.Connection.Commands
|
|||||||
{
|
{
|
||||||
string currentDirectory = navigablePath.GetDirectoryPath("");
|
string currentDirectory = navigablePath.GetDirectoryPath("");
|
||||||
|
|
||||||
yield return GenerateInfo(new DirectoryInfo(currentDirectory), writeable, "cdir");
|
yield return GenerateInfo(new DirectoryInfo(currentDirectory), writeable, "cdir");
|
||||||
|
|
||||||
if (virtualDirectory.Count(x => x == '/') > 1)
|
if (virtualDirectory.Count(x => x == '/') > 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
@@ -22,33 +20,33 @@ namespace DearFTP.Connection.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newDirectory = session.NavigablePath.GetNewSystemFilePath(argument);
|
var (navigablePath, realPath) = session.NavigablePath.GetNewSystemFilePath(argument);
|
||||||
|
|
||||||
if (newDirectory.navigablePath.CurrentDirectory == "/")
|
if (navigablePath.CurrentDirectory == "/")
|
||||||
{
|
{
|
||||||
stream.Send(ResponseCode.FileUnavailable, "Can't modify the root.");
|
stream.Send(ResponseCode.FileUnavailable, "Can't modify the root.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newDirectory.realPath == null)
|
if (realPath == null)
|
||||||
{
|
{
|
||||||
stream.Send(ResponseCode.FileUnavailable, "Invalid destination path.");
|
stream.Send(ResponseCode.FileUnavailable, "Invalid destination path.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(newDirectory.realPath) || Directory.Exists(newDirectory.realPath))
|
if (File.Exists(realPath) || Directory.Exists(realPath))
|
||||||
{
|
{
|
||||||
stream.Send(ResponseCode.FileUnavailable, "Destination path already exists.");
|
stream.Send(ResponseCode.FileUnavailable, "Destination path already exists.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.WritablesShares.Contains(newDirectory.navigablePath.CurrentShare))
|
if (!session.WritablesShares.Contains(navigablePath.CurrentShare))
|
||||||
{
|
{
|
||||||
stream.Send(ResponseCode.FileUnavailable, "You don't have write access to this file.");
|
stream.Send(ResponseCode.FileUnavailable, "You don't have write access to this file.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(newDirectory.realPath);
|
Directory.CreateDirectory(realPath);
|
||||||
|
|
||||||
stream.Send(ResponseCode.FileActionOK, "Directory successfully created.");
|
stream.Send(ResponseCode.FileActionOK, "Directory successfully created.");
|
||||||
}
|
}
|
||||||
|
|||||||
21
DearFTP/Connection/Commands/OptionsCommand.cs
Normal file
21
DearFTP/Connection/Commands/OptionsCommand.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace DearFTP.Connection.Commands
|
||||||
|
{
|
||||||
|
class OptionsCommand : ICommand
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; } = new string[]
|
||||||
|
{
|
||||||
|
"OPTS"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
|
{
|
||||||
|
if (argument.ToUpper().Contains("UTF"))
|
||||||
|
{
|
||||||
|
stream.Send(ResponseCode.OK, "UTF8 supported on this server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Send(ResponseCode.ArgumentsError, "Requested option not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class ParentDirectoryCommand : ICommand
|
class ParentDirectoryCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using DearFTP.Utils.Extensions;
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
@@ -20,7 +18,17 @@ namespace DearFTP.Connection.Commands
|
|||||||
|
|
||||||
int port = session.DataConnection.Port;
|
int port = session.DataConnection.Port;
|
||||||
var portBytes = BitConverter.GetBytes((ushort)port).Reverse().Select(x => x.ToString());
|
var portBytes = BitConverter.GetBytes((ushort)port).Reverse().Select(x => x.ToString());
|
||||||
string remote = string.Join(',', session.IP.Split('.').Concat(portBytes));
|
|
||||||
|
string remote;
|
||||||
|
|
||||||
|
if (session.RemoteIP.IsInternal())
|
||||||
|
{
|
||||||
|
remote = string.Join(',', session.LocalIP.ToString().Split('.').Concat(portBytes));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remote = string.Join(',', session.Configuration.Server.Host.Split('.').Concat(portBytes));
|
||||||
|
}
|
||||||
|
|
||||||
stream.Send(ResponseCode.PassiveMode, $"Entering Passive Mode ({remote})");
|
stream.Send(ResponseCode.PassiveMode, $"Entering Passive Mode ({remote})");
|
||||||
|
|
||||||
|
|||||||
21
DearFTP/Connection/Commands/ProtectionBufferSizeCommand.cs
Normal file
21
DearFTP/Connection/Commands/ProtectionBufferSizeCommand.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace DearFTP.Connection.Commands
|
||||||
|
{
|
||||||
|
class ProtectionBufferSizeCommand : ICommand
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; } = new string[]
|
||||||
|
{
|
||||||
|
"PBSZ"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
|
{
|
||||||
|
if (argument != "0")
|
||||||
|
{
|
||||||
|
stream.Send(ResponseCode.ArgumentsError, "Invalid argument, expected '0'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Send(ResponseCode.OK, "Ok.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
DearFTP/Connection/Commands/ProtectionCommand.cs
Normal file
36
DearFTP/Connection/Commands/ProtectionCommand.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
namespace DearFTP.Connection.Commands
|
||||||
|
{
|
||||||
|
class ProtectionCommand : ICommand
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; } = new string[]
|
||||||
|
{
|
||||||
|
"PROT"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
|
{
|
||||||
|
var dataConnection = session.DataConnection;
|
||||||
|
|
||||||
|
switch (argument.ToUpper())
|
||||||
|
{
|
||||||
|
case "C":
|
||||||
|
if (dataConnection.IsTlsProtected)
|
||||||
|
{
|
||||||
|
session.DataConnection.DesactivateTls();
|
||||||
|
}
|
||||||
|
stream.Send(ResponseCode.OK, "Data protection cleared.");
|
||||||
|
break;
|
||||||
|
case "P":
|
||||||
|
if (!dataConnection.IsTlsProtected)
|
||||||
|
{
|
||||||
|
session.DataConnection.ActivateTls();
|
||||||
|
}
|
||||||
|
stream.Send(ResponseCode.OK, "Data protection set.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
stream.Send(ResponseCode.ArgumentsError, "Invalid argument.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class PwdCommand : ICommand
|
class PwdCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class QuitCommand : ICommand
|
class QuitCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
23
DearFTP/Connection/Commands/RestartCommand.cs
Normal file
23
DearFTP/Connection/Commands/RestartCommand.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace DearFTP.Connection.Commands
|
||||||
|
{
|
||||||
|
class RestartCommand : ICommand
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; } = new string[]
|
||||||
|
{
|
||||||
|
"REST"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
|
{
|
||||||
|
if (uint.TryParse(argument, out uint position))
|
||||||
|
{
|
||||||
|
session.RestartPosition = (int)position;
|
||||||
|
stream.Send(ResponseCode.PendingFurtherInformation, $"Restart position set to {position}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.Send(ResponseCode.ArgumentsError, "Specified position is not a valid number.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
@@ -18,7 +16,7 @@ namespace DearFTP.Connection.Commands
|
|||||||
{
|
{
|
||||||
var dataConnection = session.DataConnection;
|
var dataConnection = session.DataConnection;
|
||||||
|
|
||||||
if (!dataConnection.IsAvailable)
|
if (!dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
|
||||||
{
|
{
|
||||||
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
|
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
|
||||||
return;
|
return;
|
||||||
@@ -40,28 +38,52 @@ namespace DearFTP.Connection.Commands
|
|||||||
|
|
||||||
stream.Send(ResponseCode.FileStatusOK, "File coming.");
|
stream.Send(ResponseCode.FileStatusOK, "File coming.");
|
||||||
|
|
||||||
SendFile(dataConnection.Stream, realPath);
|
dataConnection.Authenticate();
|
||||||
|
|
||||||
|
if (dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
|
||||||
|
{
|
||||||
|
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int restartPosition = session.RestartPosition;
|
||||||
|
session.RestartPosition = 0;
|
||||||
|
|
||||||
|
SendFile(dataConnection.Stream, realPath, restartPosition);
|
||||||
|
|
||||||
dataConnection.Close();
|
dataConnection.Close();
|
||||||
|
|
||||||
stream.Send(ResponseCode.CloseDataConnection, "File sent.");
|
stream.Send(ResponseCode.CloseDataConnection, "File sent.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendFile(Stream stream, string path)
|
private void SendFile(Stream stream, string path, int offset)
|
||||||
{
|
{
|
||||||
using (var file = File.OpenRead(path))
|
using (var file = File.OpenRead(path))
|
||||||
{
|
{
|
||||||
|
if (offset != 0)
|
||||||
|
{
|
||||||
|
file.Seek(offset, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
Span<byte> buffer = stackalloc byte[BufferSize];
|
Span<byte> buffer = stackalloc byte[BufferSize];
|
||||||
long bytesToWrite = Math.Min(file.Length - file.Position, BufferSize);
|
long bytesToWrite = Math.Min(file.Length - file.Position, BufferSize);
|
||||||
|
|
||||||
while (file.Read(buffer) > 0)
|
while (file.Read(buffer) > 0)
|
||||||
{
|
{
|
||||||
if (bytesToWrite < BufferSize)
|
try
|
||||||
{
|
{
|
||||||
stream.Write(buffer.Slice(0, (int)bytesToWrite));
|
if (bytesToWrite < BufferSize)
|
||||||
break;
|
{
|
||||||
|
stream.Write(buffer.Slice(0, (int)bytesToWrite));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
stream.Write(buffer);
|
|
||||||
|
|
||||||
bytesToWrite = Math.Min(file.Length - file.Position, BufferSize);
|
bytesToWrite = Math.Min(file.Length - file.Position, BufferSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class SiteCommand : ICommand
|
class SiteCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using DearFTP.Utils;
|
using System.IO;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
@@ -13,20 +10,24 @@ namespace DearFTP.Connection.Commands
|
|||||||
|
|
||||||
public string[] Aliases { get; } = new string[]
|
public string[] Aliases { get; } = new string[]
|
||||||
{
|
{
|
||||||
"STOR"
|
"APPE",
|
||||||
|
"STOR",
|
||||||
|
"STOU"
|
||||||
};
|
};
|
||||||
|
|
||||||
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
public void Execute(Session session, FtpStream stream, string alias, string argument)
|
||||||
{
|
{
|
||||||
var dataConnection = session.DataConnection;
|
var dataConnection = session.DataConnection;
|
||||||
|
|
||||||
if (!dataConnection.IsAvailable)
|
if (!dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
|
||||||
{
|
{
|
||||||
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
|
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (navigablePath, realPath) = session.NavigablePath.GetNewSystemFilePath(argument);
|
string path = alias.ToUpper() == "STOU" ? Path.GetRandomFileName() : argument;
|
||||||
|
|
||||||
|
var (navigablePath, realPath) = session.NavigablePath.GetNewSystemFilePath(path);
|
||||||
|
|
||||||
if (realPath == null)
|
if (realPath == null)
|
||||||
{
|
{
|
||||||
@@ -48,16 +49,24 @@ namespace DearFTP.Connection.Commands
|
|||||||
|
|
||||||
stream.Send(ResponseCode.FileStatusOK, "Waiting file.");
|
stream.Send(ResponseCode.FileStatusOK, "Waiting file.");
|
||||||
|
|
||||||
ReceiveFile(dataConnection.Stream, realPath);
|
dataConnection.Authenticate();
|
||||||
|
|
||||||
|
if (dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
|
||||||
|
{
|
||||||
|
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReceiveFile(dataConnection.Stream, realPath, alias.ToUpper() == "APPE");
|
||||||
|
|
||||||
dataConnection.Close();
|
dataConnection.Close();
|
||||||
|
|
||||||
stream.Send(ResponseCode.CloseDataConnection, "File received.");
|
stream.Send(ResponseCode.CloseDataConnection, "File received.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReceiveFile(NetworkStream stream, string path)
|
private void ReceiveFile(Stream stream, string path, bool append)
|
||||||
{
|
{
|
||||||
using (var file = File.Open(path, FileMode.Create))
|
using (var file = File.Open(path, append ? FileMode.Append : FileMode.Create))
|
||||||
{
|
{
|
||||||
Span<byte> buffer = stackalloc byte[BufferSize];
|
Span<byte> buffer = stackalloc byte[BufferSize];
|
||||||
int readBytes;
|
int readBytes;
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class SystemCommand : ICommand
|
class SystemCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection.Commands
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
|
||||||
{
|
{
|
||||||
class TypeCommand : ICommand
|
class TypeCommand : ICommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using DearFTP.Utils;
|
using DearFTP.Utils;
|
||||||
|
using DearFTP.Utils.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection.Commands
|
namespace DearFTP.Connection.Commands
|
||||||
{
|
{
|
||||||
@@ -17,10 +16,10 @@ namespace DearFTP.Connection.Commands
|
|||||||
{
|
{
|
||||||
var configuration = FtpServer.Instance.Configuration;
|
var configuration = FtpServer.Instance.Configuration;
|
||||||
var user = configuration.Users.FirstOrDefault(x => x.Name == argument);
|
var user = configuration.Users.FirstOrDefault(x => x.Name == argument);
|
||||||
|
string guest = configuration.Server.Guest;
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
string guest = configuration.Server.Guest;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(guest))
|
if (string.IsNullOrWhiteSpace(guest))
|
||||||
{
|
{
|
||||||
@@ -32,7 +31,7 @@ namespace DearFTP.Connection.Commands
|
|||||||
|
|
||||||
user = configuration.Users.First(x => x.Name == guest);
|
user = configuration.Users.First(x => x.Name == guest);
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(user.Password))
|
if (!string.IsNullOrWhiteSpace(user.Password) && !(session.RemoteIP.IsInternal() && configuration.Server.BypassGuestPassword && user.Name == guest))
|
||||||
{
|
{
|
||||||
stream.Send(ResponseCode.NeedPassword, "Please specify the password.");
|
stream.Send(ResponseCode.NeedPassword, "Please specify the password.");
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection
|
|
||||||
{
|
|
||||||
class DataConnection
|
|
||||||
{
|
|
||||||
public TcpListener Listener { get; private set; }
|
|
||||||
public TcpClient Client { get; private set; }
|
|
||||||
public NetworkStream Stream { get; private set; }
|
|
||||||
public int Port => ((IPEndPoint)Listener.LocalEndpoint).Port;
|
|
||||||
public bool IsAvailable { get; private set; }
|
|
||||||
|
|
||||||
public DataConnection()
|
|
||||||
{
|
|
||||||
IsAvailable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Create()
|
|
||||||
{
|
|
||||||
// Clean old connections
|
|
||||||
if (Client?.Connected == true)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Listener = new TcpListener(IPAddress.Any, 0);
|
|
||||||
Listener.Start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AcceptClient()
|
|
||||||
{
|
|
||||||
Client = Listener.AcceptTcpClient();
|
|
||||||
Stream = Client.GetStream();
|
|
||||||
|
|
||||||
IsAvailable = true;
|
|
||||||
}
|
|
||||||
public async void AcceptClientAsync()
|
|
||||||
{
|
|
||||||
Client = await Listener.AcceptTcpClientAsync();
|
|
||||||
Stream = Client.GetStream();
|
|
||||||
|
|
||||||
IsAvailable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
IsAvailable = false;
|
|
||||||
|
|
||||||
Stream.Close();
|
|
||||||
Client.Close();
|
|
||||||
Listener.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
101
DearFTP/Connection/DynamicDataConnection.cs
Normal file
101
DearFTP/Connection/DynamicDataConnection.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DearFTP.Connection
|
||||||
|
{
|
||||||
|
class DynamicDataConnection : IDataConnection
|
||||||
|
{
|
||||||
|
public const int Timeout = 10_000;
|
||||||
|
|
||||||
|
public Stream Stream { get; private set; }
|
||||||
|
public bool IsTlsProtected { get; private set; }
|
||||||
|
|
||||||
|
public int Port => ((IPEndPoint)_listener.LocalEndpoint).Port;
|
||||||
|
public bool IsAvailable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_acceptTask == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (_acceptTask.Wait(Timeout))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_acceptTask.Dispose();
|
||||||
|
_acceptTask = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TcpListener _listener;
|
||||||
|
private TcpClient _client;
|
||||||
|
private Task _acceptTask;
|
||||||
|
|
||||||
|
public DynamicDataConnection()
|
||||||
|
{
|
||||||
|
IsTlsProtected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create()
|
||||||
|
{
|
||||||
|
// Clean old connections
|
||||||
|
if (_client?.Connected == true)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
_listener = new TcpListener(IPAddress.Any, 0);
|
||||||
|
_listener.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AcceptClient()
|
||||||
|
{
|
||||||
|
_acceptTask = _listener.AcceptTcpClientAsync().ContinueWith(t =>
|
||||||
|
{
|
||||||
|
_client = t.Result;
|
||||||
|
|
||||||
|
if (IsTlsProtected)
|
||||||
|
{
|
||||||
|
Stream = new SslStream(_client.GetStream(), false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stream = _client.GetStream();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Authenticate()
|
||||||
|
{
|
||||||
|
if (IsTlsProtected)
|
||||||
|
{
|
||||||
|
((SslStream)Stream).AuthenticateAsServer(FtpServer.Instance.Configuration.Tls.X509Certificate, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ActivateTls()
|
||||||
|
{
|
||||||
|
IsTlsProtected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DesactivateTls()
|
||||||
|
{
|
||||||
|
IsTlsProtected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
Stream.Close();
|
||||||
|
_client.Close();
|
||||||
|
_listener.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace DearFTP.Connection
|
namespace DearFTP.Connection
|
||||||
@@ -10,18 +9,18 @@ namespace DearFTP.Connection
|
|||||||
{
|
{
|
||||||
public const int BUFFER_SIZE = 4096;
|
public const int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
public NetworkStream NetworkStream { get; }
|
public Stream Stream { get; }
|
||||||
|
|
||||||
public FtpStream(NetworkStream networkStream)
|
public FtpStream(Stream stream)
|
||||||
{
|
{
|
||||||
NetworkStream = networkStream;
|
Stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (string command, string argument) Receive()
|
public (string command, string argument) Receive()
|
||||||
{
|
{
|
||||||
var buffer = new byte[BUFFER_SIZE];
|
var buffer = new byte[BUFFER_SIZE];
|
||||||
|
|
||||||
int readBytes = NetworkStream.Read(buffer, 0, buffer.Length);
|
int readBytes = Stream.Read(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
if (readBytes == 0)
|
if (readBytes == 0)
|
||||||
{
|
{
|
||||||
@@ -49,14 +48,14 @@ namespace DearFTP.Connection
|
|||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes($"{message}{(end ? "\r\n" : "")}");
|
var bytes = Encoding.UTF8.GetBytes($"{message}{(end ? "\r\n" : "")}");
|
||||||
|
|
||||||
NetworkStream.Write(bytes, 0, bytes.Length);
|
Stream.Write(bytes, 0, bytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send(ResponseCode code, string argument)
|
public void Send(ResponseCode code, string argument)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes($"{(uint)code} {argument}\r\n");
|
var bytes = Encoding.UTF8.GetBytes($"{(uint)code} {argument}\r\n");
|
||||||
|
|
||||||
NetworkStream.Write(bytes, 0, bytes.Length);
|
Stream.Write(bytes, 0, bytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send(ResponseCode code, string message, params string[] arguments)
|
public void Send(ResponseCode code, string message, params string[] arguments)
|
||||||
@@ -74,7 +73,7 @@ namespace DearFTP.Connection
|
|||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(builder.ToString());
|
var bytes = Encoding.UTF8.GetBytes(builder.ToString());
|
||||||
|
|
||||||
NetworkStream.Write(bytes, 0, bytes.Length);
|
Stream.Write(bytes, 0, bytes.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
DearFTP/Connection/IDataConnection.cs
Normal file
19
DearFTP/Connection/IDataConnection.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace DearFTP.Connection
|
||||||
|
{
|
||||||
|
interface IDataConnection
|
||||||
|
{
|
||||||
|
Stream Stream { get; }
|
||||||
|
int Port { get; }
|
||||||
|
bool IsTlsProtected { get; }
|
||||||
|
bool IsAvailable { get; }
|
||||||
|
|
||||||
|
void Create();
|
||||||
|
void AcceptClient();
|
||||||
|
void Authenticate();
|
||||||
|
void ActivateTls();
|
||||||
|
void DesactivateTls();
|
||||||
|
void Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace DearFTP.Connection
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection
|
|
||||||
{
|
{
|
||||||
public enum ResponseCode : uint
|
public enum ResponseCode : uint
|
||||||
{
|
{
|
||||||
@@ -175,6 +171,10 @@ namespace DearFTP.Connection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ArgumentNotImplemented = 504,
|
ArgumentNotImplemented = 504,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Current level of protection is insufficient, need TLS/SSL
|
||||||
|
/// </summary>
|
||||||
|
InsufficientProtection = 522,
|
||||||
|
/// <summary>
|
||||||
/// Not logged in.
|
/// Not logged in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NotLoggedIn = 530,
|
NotLoggedIn = 530,
|
||||||
|
|||||||
@@ -2,12 +2,9 @@
|
|||||||
using DearFTP.Connection.Commands;
|
using DearFTP.Connection.Commands;
|
||||||
using DearFTP.Utils;
|
using DearFTP.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Security;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Connection
|
namespace DearFTP.Connection
|
||||||
{
|
{
|
||||||
@@ -19,14 +16,19 @@ namespace DearFTP.Connection
|
|||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
public Share[] Shares { get; set; }
|
public Share[] Shares { get; set; }
|
||||||
public Share[] WritablesShares { get; set; }
|
public Share[] WritablesShares { get; set; }
|
||||||
public FtpStream FtpStream { get; }
|
|
||||||
public NavigablePath NavigablePath { get; set; }
|
public NavigablePath NavigablePath { get; set; }
|
||||||
|
public FtpStream FtpStream { get; private set; }
|
||||||
|
public IDataConnection DataConnection { get; set; }
|
||||||
|
public int RestartPosition { get; set; }
|
||||||
|
public bool IsTlsProtected { get; private set; }
|
||||||
public string CurrentWorkingDirectory => NavigablePath.CurrentDirectory;
|
public string CurrentWorkingDirectory => NavigablePath.CurrentDirectory;
|
||||||
public string IP => ((IPEndPoint)_client.Client.LocalEndPoint).Address.ToString();
|
|
||||||
public DataConnection DataConnection { get; set; }
|
|
||||||
|
|
||||||
private TcpClient _client;
|
public IPAddress LocalIP => ((IPEndPoint)_client.Client.LocalEndPoint).Address;
|
||||||
private NetworkStream _networkStream;
|
public IPAddress RemoteIP => ((IPEndPoint)_client.Client.RemoteEndPoint).Address;
|
||||||
|
|
||||||
|
private readonly TcpClient _client;
|
||||||
|
private readonly NetworkStream _networkStream;
|
||||||
|
private SslStream _sslStream;
|
||||||
private bool _isRunning = true;
|
private bool _isRunning = true;
|
||||||
|
|
||||||
public Session(TcpClient client)
|
public Session(TcpClient client)
|
||||||
@@ -38,7 +40,9 @@ namespace DearFTP.Connection
|
|||||||
Configuration = FtpServer.Instance.Configuration;
|
Configuration = FtpServer.Instance.Configuration;
|
||||||
CommandsDispatcher = FtpServer.Instance.CommandsDispatcher;
|
CommandsDispatcher = FtpServer.Instance.CommandsDispatcher;
|
||||||
Logger = FtpServer.Instance.Logger;
|
Logger = FtpServer.Instance.Logger;
|
||||||
DataConnection = new DataConnection();
|
DataConnection = Configuration.Server.ForceDataPort == 0 ? (IDataConnection)new DynamicDataConnection() : new StaticDataConnection();
|
||||||
|
RestartPosition = 0;
|
||||||
|
IsTlsProtected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
@@ -48,7 +52,7 @@ namespace DearFTP.Connection
|
|||||||
while (_isRunning)
|
while (_isRunning)
|
||||||
{
|
{
|
||||||
(string command, string argument) = FtpStream.Receive();
|
(string command, string argument) = FtpStream.Receive();
|
||||||
Logger.Log($"[{_client.Client.RemoteEndPoint}]: {command} {argument}");
|
Log($"{command} {argument}");
|
||||||
|
|
||||||
CommandsDispatcher.Dispatch(this, command, argument);
|
CommandsDispatcher.Dispatch(this, command, argument);
|
||||||
}
|
}
|
||||||
@@ -61,31 +65,33 @@ namespace DearFTP.Connection
|
|||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetRealPath(string path)
|
public void Log(string message)
|
||||||
{
|
{
|
||||||
string completePath = Path.Combine(CurrentWorkingDirectory, path);
|
Logger.Log($"[{_client.Client.RemoteEndPoint}]: {message}");
|
||||||
|
}
|
||||||
|
|
||||||
string[] split = CurrentWorkingDirectory.Substring(1).Split('/');
|
public void LogError(string error, string description)
|
||||||
|
{
|
||||||
|
Logger.LogError(error, $"[{_client.Client.RemoteEndPoint}]: {description}");
|
||||||
|
}
|
||||||
|
|
||||||
if (split.Length == 0)
|
public void ActivateTls()
|
||||||
{
|
{
|
||||||
return null;
|
_sslStream = new SslStream(_networkStream, true);
|
||||||
}
|
|
||||||
|
|
||||||
var share = Shares.FirstOrDefault(x => x.Name == split[0]);
|
_sslStream.AuthenticateAsServer(Configuration.Tls.X509Certificate, false, true);
|
||||||
|
|
||||||
if (share == null)
|
FtpStream = new FtpStream(_sslStream);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Path.Combine(split.Skip(1).Prepend(share.Path).ToArray());
|
IsTlsProtected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_sslStream?.Close();
|
||||||
_networkStream.Close();
|
_networkStream.Close();
|
||||||
_client.Close();
|
_client.Close();
|
||||||
|
DataConnection.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
112
DearFTP/Connection/StaticDataConnection.cs
Normal file
112
DearFTP/Connection/StaticDataConnection.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DearFTP.Connection
|
||||||
|
{
|
||||||
|
class StaticDataConnection : IDataConnection
|
||||||
|
{
|
||||||
|
public const int Timeout = 10_000;
|
||||||
|
|
||||||
|
public Stream Stream { get; private set; }
|
||||||
|
public bool IsTlsProtected { get; private set; }
|
||||||
|
|
||||||
|
public int Port => ((IPEndPoint)_listener.LocalEndpoint).Port;
|
||||||
|
public bool IsAvailable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_acceptTask == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (_acceptTask.Wait(Timeout))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_acceptTask.Dispose();
|
||||||
|
_acceptTask = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TcpListener _listener;
|
||||||
|
private static Task _acceptTask;
|
||||||
|
private TcpClient _client;
|
||||||
|
|
||||||
|
public StaticDataConnection()
|
||||||
|
{
|
||||||
|
if (_listener == null)
|
||||||
|
{
|
||||||
|
_listener = new TcpListener(IPAddress.Any, FtpServer.Instance.Configuration.Server.ForceDataPort);
|
||||||
|
_listener.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
IsTlsProtected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AcceptClient()
|
||||||
|
{
|
||||||
|
if (_acceptTask?.IsCompleted == false)
|
||||||
|
{
|
||||||
|
_acceptTask.Wait(Timeout);
|
||||||
|
|
||||||
|
_acceptTask.Dispose();
|
||||||
|
_acceptTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_acceptTask = _listener.AcceptTcpClientAsync().ContinueWith(t =>
|
||||||
|
{
|
||||||
|
_client = t.Result;
|
||||||
|
|
||||||
|
if (IsTlsProtected)
|
||||||
|
{
|
||||||
|
Stream = new SslStream(_client.GetStream(), false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stream = _client.GetStream();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Authenticate()
|
||||||
|
{
|
||||||
|
if (IsTlsProtected)
|
||||||
|
{
|
||||||
|
((SslStream)Stream).AuthenticateAsServer(FtpServer.Instance.Configuration.Tls.X509Certificate, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ActivateTls()
|
||||||
|
{
|
||||||
|
IsTlsProtected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
Stream.Close();
|
||||||
|
_client.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create()
|
||||||
|
{
|
||||||
|
// Nothing to create as the TcpListener is shared by all
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DesactivateTls()
|
||||||
|
{
|
||||||
|
IsTlsProtected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Stop()
|
||||||
|
{
|
||||||
|
_listener?.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
using System;
|
using DearFTP.Configurations;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using DearFTP.Configurations;
|
|
||||||
using DearFTP.Connection;
|
using DearFTP.Connection;
|
||||||
using DearFTP.Connection.Commands;
|
using DearFTP.Connection.Commands;
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DearFTP
|
namespace DearFTP
|
||||||
{
|
{
|
||||||
@@ -21,6 +18,7 @@ namespace DearFTP
|
|||||||
|
|
||||||
private bool _isRunning = true;
|
private bool _isRunning = true;
|
||||||
private TcpListener _listener;
|
private TcpListener _listener;
|
||||||
|
private TcpListener _ftpsListener;
|
||||||
|
|
||||||
public FtpServer()
|
public FtpServer()
|
||||||
{
|
{
|
||||||
@@ -39,13 +37,21 @@ namespace DearFTP
|
|||||||
Logger.Log("DearFTP started");
|
Logger.Log("DearFTP started");
|
||||||
Logger.Log();
|
Logger.Log();
|
||||||
|
|
||||||
|
if (Configuration.Server.ImplicitFtpsPort != 0)
|
||||||
|
{
|
||||||
|
Task.Run(() => FtpsTcpLoop());
|
||||||
|
}
|
||||||
|
|
||||||
TcpLoop();
|
TcpLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
|
StaticDataConnection.Stop();
|
||||||
_listener.Stop();
|
_listener.Stop();
|
||||||
|
_ftpsListener?.Stop();
|
||||||
|
Configuration.Tls.X509Certificate?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TcpLoop()
|
private void TcpLoop()
|
||||||
@@ -63,12 +69,33 @@ namespace DearFTP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateSession(TcpClient client)
|
private void FtpsTcpLoop()
|
||||||
|
{
|
||||||
|
_ftpsListener = new TcpListener(IPAddress.Any, Configuration.Server.ImplicitFtpsPort);
|
||||||
|
_ftpsListener.Start(5);
|
||||||
|
|
||||||
|
Logger.Log($"Listening on: {_ftpsListener.LocalEndpoint}");
|
||||||
|
|
||||||
|
while (_isRunning)
|
||||||
|
{
|
||||||
|
var client = _ftpsListener.AcceptTcpClient();
|
||||||
|
|
||||||
|
Task.Run(() => CreateSession(client, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSession(TcpClient client, bool implicitFtps = false)
|
||||||
{
|
{
|
||||||
Logger.Log($"Client connected: {client.Client.RemoteEndPoint}");
|
Logger.Log($"Client connected: {client.Client.RemoteEndPoint}");
|
||||||
|
|
||||||
using (var session = new Session(client))
|
using (var session = new Session(client))
|
||||||
{
|
{
|
||||||
|
if (implicitFtps)
|
||||||
|
{
|
||||||
|
session.ActivateTls();
|
||||||
|
session.DataConnection.ActivateTls();
|
||||||
|
}
|
||||||
|
|
||||||
session.Start();
|
session.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using DearFTP.Configurations;
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using DearFTP.Configurations;
|
|
||||||
|
|
||||||
namespace DearFTP
|
namespace DearFTP
|
||||||
{
|
{
|
||||||
@@ -10,12 +8,12 @@ namespace DearFTP
|
|||||||
{
|
{
|
||||||
|
|
||||||
public bool WriteToFile { get; private set; }
|
public bool WriteToFile { get; private set; }
|
||||||
public string FilePath { get; private set; }
|
public string FilePath { get; private set; }
|
||||||
|
|
||||||
public Logger(Configuration configuration)
|
public Logger(Configuration configuration)
|
||||||
{
|
{
|
||||||
WriteToFile = configuration.Server.LogFileEnabled;
|
WriteToFile = configuration.Server.LogFileEnabled;
|
||||||
FilePath = configuration.Server.LogFilePath;
|
FilePath = configuration.Server.LogFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log(string message)
|
public void Log(string message)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DearFTP
|
namespace DearFTP
|
||||||
@@ -25,13 +23,7 @@ namespace DearFTP
|
|||||||
|
|
||||||
var server = new FtpServer();
|
var server = new FtpServer();
|
||||||
|
|
||||||
Task.Run(() => server.Start());
|
server.Start();
|
||||||
|
|
||||||
while (Console.ReadLine() != "stop") { }
|
|
||||||
|
|
||||||
server.Stop();
|
|
||||||
|
|
||||||
Environment.Exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProceedArguments(string[] args)
|
private static void ProceedArguments(string[] args)
|
||||||
|
|||||||
32
DearFTP/Utils/Extensions/IPAddressExtensions.cs
Normal file
32
DearFTP/Utils/Extensions/IPAddressExtensions.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DearFTP.Utils.Extensions
|
||||||
|
{
|
||||||
|
public static class IPAddressExtensions
|
||||||
|
{
|
||||||
|
// Source: https://stackoverflow.com/a/39120248
|
||||||
|
/// <summary>
|
||||||
|
/// An extension method to determine if an IP address is internal, as specified in RFC1918
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toTest">The IP address that will be tested</param>
|
||||||
|
/// <returns>Returns true if the IP is internal, false if it is external</returns>
|
||||||
|
public static bool IsInternal(this IPAddress toTest)
|
||||||
|
{
|
||||||
|
byte[] bytes = toTest.GetAddressBytes();
|
||||||
|
switch (bytes[0])
|
||||||
|
{
|
||||||
|
case 10:
|
||||||
|
return true;
|
||||||
|
case 172:
|
||||||
|
return bytes[1] < 32 && bytes[1] >= 16;
|
||||||
|
case 192:
|
||||||
|
return bytes[1] == 168;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DearFTP.Utils
|
namespace DearFTP.Utils
|
||||||
{
|
{
|
||||||
@@ -28,7 +27,7 @@ namespace DearFTP.Utils
|
|||||||
CurrentShare = navigablePath.CurrentShare;
|
CurrentShare = navigablePath.CurrentShare;
|
||||||
CurrentDirectory = navigablePath.CurrentDirectory;
|
CurrentDirectory = navigablePath.CurrentDirectory;
|
||||||
|
|
||||||
_path = new Stack<string>(navigablePath._path);
|
_path = new Stack<string>(navigablePath._path.Reverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFilePath(string fileName)
|
public string GetFilePath(string fileName)
|
||||||
@@ -138,7 +137,7 @@ namespace DearFTP.Utils
|
|||||||
return (tempNavigablePath, null);
|
return (tempNavigablePath, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (tempNavigablePath, Path.Combine(GetRealPath(), split.Last()));
|
return (tempNavigablePath, Path.Combine(tempNavigablePath.GetRealPath(), split.Last()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetDirectories()
|
public IEnumerable<string> GetDirectories()
|
||||||
@@ -193,7 +192,7 @@ namespace DearFTP.Utils
|
|||||||
|
|
||||||
public bool NavigateTo(string path)
|
public bool NavigateTo(string path)
|
||||||
{
|
{
|
||||||
var oldPath = new Stack<string>(_path);
|
var oldPath = new Stack<string>(_path.Reverse());
|
||||||
var queue = new Queue<string>(path.Split('/'));
|
var queue = new Queue<string>(path.Split('/'));
|
||||||
|
|
||||||
// If absolute path
|
// If absolute path
|
||||||
@@ -205,7 +204,7 @@ namespace DearFTP.Utils
|
|||||||
|
|
||||||
if (NavigateTo(queue))
|
if (NavigateTo(queue))
|
||||||
{
|
{
|
||||||
CurrentDirectory = $"/{string.Join('/', _path)}";
|
CurrentDirectory = $"/{string.Join('/', _path.Reverse())}";
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -269,7 +268,7 @@ namespace DearFTP.Utils
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Path.Combine(_path.Skip(1).Prepend(CurrentShare.Path).ToArray());
|
return Path.Combine(_path.Reverse().Skip(1).Prepend(CurrentShare.Path).ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
530
DearFTP/Utils/OpenSslKey.cs
Normal file
530
DearFTP/Utils/OpenSslKey.cs
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
//**********************************************************************************
|
||||||
|
//
|
||||||
|
//OpenSSLKey
|
||||||
|
// .NET 2.0 OpenSSL Public & Private Key Parser
|
||||||
|
//
|
||||||
|
// Copyright (C) 2008 JavaScience Consulting
|
||||||
|
//
|
||||||
|
//***********************************************************************************
|
||||||
|
//
|
||||||
|
// opensslkey.cs
|
||||||
|
//
|
||||||
|
// Reads and parses:
|
||||||
|
// (1) OpenSSL PEM or DER public keys
|
||||||
|
// (2) OpenSSL PEM or DER traditional SSLeay private keys (encrypted and unencrypted)
|
||||||
|
// (3) PKCS #8 PEM or DER encoded private keys (encrypted and unencrypted)
|
||||||
|
// Keys in PEM format must have headers/footers .
|
||||||
|
// Encrypted Private Key in SSLEay format not supported in DER
|
||||||
|
// Removes header/footer lines.
|
||||||
|
// For traditional SSLEAY PEM private keys, checks for encrypted format and
|
||||||
|
// uses PBE to extract 3DES key.
|
||||||
|
// For SSLEAY format, only supports encryption format: DES-EDE3-CBC
|
||||||
|
// For PKCS #8, only supports PKCS#5 v2.0 3des.
|
||||||
|
// Parses private and public key components and returns .NET RSA object.
|
||||||
|
// Creates dummy unsigned certificate linked to private keypair and
|
||||||
|
// optionally exports to pkcs #12
|
||||||
|
//
|
||||||
|
// See also:
|
||||||
|
// http://www.openssl.org/docs/crypto/pem.html#PEM_ENCRYPTION_FORMAT
|
||||||
|
//**************************************************************************************
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DearFTP.Utils
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct CRYPT_KEY_PROV_INFO
|
||||||
|
{
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)] public string pwszContainerName;
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)] public string pwszProvName;
|
||||||
|
public uint dwProvType;
|
||||||
|
public uint dwFlags;
|
||||||
|
public uint cProvParam;
|
||||||
|
public IntPtr rgProvParam;
|
||||||
|
public uint dwKeySpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct CERT_NAME_BLOB
|
||||||
|
{
|
||||||
|
public int cbData;
|
||||||
|
public IntPtr pbData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OpenSslKey
|
||||||
|
{
|
||||||
|
//-------- Get the binary PKCS #8 PRIVATE key --------
|
||||||
|
public static byte[] DecodePkcs8PrivateKey(string instr)
|
||||||
|
{
|
||||||
|
const string pemp8header = "-----BEGIN PRIVATE KEY-----";
|
||||||
|
const string pemp8footer = "-----END PRIVATE KEY-----";
|
||||||
|
string pemstr = instr.Trim();
|
||||||
|
byte[] binkey;
|
||||||
|
if (!pemstr.StartsWith(pemp8header) || !pemstr.EndsWith(pemp8footer))
|
||||||
|
return null;
|
||||||
|
StringBuilder sb = new StringBuilder(pemstr);
|
||||||
|
sb.Replace(pemp8header, ""); //remove headers/footers, if present
|
||||||
|
sb.Replace(pemp8footer, "");
|
||||||
|
|
||||||
|
string pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
binkey = Convert.FromBase64String(pubstr);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{ //if can't b64 decode, data is not valid
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return binkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------- Parses binary asn.1 PKCS #8 PrivateKeyInfo; returns RSACryptoServiceProvider ---
|
||||||
|
public static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
|
||||||
|
{
|
||||||
|
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
|
||||||
|
// this byte[] includes the sequence byte and terminal encoded null
|
||||||
|
byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
|
||||||
|
byte[] seq;
|
||||||
|
|
||||||
|
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
|
||||||
|
MemoryStream mem = new MemoryStream(pkcs8);
|
||||||
|
int lenstream = (int)mem.Length;
|
||||||
|
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
|
||||||
|
byte bt;
|
||||||
|
ushort twobytes;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16();
|
||||||
|
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
|
||||||
|
binr.ReadByte(); //advance 1 byte
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16(); //advance 2 bytes
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
if (bt != 0x02)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16();
|
||||||
|
|
||||||
|
if (twobytes != 0x0001)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
seq = binr.ReadBytes(15); //read the Sequence OID
|
||||||
|
if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
|
||||||
|
return null;
|
||||||
|
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
if (bt != 0x04) //expect an Octet string
|
||||||
|
return null;
|
||||||
|
|
||||||
|
bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count
|
||||||
|
if (bt == 0x81)
|
||||||
|
binr.ReadByte();
|
||||||
|
else
|
||||||
|
if (bt == 0x82)
|
||||||
|
binr.ReadUInt16();
|
||||||
|
//------ at this stage, the remaining sequence should be the RSA private key
|
||||||
|
|
||||||
|
byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position));
|
||||||
|
RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
|
||||||
|
return rsacsp;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
binr.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- Get the binary PKCS #8 Encrypted PRIVATE key --------
|
||||||
|
public static byte[] DecodePkcs8EncPrivateKey(string instr)
|
||||||
|
{
|
||||||
|
const string pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
|
||||||
|
const string pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----";
|
||||||
|
string pemstr = instr.Trim();
|
||||||
|
byte[] binkey;
|
||||||
|
if (!pemstr.StartsWith(pemp8encheader) || !pemstr.EndsWith(pemp8encfooter))
|
||||||
|
return null;
|
||||||
|
StringBuilder sb = new StringBuilder(pemstr);
|
||||||
|
sb.Replace(pemp8encheader, ""); //remove headers/footers, if present
|
||||||
|
sb.Replace(pemp8encfooter, "");
|
||||||
|
|
||||||
|
string pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
binkey = Convert.FromBase64String(pubstr);
|
||||||
|
}
|
||||||
|
catch (System.FormatException)
|
||||||
|
{ //if can't b64 decode, data is not valid
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return binkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------- Parses binary asn.1 EncryptedPrivateKeyInfo; returns RSACryptoServiceProvider ---
|
||||||
|
public static RSACryptoServiceProvider DecodeEncryptedPrivateKeyInfo(byte[] encpkcs8)
|
||||||
|
{
|
||||||
|
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
|
||||||
|
// this byte[] includes the sequence byte and terminal encoded null
|
||||||
|
byte[] OIDpkcs5PBES2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D };
|
||||||
|
byte[] OIDpkcs5PBKDF2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C };
|
||||||
|
byte[] OIDdesEDE3CBC = { 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07 };
|
||||||
|
byte[] seqdes;
|
||||||
|
byte[] seq;
|
||||||
|
byte[] salt;
|
||||||
|
byte[] IV;
|
||||||
|
byte[] encryptedpkcs8;
|
||||||
|
byte[] pkcs8;
|
||||||
|
|
||||||
|
int saltsize, ivsize, encblobsize;
|
||||||
|
int iterations;
|
||||||
|
|
||||||
|
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
|
||||||
|
MemoryStream mem = new MemoryStream(encpkcs8);
|
||||||
|
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
|
||||||
|
byte bt;
|
||||||
|
ushort twobytes;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16();
|
||||||
|
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
|
||||||
|
binr.ReadByte(); //advance 1 byte
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16(); //advance 2 bytes
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16(); //inner sequence
|
||||||
|
if (twobytes == 0x8130)
|
||||||
|
binr.ReadByte();
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16();
|
||||||
|
|
||||||
|
|
||||||
|
seq = binr.ReadBytes(11); //read the Sequence OID
|
||||||
|
if (!CompareBytearrays(seq, OIDpkcs5PBES2)) //is it a OIDpkcs5PBES2 ?
|
||||||
|
return null;
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
|
||||||
|
if (twobytes == 0x8130)
|
||||||
|
binr.ReadByte();
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16();
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
|
||||||
|
if (twobytes == 0x8130)
|
||||||
|
binr.ReadByte();
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16();
|
||||||
|
|
||||||
|
seq = binr.ReadBytes(11); //read the Sequence OID
|
||||||
|
if (!CompareBytearrays(seq, OIDpkcs5PBKDF2)) //is it a OIDpkcs5PBKDF2 ?
|
||||||
|
return null;
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16();
|
||||||
|
if (twobytes == 0x8130)
|
||||||
|
binr.ReadByte();
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16();
|
||||||
|
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
if (bt != 0x04) //expect octet string for salt
|
||||||
|
return null;
|
||||||
|
saltsize = binr.ReadByte();
|
||||||
|
salt = binr.ReadBytes(saltsize);
|
||||||
|
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
if (bt != 0x02) //expect an integer for PBKF2 interation count
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int itbytes = binr.ReadByte(); //PBKD2 iterations should fit in 2 bytes.
|
||||||
|
if (itbytes == 1)
|
||||||
|
iterations = binr.ReadByte();
|
||||||
|
else if (itbytes == 2)
|
||||||
|
iterations = 256 * binr.ReadByte() + binr.ReadByte();
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16();
|
||||||
|
if (twobytes == 0x8130)
|
||||||
|
binr.ReadByte();
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16();
|
||||||
|
|
||||||
|
seqdes = binr.ReadBytes(10); //read the Sequence OID
|
||||||
|
if (!CompareBytearrays(seqdes, OIDdesEDE3CBC)) //is it a OIDdes-EDE3-CBC ?
|
||||||
|
return null;
|
||||||
|
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
if (bt != 0x04) //expect octet string for IV
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ivsize = binr.ReadByte(); // IV byte size should fit in one byte (24 expected for 3DES)
|
||||||
|
IV = binr.ReadBytes(ivsize);
|
||||||
|
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
if (bt != 0x04) // expect octet string for encrypted PKCS8 data
|
||||||
|
return null;
|
||||||
|
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
|
||||||
|
if (bt == 0x81)
|
||||||
|
encblobsize = binr.ReadByte(); // data size in next byte
|
||||||
|
else if (bt == 0x82)
|
||||||
|
encblobsize = 256 * binr.ReadByte() + binr.ReadByte();
|
||||||
|
else
|
||||||
|
encblobsize = bt; // we already have the data size
|
||||||
|
|
||||||
|
encryptedpkcs8 = binr.ReadBytes(encblobsize);
|
||||||
|
|
||||||
|
SecureString secpswd = GetSecPswd("Enter password for Encrypted PKCS #8 ==>");
|
||||||
|
pkcs8 = DecryptPBDK2(encryptedpkcs8, salt, IV, secpswd, iterations);
|
||||||
|
|
||||||
|
if (pkcs8 == null) // probably a bad pswd entered.
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//----- With a decrypted pkcs #8 PrivateKeyInfo blob, decode it to an RSA ---
|
||||||
|
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8);
|
||||||
|
|
||||||
|
return rsa;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
binr.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ Uses PBKD2 to derive a 3DES key and decrypts data --------
|
||||||
|
public static byte[] DecryptPBDK2(byte[] edata, byte[] salt, byte[] IV, SecureString secpswd, int iterations)
|
||||||
|
{
|
||||||
|
byte[] psbytes = new byte[secpswd.Length];
|
||||||
|
IntPtr unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
|
||||||
|
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
|
||||||
|
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Rfc2898DeriveBytes kd = new Rfc2898DeriveBytes(psbytes, salt, iterations);
|
||||||
|
TripleDES decAlg = TripleDES.Create();
|
||||||
|
decAlg.Key = kd.GetBytes(24);
|
||||||
|
decAlg.IV = IV;
|
||||||
|
MemoryStream memstr = new MemoryStream();
|
||||||
|
CryptoStream decrypt = new CryptoStream(memstr, decAlg.CreateDecryptor(), CryptoStreamMode.Write);
|
||||||
|
decrypt.Write(edata, 0, edata.Length);
|
||||||
|
decrypt.Flush();
|
||||||
|
decrypt.Close(); // this is REQUIRED.
|
||||||
|
byte[] cleartext = memstr.ToArray();
|
||||||
|
return cleartext;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Problem decrypting: {0}", e.Message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider ---
|
||||||
|
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
|
||||||
|
{
|
||||||
|
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
|
||||||
|
|
||||||
|
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
|
||||||
|
MemoryStream mem = new MemoryStream(privkey);
|
||||||
|
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
|
||||||
|
byte bt;
|
||||||
|
ushort twobytes;
|
||||||
|
int elems;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
twobytes = binr.ReadUInt16();
|
||||||
|
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
|
||||||
|
binr.ReadByte(); //advance 1 byte
|
||||||
|
else if (twobytes == 0x8230)
|
||||||
|
binr.ReadInt16(); //advance 2 bytes
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
twobytes = binr.ReadUInt16();
|
||||||
|
if (twobytes != 0x0102) //version number
|
||||||
|
return null;
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
if (bt != 0x00)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
|
||||||
|
//------ all private key components are Integer sequences ----
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
MODULUS = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
E = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
D = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
P = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
Q = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
DP = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
DQ = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
elems = GetIntegerSize(binr);
|
||||||
|
IQ = binr.ReadBytes(elems);
|
||||||
|
|
||||||
|
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
|
||||||
|
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
|
||||||
|
RSAParameters RSAparams = new RSAParameters
|
||||||
|
{
|
||||||
|
Modulus = MODULUS,
|
||||||
|
Exponent = E,
|
||||||
|
D = D,
|
||||||
|
P = P,
|
||||||
|
Q = Q,
|
||||||
|
DP = DP,
|
||||||
|
DQ = DQ,
|
||||||
|
InverseQ = IQ
|
||||||
|
};
|
||||||
|
|
||||||
|
RSA.ImportParameters(RSAparams);
|
||||||
|
|
||||||
|
return RSA;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally { binr.Close(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetIntegerSize(BinaryReader binr)
|
||||||
|
{
|
||||||
|
byte bt = binr.ReadByte();
|
||||||
|
|
||||||
|
if (bt != 0x02) //expect integer
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
bt = binr.ReadByte();
|
||||||
|
|
||||||
|
int count;
|
||||||
|
if (bt == 0x81)
|
||||||
|
count = binr.ReadByte(); // data size in next byte
|
||||||
|
|
||||||
|
else if (bt == 0x82)
|
||||||
|
{
|
||||||
|
byte highbyte = binr.ReadByte();
|
||||||
|
byte lowbyte = binr.ReadByte();
|
||||||
|
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
|
||||||
|
count = BitConverter.ToInt32(modint, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
count = bt; // we already have the data size
|
||||||
|
}
|
||||||
|
|
||||||
|
while (binr.ReadByte() == 0x00)
|
||||||
|
{ //remove high order zeros in data
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SecureString GetSecPswd(string prompt)
|
||||||
|
{
|
||||||
|
SecureString password = new SecureString();
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
|
Console.Write(prompt);
|
||||||
|
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ConsoleKeyInfo cki = Console.ReadKey(true);
|
||||||
|
if (cki.Key == ConsoleKey.Enter)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
|
Console.WriteLine();
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
else if (cki.Key == ConsoleKey.Backspace)
|
||||||
|
{
|
||||||
|
// remove the last asterisk from the screen...
|
||||||
|
if (password.Length > 0)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
|
||||||
|
Console.Write(" ");
|
||||||
|
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
|
||||||
|
password.RemoveAt(password.Length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cki.Key == ConsoleKey.Escape)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
|
Console.WriteLine();
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
else if (char.IsLetterOrDigit(cki.KeyChar) || char.IsSymbol(cki.KeyChar))
|
||||||
|
{
|
||||||
|
if (password.Length < 20)
|
||||||
|
{
|
||||||
|
password.AppendChar(cki.KeyChar);
|
||||||
|
Console.Write("*");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Beep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Beep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CompareBytearrays(byte[] a, byte[] b)
|
||||||
|
{
|
||||||
|
if (a.Length != b.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
foreach (byte c in a)
|
||||||
|
{
|
||||||
|
if (c != b[i])
|
||||||
|
return false;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user