mirror of
https://github.com/winscp/winscp.git
synced 2026-10-24 02:28:55 +03:00
538 lines
20 KiB
C#
538 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.RegularExpressions;
|
|
using System.Globalization;
|
|
using System.Security;
|
|
|
|
namespace WinSCP
|
|
{
|
|
[Guid("F25C49A5-74A6-4E8F-AEB4-5B4E0DDF0EF9")]
|
|
[ComVisible(true)]
|
|
public enum Protocol
|
|
{
|
|
Sftp = 0,
|
|
Scp = 1,
|
|
Ftp = 2,
|
|
Webdav = 3,
|
|
S3 = 4,
|
|
}
|
|
|
|
[Guid("D924FAB9-FCE7-47B8-9F23-5717698384D3")]
|
|
[ComVisible(true)]
|
|
public enum FtpMode
|
|
{
|
|
Passive = 0,
|
|
Active = 1,
|
|
}
|
|
|
|
[Guid("F2FC81EB-4761-4A4E-A3EC-4AFDD474C18C")]
|
|
[ComVisible(true)]
|
|
public enum FtpSecure
|
|
{
|
|
None = 0,
|
|
Implicit = 1,
|
|
Explicit = 3,
|
|
}
|
|
|
|
[Guid("8A98AB8F-30E8-4539-A3DE-A33DDC43B33C")]
|
|
[ComVisible(true)]
|
|
public enum SshHostKeyPolicy
|
|
{
|
|
Check = 0,
|
|
GiveUpSecurityAndAcceptAny = 1,
|
|
AcceptNew = 2,
|
|
}
|
|
|
|
[Guid("2D4EF368-EE80-4C15-AE77-D12AEAF4B00A")]
|
|
[ClassInterface(Constants.ClassInterface)]
|
|
[ComVisible(true)]
|
|
public sealed class SessionOptions
|
|
{
|
|
public SessionOptions()
|
|
{
|
|
Timeout = new TimeSpan(0, 0, 15);
|
|
RawSettings = new Dictionary<string,string>();
|
|
}
|
|
|
|
public string Name { get { return GetName(); } set { _name = value; } }
|
|
public Protocol Protocol { get { return _protocol; } set { SetProtocol(value); } }
|
|
public string HostName { get; set; }
|
|
public int PortNumber { get { return _portNumber; } set { SetPortNumber(value); } }
|
|
public string UserName { get; set; }
|
|
public string Password { get { return GetPassword(_securePassword); } set { SetPassword(ref _securePassword, value); } }
|
|
public SecureString SecurePassword { get { return _securePassword; } set { _securePassword = value; } }
|
|
public string NewPassword { get { return GetPassword(_secureNewPassword); } set { SetPassword(ref _secureNewPassword, value); } }
|
|
public SecureString SecureNewPassword { get { return _secureNewPassword; } set { _secureNewPassword = value; } }
|
|
public TimeSpan Timeout { get { return _timeout; } set { SetTimeout(value); } }
|
|
public int TimeoutInMilliseconds { get { return Tools.TimeSpanToMilliseconds(Timeout); } set { Timeout = Tools.MillisecondsToTimeSpan(value); } }
|
|
public string PrivateKeyPassphrase { get { return GetPassword(_securePrivateKeyPassphrase); } set { SetPassword(ref _securePrivateKeyPassphrase, value); } }
|
|
public SecureString SecurePrivateKeyPassphrase { get { return _securePrivateKeyPassphrase; } set { _securePrivateKeyPassphrase = value; } }
|
|
public string RootPath { get { return _rootPath; } set { SetRootPath(value); } }
|
|
public bool Secure { get; set; }
|
|
|
|
// SSH
|
|
public string SshHostKeyFingerprint { get { return _sshHostKeyFingerprint; } set { SetSshHostKeyFingerprint(value); } }
|
|
public SshHostKeyPolicy SshHostKeyPolicy { get; set; }
|
|
[Obsolete("Use SshHostKeyPolicy")]
|
|
public bool GiveUpSecurityAndAcceptAnySshHostKey { get { return GetGiveUpSecurityAndAcceptAnySshHostKey(); } set { SetGiveUpSecurityAndAcceptAnySshHostKey(value); } }
|
|
public string SshPrivateKeyPath { get; set; }
|
|
public string SshPrivateKey { get; set; }
|
|
[Obsolete("Use PrivateKeyPassphrase")]
|
|
public string SshPrivateKeyPassphrase { get { return PrivateKeyPassphrase; } set { PrivateKeyPassphrase = value; } }
|
|
|
|
// FTP
|
|
public FtpMode FtpMode { get; set; }
|
|
public FtpSecure FtpSecure { get; set; }
|
|
|
|
// WebDAV
|
|
[Obsolete("Use Secure")]
|
|
public bool WebdavSecure { get { return Secure; } set { Secure = value; } }
|
|
[Obsolete("Use RootPath")]
|
|
public string WebdavRoot { get { return RootPath; } set { RootPath = value; } }
|
|
|
|
// TLS
|
|
public string TlsHostCertificateFingerprint { get { return _tlsHostCertificateFingerprint; } set { SetHostTlsCertificateFingerprint(value); } }
|
|
public bool GiveUpSecurityAndAcceptAnyTlsHostCertificate { get; set; }
|
|
public string TlsClientCertificatePath { get; set; }
|
|
|
|
public void AddRawSettings(string setting, string value)
|
|
{
|
|
RawSettings.Add(setting, value);
|
|
}
|
|
|
|
public void ParseUrl(string url)
|
|
{
|
|
if (url == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(url));
|
|
}
|
|
|
|
url = url.Trim();
|
|
const string protocolSeparator = "://";
|
|
int index = url.IndexOf(protocolSeparator, StringComparison.OrdinalIgnoreCase);
|
|
if (index < 0)
|
|
{
|
|
throw new ArgumentException("Protocol not specified", nameof(url));
|
|
}
|
|
|
|
string protocol = url.Substring(0, index).Trim();
|
|
if (!ParseProtocol(protocol))
|
|
{
|
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Unknown protocol {0}", protocol), nameof(url));
|
|
}
|
|
|
|
url = url.Substring(index + protocolSeparator.Length).Trim();
|
|
index = url.IndexOf('/');
|
|
RootPath = null;
|
|
if (index >= 0)
|
|
{
|
|
string path = url.Substring(index).Trim();
|
|
url = url.Substring(0, index).Trim();
|
|
string parameters = path;
|
|
path = CutToChar(ref parameters, ';');
|
|
if (!string.IsNullOrEmpty(path) && (path != "/"))
|
|
{
|
|
if ((Protocol != Protocol.Webdav) && (Protocol != Protocol.S3))
|
|
{
|
|
throw new ArgumentException("Root path can be specified for WebDAV and S3 protocols only", nameof(url));
|
|
}
|
|
RootPath = path;
|
|
}
|
|
|
|
// forward compatibility
|
|
if (!string.IsNullOrEmpty(parameters))
|
|
{
|
|
throw new ArgumentException("No session parameters are supported", nameof(url));
|
|
}
|
|
}
|
|
|
|
index = url.LastIndexOf('@');
|
|
|
|
string hostInfo;
|
|
string userInfo = null;
|
|
if (index >= 0)
|
|
{
|
|
userInfo = url.Substring(0, index).Trim();
|
|
hostInfo = url.Substring(index + 1).Trim();
|
|
}
|
|
else
|
|
{
|
|
hostInfo = url;
|
|
}
|
|
|
|
PortNumber = 0;
|
|
string portNumber = null;
|
|
if ((hostInfo.Length >= 2) && (hostInfo[0] == '[') && ((index = hostInfo.IndexOf(']')) > 0))
|
|
{
|
|
HostName = hostInfo.Substring(1, index - 1).Trim();
|
|
hostInfo = hostInfo.Substring(index + 1).Trim();
|
|
if (hostInfo.Length > 0)
|
|
{
|
|
if (hostInfo[0] != ':')
|
|
{
|
|
throw new ArgumentException("Unexpected syntax after ]", nameof(url));
|
|
}
|
|
else
|
|
{
|
|
portNumber = hostInfo.Substring(1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HostName = UriUnescape(CutToChar(ref hostInfo, ':'));
|
|
portNumber = hostInfo;
|
|
}
|
|
|
|
// Contrary to TSessionData::ParseUrl, not converting Webdav to S3 on S3 hostname.
|
|
// Not sure if it is desirable and WinSCP will do the conversion for us later anyway.
|
|
|
|
if (string.IsNullOrEmpty(HostName))
|
|
{
|
|
throw new ArgumentException("No host name", nameof(url));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(portNumber))
|
|
{
|
|
PortNumber = 0;
|
|
}
|
|
else
|
|
{
|
|
portNumber = UriUnescape(portNumber);
|
|
if (!int.TryParse(portNumber, 0, CultureInfo.InvariantCulture, out int number))
|
|
{
|
|
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} is not a valid port number", portNumber), nameof(url));
|
|
}
|
|
else
|
|
{
|
|
PortNumber = number;
|
|
}
|
|
}
|
|
|
|
UserName = null;
|
|
Password = null;
|
|
SshHostKeyFingerprint = null;
|
|
SshHostKeyPolicy = SshHostKeyPolicy.Check;
|
|
TlsHostCertificateFingerprint = null;
|
|
GiveUpSecurityAndAcceptAnyTlsHostCertificate = false;
|
|
if (!string.IsNullOrEmpty(userInfo))
|
|
{
|
|
string parameters = userInfo;
|
|
userInfo = CutToChar(ref parameters, ';');
|
|
|
|
bool hasPassword = (userInfo.IndexOf(':') >= 0);
|
|
UserName = EmptyToNull(UriUnescape(CutToChar(ref userInfo, ':')));
|
|
Password = hasPassword ? UriUnescape(userInfo) : null;
|
|
|
|
while (!string.IsNullOrEmpty(parameters))
|
|
{
|
|
string parameter = CutToChar(ref parameters, ';');
|
|
string parameterName = CutToChar(ref parameter, '=');
|
|
parameter = UriUnescape(parameter);
|
|
const string RawSettingsPrefix = "x-";
|
|
if (parameterName.Equals("fingerprint", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
switch (Protocol)
|
|
{
|
|
case Protocol.Sftp:
|
|
case Protocol.Scp:
|
|
SshHostKeyFingerprint = parameter;
|
|
break;
|
|
|
|
case Protocol.Ftp:
|
|
case Protocol.Webdav:
|
|
case Protocol.S3:
|
|
TlsHostCertificateFingerprint = parameter;
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentException();
|
|
}
|
|
}
|
|
else if (parameterName.StartsWith(RawSettingsPrefix, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
parameterName = UriUnescape(parameterName.Substring(RawSettingsPrefix.Length));
|
|
if (parameterName.Equals("name", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Name = parameter;
|
|
}
|
|
else
|
|
{
|
|
AddRawSettings(parameterName, parameter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Unsupported connection parameter {0}", parameterName), nameof(url));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool ParseProtocol(string protocol)
|
|
{
|
|
bool result = true;
|
|
FtpSecure = FtpSecure.None;
|
|
|
|
if (protocol.Equals("sftp", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.Sftp;
|
|
}
|
|
else if (protocol.Equals("scp", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.Scp;
|
|
}
|
|
else if (protocol.Equals("ftp", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.Ftp;
|
|
}
|
|
else if (protocol.Equals("ftps", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.Ftp;
|
|
FtpSecure = FtpSecure.Implicit;
|
|
}
|
|
else if (protocol.Equals("ftpes", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.Ftp;
|
|
FtpSecure = FtpSecure.Explicit;
|
|
}
|
|
else if (protocol.Equals("dav", StringComparison.OrdinalIgnoreCase) ||
|
|
protocol.Equals("http", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.Webdav;
|
|
}
|
|
else if (protocol.Equals("davs", StringComparison.OrdinalIgnoreCase) ||
|
|
protocol.Equals("https", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.Webdav;
|
|
Secure = true;
|
|
}
|
|
else if (protocol.Equals("s3plain", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.S3;
|
|
Secure = false;
|
|
}
|
|
else if (protocol.Equals("s3", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Protocol = Protocol.S3;
|
|
}
|
|
else
|
|
{
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static string EmptyToNull(string s)
|
|
{
|
|
if (string.IsNullOrEmpty(s))
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
return s;
|
|
}
|
|
}
|
|
|
|
private static string UriUnescape(string s)
|
|
{
|
|
return Uri.UnescapeDataString(s);
|
|
}
|
|
|
|
private static string CutToChar(ref string s, char c)
|
|
{
|
|
int index = s.IndexOf(c);
|
|
string result;
|
|
if (index >= 0)
|
|
{
|
|
result = s.Substring(0, index).Trim();
|
|
s = s.Substring(index + 1).Trim();
|
|
}
|
|
else
|
|
{
|
|
result = s;
|
|
s = string.Empty;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal Dictionary<string, string> RawSettings { get; private set; }
|
|
internal bool IsSsh { get { return (Protocol == Protocol.Sftp) || (Protocol == Protocol.Scp); } }
|
|
internal bool IsTls { get { return GetIsTls(); } }
|
|
|
|
private bool GetIsTls()
|
|
{
|
|
return
|
|
((Protocol == Protocol.Ftp) && (FtpSecure != FtpSecure.None)) ||
|
|
(((Protocol == Protocol.Webdav) || (Protocol == Protocol.S3)) && Secure);
|
|
}
|
|
|
|
private void SetSshHostKeyFingerprint(string s)
|
|
{
|
|
if (s != null)
|
|
{
|
|
Match match = _sshHostKeyRegex.Match(s);
|
|
|
|
if (!match.Success || (match.Length != s.Length))
|
|
{
|
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "SSH host key fingerprint \"{0}\" does not match pattern /{1}/", s, _sshHostKeyRegex));
|
|
}
|
|
}
|
|
|
|
_sshHostKeyFingerprint = s;
|
|
}
|
|
|
|
private void SetHostTlsCertificateFingerprint(string s)
|
|
{
|
|
if (s != null)
|
|
{
|
|
Match match = _tlsCertificateRegex.Match(s);
|
|
|
|
if (!match.Success || (match.Length != s.Length))
|
|
{
|
|
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "TLS host certificate fingerprint \"{0}\" does not match pattern /{1}/", s, _tlsCertificateRegex));
|
|
}
|
|
}
|
|
|
|
_tlsHostCertificateFingerprint = s;
|
|
}
|
|
|
|
private void SetTimeout(TimeSpan value)
|
|
{
|
|
if (value <= TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentException("Timeout has to be positive non-zero value");
|
|
}
|
|
|
|
_timeout = value;
|
|
}
|
|
|
|
private void SetPortNumber(int value)
|
|
{
|
|
if ((value < 0) || (value > 65535))
|
|
{
|
|
throw new ArgumentOutOfRangeException("Port number has to be in range from 0 to 65535");
|
|
}
|
|
|
|
_portNumber = value;
|
|
}
|
|
|
|
private void SetProtocol(Protocol value)
|
|
{
|
|
_protocol = value;
|
|
if ((_protocol == Protocol.S3) && string.IsNullOrEmpty(HostName))
|
|
{
|
|
HostName = "s3.amazonaws.com";
|
|
Secure = true;
|
|
}
|
|
}
|
|
|
|
private void SetRootPath(string value)
|
|
{
|
|
if (!string.IsNullOrEmpty(value) && (value[0] != '/'))
|
|
{
|
|
throw new ArgumentException("Root path has to start with a slash");
|
|
}
|
|
_rootPath = value;
|
|
}
|
|
|
|
private static void SetPassword(ref SecureString securePassword, string value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
securePassword = null;
|
|
}
|
|
else
|
|
{
|
|
securePassword = new SecureString();
|
|
foreach (char c in value)
|
|
{
|
|
securePassword.AppendChar(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string GetPassword(SecureString securePassword)
|
|
{
|
|
if (securePassword == null)
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
IntPtr ptr = IntPtr.Zero;
|
|
try
|
|
{
|
|
ptr = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
|
|
return Marshal.PtrToStringUni(ptr);
|
|
}
|
|
finally
|
|
{
|
|
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
private string GetName()
|
|
{
|
|
string result;
|
|
if (_name != null)
|
|
{
|
|
result = _name;
|
|
}
|
|
else
|
|
{
|
|
if (!string.IsNullOrEmpty(HostName) && !string.IsNullOrEmpty(UserName))
|
|
{
|
|
result = $"{UserName}@{HostName}";
|
|
}
|
|
else if (!string.IsNullOrEmpty(HostName))
|
|
{
|
|
result = HostName;
|
|
}
|
|
else
|
|
{
|
|
result = "session";
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
private void SetGiveUpSecurityAndAcceptAnySshHostKey(bool value)
|
|
{
|
|
SshHostKeyPolicy = value ? SshHostKeyPolicy.GiveUpSecurityAndAcceptAny : SshHostKeyPolicy.Check;
|
|
}
|
|
|
|
private bool GetGiveUpSecurityAndAcceptAnySshHostKey()
|
|
{
|
|
return (SshHostKeyPolicy == SshHostKeyPolicy.GiveUpSecurityAndAcceptAny);
|
|
}
|
|
|
|
private SecureString _securePassword;
|
|
private SecureString _secureNewPassword;
|
|
private SecureString _securePrivateKeyPassphrase;
|
|
private string _sshHostKeyFingerprint;
|
|
private string _tlsHostCertificateFingerprint;
|
|
private TimeSpan _timeout;
|
|
private int _portNumber;
|
|
private string _rootPath;
|
|
private Protocol _protocol;
|
|
private string _name;
|
|
|
|
private const string _listPattern = @"{0}(;{0})*";
|
|
private const string _sshHostKeyPattern = @"((ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-nistp(256|384|521))( |-))?(\d+ )?(([0-9a-fA-F]{2}(:|-)){15}[0-9a-fA-F]{2}|[0-9a-zA-Z+/\-_]{43}=?)";
|
|
private static readonly Regex _sshHostKeyRegex =
|
|
new Regex(string.Format(CultureInfo.InvariantCulture, _listPattern, _sshHostKeyPattern));
|
|
private const string _tlsCertificatePattern = @"((([0-9a-fA-F]{2}[:\-]){31})|(([0-9a-fA-F]{2}[:\-]){19}))[0-9a-fA-F]{2}";
|
|
private static readonly Regex _tlsCertificateRegex =
|
|
new Regex(string.Format(CultureInfo.InvariantCulture, _listPattern, _tlsCertificatePattern));
|
|
}
|
|
}
|