1269 lines
48 KiB
C#
1269 lines
48 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Net;
|
|
using System.IO;
|
|
using System.Xml;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Net.Security;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Sockets;
|
|
using System.Threading.Tasks;
|
|
using System.Security;
|
|
using Common.Utils;
|
|
|
|
namespace Intel.Management.Wsman
|
|
{
|
|
/// <summary>
|
|
/// Implementation of the IWsmanConnection interface
|
|
/// </summary>
|
|
[Guid("73131C56-E7EA-4c1d-A6E2-03B6350A9B60")]
|
|
[ComVisible(true)]
|
|
public class WsmanConnection : IWsmanConnection
|
|
{
|
|
#region Consts
|
|
|
|
/// <summary>
|
|
/// consts defining username and password limits
|
|
/// </summary>
|
|
public const int MAX_PWD_LENGTH = 32;
|
|
public const int MIN_PWD_LENGTH = 8;
|
|
public const int MAX_USERNAME_LENGTH = 32;
|
|
|
|
private const string DIGEST_AUTH = "Digest";
|
|
private const string KERBEROS_AUTH = "Negotiate";
|
|
|
|
private const int CONNECTION_TIMEOUT = 10060;
|
|
private const int CONNECTION_REFUSED = 10061;
|
|
#endregion
|
|
|
|
#region Fields
|
|
|
|
private Uri _address = new Uri("http://localhost:16992/wsman");
|
|
private string _username = string.Empty;
|
|
private SecureString _password = new SecureString();
|
|
private string _scheme = KERBEROS_AUTH;
|
|
private SecureString _dmp = null;
|
|
private DateTime _serverTime;
|
|
|
|
private HttpClient _client;
|
|
|
|
private readonly WsmanConnectionOptions _options = new WsmanConnectionOptions();
|
|
private IWsmanFault _lastError = null;
|
|
private ClientRequest _reqObj = null;
|
|
|
|
private bool _changed = true;
|
|
|
|
private static readonly object _nsLock = new object();
|
|
private static IDictionary<string, string> _nsResolver;
|
|
private static readonly System.Diagnostics.TraceSource _trace = new System.Diagnostics.TraceSource("Intel.Management.Wsman");
|
|
|
|
internal bool? allowCertificateError = null;
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
/// <summary>
|
|
/// Event that raised once we get the server certificate
|
|
/// </summary>
|
|
public static event Action<X509Certificate2> ServerCertRaised;
|
|
#endregion
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Initializes a new instance of the WsmanConnection class
|
|
/// </summary>
|
|
public WsmanConnection()
|
|
{
|
|
// add default namespaces
|
|
lock (_nsLock)
|
|
{
|
|
if (_nsResolver == null)
|
|
{
|
|
_nsResolver = new Dictionary<string, string>
|
|
{
|
|
{ "CIM", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/" },
|
|
{ "AMT", "http://intel.com/wbem/wscim/1/amt-schema/1/" },
|
|
{ "IPS", "http://intel.com/wbem/wscim/1/ips-schema/1/" },
|
|
{ "Win32", "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/" },
|
|
{ "Datetime", "http://schemas.dmtf.org/wbem/wscim/1/common" },
|
|
{ "Interval", "http://schemas.dmtf.org/wbem/wscim/1/common" },
|
|
{ "AssociatedInstances", "http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" },
|
|
{ "*", "http://schemas.dmtf.org/wbem/wscim/1" }
|
|
};
|
|
}
|
|
}
|
|
|
|
// Enforcing TLS1.2 and TLS1.1 - This is a W\A when debugging with Visual Studio (.NET doesn't add these protocols when debugging)
|
|
// Issue does not occur when running with CLI.
|
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11;
|
|
}
|
|
#endregion
|
|
|
|
#region Finalizer
|
|
/// <summary>
|
|
/// Disposes Client and Handler
|
|
/// </summary>
|
|
~WsmanConnection()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the username the connection will use to authenticate
|
|
/// </summary>
|
|
public string Username
|
|
{
|
|
get
|
|
{
|
|
return _username;
|
|
}
|
|
set
|
|
{
|
|
int pos = -1;
|
|
if (value == null)
|
|
value = string.Empty;
|
|
else
|
|
pos = value.IndexOf('\\');
|
|
if (pos > 0 || value == string.Empty) //Kerberos
|
|
{
|
|
AuthenticationScheme = KERBEROS_AUTH;
|
|
//Accepting null value for Kerberos connection
|
|
var newName = value;
|
|
_changed = _changed || _username != newName;
|
|
_username = newName;
|
|
if (_username.Length > MAX_USERNAME_LENGTH)
|
|
throw new WsmanConnectionException("Username can contain up to 32 characters.");
|
|
}
|
|
else //Digest
|
|
{
|
|
AuthenticationScheme = DIGEST_AUTH;
|
|
if (pos == 0)
|
|
{
|
|
value = value.Substring(1);
|
|
}
|
|
if (string.IsNullOrEmpty(value))
|
|
throw new WsmanConnectionException("Username null or an empty string.");
|
|
_changed = _changed || _username != value;
|
|
_username = value;
|
|
if (_username.Length > MAX_USERNAME_LENGTH)
|
|
throw new WsmanConnectionException("Username can contain up to 32 characters.");
|
|
}
|
|
_dmp = null;
|
|
ReqObj = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the password the connection will use to authenticate
|
|
/// </summary>
|
|
public SecureString Password
|
|
{
|
|
get
|
|
{
|
|
return _password;
|
|
}
|
|
set
|
|
{
|
|
if (string.IsNullOrEmpty(_scheme))
|
|
throw new WsmanConnectionException("Cannot initialize password, authentication scheme is null or empty.");
|
|
|
|
if (value == null)
|
|
{
|
|
//Kerberos allows empty password
|
|
if (_scheme == KERBEROS_AUTH)
|
|
{
|
|
_changed = _changed || _password.Length > 0 ;
|
|
_password?.Dispose();
|
|
_password = new SecureString();
|
|
}
|
|
else
|
|
throw new WsmanConnectionException("Password is null or an empty string.");
|
|
}
|
|
else
|
|
{
|
|
if (value.Length < MIN_PWD_LENGTH || value.Length > MAX_PWD_LENGTH)
|
|
throw new WsmanConnectionException("Password can contain between 8 to 32 characters.");
|
|
|
|
_changed = _changed || !_password.ValueEquals(value);
|
|
_password?.Dispose();
|
|
_password = value;
|
|
}
|
|
|
|
_dmp = null;
|
|
ReqObj = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the address that will be associated with this connection
|
|
/// </summary>
|
|
///
|
|
public string Address
|
|
{
|
|
get
|
|
{
|
|
return _address.ToString();
|
|
}
|
|
set
|
|
{
|
|
if (!Uri.IsWellFormedUriString(value, 0))
|
|
throw new WsmanConnectionException("Invalid argument - Address is not a valid uri");
|
|
var newUri = new Uri(value);
|
|
_changed = _changed || _address != newUri;
|
|
_address = newUri;
|
|
ReqObj = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the authentication scheme the connection will use
|
|
/// </summary>
|
|
public string AuthenticationScheme
|
|
{
|
|
get
|
|
{
|
|
return _scheme;
|
|
}
|
|
set
|
|
{
|
|
_changed = _changed || _scheme != value;
|
|
_scheme = value;
|
|
ReqObj = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Connection Options
|
|
/// </summary>
|
|
public IWsmanConnectionOptions Options => _options;
|
|
|
|
private ClientRequest ReqObj
|
|
{
|
|
get => _reqObj;
|
|
set
|
|
{
|
|
_reqObj?.Dispose();
|
|
_reqObj = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the last error that occured when using the connection
|
|
/// </summary>
|
|
public IWsmanFault LastError => _lastError;
|
|
|
|
public DateTime ServerTime => _serverTime;
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
private bool _disposed = false;
|
|
|
|
/// <summary>
|
|
/// Implement IDisposable method
|
|
/// </summary>
|
|
/// <param name="disposing"></param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
if (disposing)
|
|
{
|
|
_client?.Dispose();
|
|
_client = null;
|
|
_dmp?.Dispose();
|
|
Password?.Dispose();
|
|
Options?.Dispose();
|
|
ReqObj?.Dispose();
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves a class name prefix to a full resourceUri
|
|
/// </summary>
|
|
/// <param name="prefix"></param>
|
|
/// <return>The resoved resourceUri</return>
|
|
public string ResolveNamespace(string prefix)
|
|
{
|
|
string result = null;
|
|
lock (_nsLock)
|
|
{
|
|
_nsResolver.TryGetValue(prefix, out result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a namespaceUri for a class name prefix
|
|
/// </summary>
|
|
/// <param name="prefix"></param>
|
|
/// <param name="ns"></param>
|
|
public void RegisterNamespace(string prefix, string ns)
|
|
{
|
|
lock (_nsLock)
|
|
{
|
|
_nsResolver[prefix] = ns;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes the connection
|
|
/// </summary>
|
|
/// <remarks>Forces future request to use a new network connection</remarks>
|
|
public void Close()
|
|
{
|
|
ReqObj?.Dispose();
|
|
_dmp?.Dispose();
|
|
_client?.Dispose();
|
|
_client = null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Tests the connection to see if the endpoint supports Wsman
|
|
/// </summary>
|
|
public IManagedInstance Identify()
|
|
{
|
|
return (IManagedInstance)SubmitRequest(WsmanResources.IDENTIFY_XML, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a reference to a object supported by a Wsman Service
|
|
/// </summary>
|
|
/// <param name="resourceUri">A string describing the object reference</param>
|
|
/// <return>A IManagedReference pointing to the object</return>
|
|
public IManagedReference NewReference(string resourceUri)
|
|
{
|
|
ManagedReference wsRef = new ManagedReference(this, resourceUri);
|
|
return wsRef;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new representation of a CIM object that can be exchanged with the server
|
|
/// </summary>
|
|
/// <param name="resourceUri">A string describing the instance</param>
|
|
/// <return>A IManagedInstance representing the resource</return>
|
|
public IManagedInstance NewInstance(string resourceUri)
|
|
{
|
|
ManagedInstance inst = new ManagedInstance(this, resourceUri);
|
|
return inst;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a simple SQL type query
|
|
/// </summary>
|
|
/// <param name="queryString"></param>
|
|
/// <return>An IWsmanEnumeration containing resulting items</return>
|
|
public IWsmanEnumeration ExecQuery(string queryString)
|
|
{
|
|
IWsmanEnumeration result = null;
|
|
IManagedReference objRef = GetReferenceFromSQL(queryString);
|
|
|
|
try
|
|
{
|
|
result = objRef.Enumerate(null, null);
|
|
}
|
|
catch (WsmanUnreachableException)
|
|
{
|
|
result = new WsmanEnumeration(this, null, null);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a simple SQL type query
|
|
/// </summary>
|
|
/// <param name="queryString"></param>
|
|
/// <return>An IManagedReference to the object described by the SQL query string</return>
|
|
public IManagedReference GetReferenceFromSQL(string queryString)
|
|
{
|
|
string tableName = string.Empty;
|
|
int pos = -1;
|
|
IManagedReference result = null;
|
|
const string SELECT_CLAUSE = "select * from ";
|
|
|
|
|
|
tableName = queryString.Substring(SELECT_CLAUSE.Length).TrimStart();
|
|
queryString = tableName;
|
|
|
|
pos = tableName.IndexOf(" ");
|
|
if (pos > 0)
|
|
{
|
|
tableName = tableName.Substring(0, pos);
|
|
queryString = queryString.Substring(pos).Trim();
|
|
}
|
|
if (tableName.Equals(string.Empty))
|
|
{
|
|
throw new WsmanQueryFormatException("Missing Resource Name");
|
|
}
|
|
|
|
result = NewReference(tableName);
|
|
|
|
//check where clause
|
|
if (queryString.ToLower().StartsWith("where"))
|
|
{
|
|
string name = null;
|
|
string value = null;
|
|
queryString = queryString.Substring("where".Length).Trim();
|
|
while (!queryString.Equals(string.Empty))
|
|
{
|
|
pos = queryString.IndexOf("=");
|
|
if (pos <= 0)
|
|
throw new WsmanQueryFormatException("Missing property name");
|
|
name = queryString.Substring(0, pos);
|
|
value = queryString.Substring(pos + 1).TrimStart();
|
|
queryString = value;
|
|
if (value.StartsWith("'"))
|
|
{
|
|
pos = value.IndexOf("'", 1);
|
|
if (pos > 0)
|
|
{
|
|
value = value.Substring(1, pos - 1);
|
|
queryString = queryString.Substring(pos + 1).TrimStart();
|
|
}
|
|
else
|
|
throw new WsmanQueryFormatException("Missing close quote");
|
|
}
|
|
else if (value.StartsWith("\""))
|
|
{
|
|
pos = value.IndexOf("\"", 1);
|
|
if (pos > 0)
|
|
{
|
|
value = value.Substring(1, pos - 1);
|
|
queryString = queryString.Substring(pos + 1).TrimStart();
|
|
}
|
|
else
|
|
throw new WsmanQueryFormatException("Missing close quote");
|
|
}
|
|
else
|
|
{
|
|
pos = value.IndexOf(" ");
|
|
if (pos > 0)
|
|
{
|
|
value = value.Substring(0, pos);
|
|
queryString = queryString.Substring(pos).TrimStart();
|
|
}
|
|
else
|
|
{
|
|
queryString = queryString.Substring(value.Length).TrimStart();
|
|
}
|
|
|
|
}
|
|
if (queryString.ToLower().StartsWith("and "))
|
|
{
|
|
queryString = queryString.Substring("and ".Length).TrimStart();
|
|
}
|
|
|
|
result.AddSelector(name, value);
|
|
|
|
}//end while where
|
|
}//end if where
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Submits a Wsman command to the server
|
|
/// </summary>
|
|
/// <param name="requestString">The string command to send</param>
|
|
/// <param name="refObj">The reference the Wsman command will use</param>
|
|
/// <param name="input">Message Parameters</param>
|
|
/// <returns>An object representing the results of the command</returns>
|
|
public object SubmitRequest(string requestString, IManagedReference refObj, IManagedInstance input)
|
|
{
|
|
XmlDocument reqDoc = new XmlDocument();
|
|
reqDoc.LoadXml(requestString);
|
|
return SubmitRequest(reqDoc, refObj, input);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Submits a Wsman command to the server
|
|
/// </summary>
|
|
/// <param name="reqDoc">The XML document to send</param>
|
|
/// <param name="refObj">The reference the Wsman command will use</param>
|
|
/// <param name="input">Message Parameters</param>
|
|
/// <returns>An object representing the results of the command</returns>
|
|
public object SubmitRequest(XmlDocument reqDoc, IManagedReference refObj, IManagedInstance input)
|
|
{
|
|
//load the requesting namespace manager
|
|
XmlNamespaceManager ns = LoadNamespaces(reqDoc);
|
|
|
|
// Set the MessageID
|
|
string reqUUID = "uuid:" + Guid.NewGuid().ToString().ToUpper();
|
|
XmlNode node = reqDoc.DocumentElement.SelectSingleNode("s:Header/wsa:MessageID", ns);
|
|
if (node != null)
|
|
node.InnerText = reqUUID;
|
|
|
|
SetEpr(refObj, reqDoc, ns);
|
|
|
|
//add input
|
|
if (input != null)
|
|
{
|
|
node = reqDoc.DocumentElement.SelectSingleNode("s:Body", ns);
|
|
if (node != null)
|
|
node.InnerXml = input.Xml;
|
|
}
|
|
MergeNamespaces(reqDoc, ns);
|
|
|
|
TraceDocument(reqDoc);
|
|
|
|
|
|
if (ReqObj == null)
|
|
{
|
|
if (Options.ClientCertificate != null)
|
|
{
|
|
X509Certificate2 cert2 = (X509Certificate2)Options.ClientCertificate;
|
|
//check provisioning cert
|
|
if (_options.IsSetupCertificate(cert2))
|
|
{
|
|
ReqObj = CreateRequest();
|
|
}
|
|
}
|
|
}
|
|
|
|
return ReqObj != null
|
|
? SendObjectRequest(reqUUID, reqDoc, refObj, input)
|
|
: SendWebRequest(reqUUID, reqDoc, refObj);
|
|
}
|
|
|
|
public void TraceDocument(XmlDocument doc)
|
|
{
|
|
TraceDocument(doc, System.Diagnostics.TraceEventType.Information);
|
|
}
|
|
|
|
public void TraceDocument(XmlDocument doc, System.Diagnostics.TraceEventType eventType)
|
|
{
|
|
if (_trace.Switch.ShouldTrace(eventType))
|
|
{
|
|
StringBuilder builder = new StringBuilder(Environment.NewLine);
|
|
XmlWriterSettings settings = new XmlWriterSettings();
|
|
settings.Encoding = Encoding.Unicode;
|
|
|
|
settings.ConformanceLevel = ConformanceLevel.Document;
|
|
settings.Indent = true;
|
|
settings.IndentChars = (" ");
|
|
XmlWriter writer = XmlWriter.Create(builder, settings);
|
|
doc.WriteContentTo(writer);
|
|
writer.Close();
|
|
_trace.TraceInformation(builder.ToString());
|
|
}
|
|
}
|
|
|
|
public static System.Diagnostics.TraceSource TraceSource
|
|
{
|
|
get { return _trace; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the host and port used by the connection
|
|
/// </summary>
|
|
/// <param name="hostname">The host name or IP address of the Wsman service</param>
|
|
/// <param name="port">The port the Wsman service is listening on</param>
|
|
public void SetHost(string hostname, string port)
|
|
{
|
|
StringBuilder builder = new StringBuilder();
|
|
if (port.Equals("16993"))
|
|
builder.Append("https://");
|
|
else
|
|
builder.Append("http://");
|
|
|
|
builder.Append(hostname);
|
|
if (!port.Equals(string.Empty))
|
|
{
|
|
builder.Append(":");
|
|
builder.Append(port);
|
|
}
|
|
builder.Append("/wsman");
|
|
Address = builder.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the connection credentials
|
|
/// </summary>
|
|
/// <param name="name">The user name</param>
|
|
/// <param name="pass">Secure Password object</param>
|
|
/// <remarks>Use this for passing a System.Security.SecureString</remarks>
|
|
public void SetCredentials(string name, SecureString pass)
|
|
{
|
|
|
|
if (name.Equals(string.Empty) || name.IndexOf('\\') > 0)
|
|
{
|
|
AuthenticationScheme = KERBEROS_AUTH;
|
|
}
|
|
else
|
|
{
|
|
AuthenticationScheme = DIGEST_AUTH;
|
|
}
|
|
Username = name;
|
|
Password = pass;
|
|
}
|
|
|
|
public bool Equals(WsmanConnection other)
|
|
{
|
|
if (ReferenceEquals(null, other)) return false;
|
|
if (ReferenceEquals(this, other)) return true;
|
|
return Equals(other._username, _username) && Equals(other.Password.ValueEquals(Password)) && Equals(other._address, _address);
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (ReferenceEquals(null, obj)) return false;
|
|
if (ReferenceEquals(this, obj)) return true;
|
|
if (obj.GetType() != typeof(WsmanConnection)) return false;
|
|
return Equals((WsmanConnection)obj);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
unchecked
|
|
{
|
|
int result = (_username != null ? _username.GetHashCode() : 0);
|
|
result = (result * 397) ^ (Password != null ? Password.GetHashCode() : 0);
|
|
result = (result * 397) ^ (Address != null ? Address.GetHashCode() : 0);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
#endregion //Public Methods
|
|
|
|
#region Private Methods
|
|
|
|
private object SendObjectRequest(string msgId, XmlDocument reqDoc, IManagedReference refObj, IManagedInstance input)
|
|
{
|
|
XmlDocument resDoc = null;
|
|
Exception reqExp;
|
|
TraceDocument(reqDoc);
|
|
ServerResponse response = RetryLoop(reqDoc, out reqExp);
|
|
if (response == null)
|
|
{
|
|
Close();
|
|
throw new WsmanConnectionException(reqExp.Message, reqExp);
|
|
}
|
|
if (response.StatusCode >= 400)
|
|
{
|
|
XmlDocument faultDoc = null;
|
|
switch (response.StatusCode)
|
|
{
|
|
case 400:
|
|
faultDoc = response.GetXml();
|
|
_lastError = WsmanFault.GetXmlFault(faultDoc);
|
|
if (_lastError.Subcode.Equals("DestinationUnreachable"))
|
|
{
|
|
TraceDocument(faultDoc, System.Diagnostics.TraceEventType.Warning);
|
|
throw new WsmanUnreachableException(_lastError.Reason);
|
|
}
|
|
TraceDocument(faultDoc, System.Diagnostics.TraceEventType.Error);
|
|
throw new WsmanException(_lastError.Reason);
|
|
case 500:
|
|
faultDoc = response.GetXml();
|
|
_lastError = WsmanFault.GetXmlFault(faultDoc);
|
|
TraceDocument(faultDoc, System.Diagnostics.TraceEventType.Error);
|
|
if (response.StatusCode == 500)
|
|
throw new WsmanRecieverFault(_lastError.Reason);
|
|
break;
|
|
case 401:
|
|
throw new WsmanUnauthorizedException("Unauthorized");
|
|
default:
|
|
throw new WsmanException(response.StatusText);
|
|
}
|
|
|
|
}
|
|
|
|
_serverTime = response.Date;
|
|
resDoc = response.GetXml();
|
|
|
|
TraceDocument(resDoc);
|
|
return GetResponseObject(refObj, msgId, resDoc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initiate HTTPClient Handler
|
|
/// </summary>
|
|
/// <returns> HTTPClient Handler </returns>
|
|
private HttpClientHandler CreateHttpHandler()
|
|
{
|
|
var handler = new HttpClientHandler();
|
|
handler.AllowAutoRedirect = false;
|
|
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
|
|
handler.Proxy = _options.Proxy;
|
|
|
|
//register callback to the certificate call back
|
|
if (Options.ServerCertificateValidationCallback != null)
|
|
handler.ServerCertificateCustomValidationCallback += CertificateValidationCallback;
|
|
if (Options.AcceptSelfSignedCertificate)
|
|
handler.ServerCertificateCustomValidationCallback += SelfSignedCertificateCallback;
|
|
|
|
if (Options.ClientCertificate != null)
|
|
{
|
|
handler.ClientCertificates.Add(Options.ClientCertificate);
|
|
}
|
|
|
|
NetworkCredential networkCredential = null;
|
|
switch (_scheme)
|
|
{
|
|
case KERBEROS_AUTH:
|
|
int pos = _username.IndexOf('\\');
|
|
string domain = null;
|
|
string user = null;
|
|
if (pos > 0)
|
|
{
|
|
domain = _username.Substring(0, pos);
|
|
user = _username.Substring(pos + 1);
|
|
}
|
|
|
|
if (domain != null && !domain.Equals(string.Empty))
|
|
{
|
|
networkCredential = new NetworkCredential(user, Password.Copy(), domain);
|
|
}
|
|
|
|
if (!AuthenticationManager.CustomTargetNameDictionary.ContainsKey(_address.ToString()))
|
|
{
|
|
var spnPort = "HTTP/" + _address.Authority.ToUpper();
|
|
AuthenticationManager.CustomTargetNameDictionary.Add(_address.ToString(), spnPort);
|
|
}
|
|
|
|
handler.PreAuthenticate = true;
|
|
break;
|
|
case DIGEST_AUTH:
|
|
networkCredential = new NetworkCredential(_username, Password.Copy());
|
|
break;
|
|
}
|
|
if (networkCredential != null)
|
|
{
|
|
handler.Credentials = new CredentialCache { { _address, _scheme, networkCredential } };
|
|
}
|
|
else
|
|
{
|
|
handler.Credentials = CredentialCache.DefaultNetworkCredentials.GetCredential(_address, KERBEROS_AUTH);
|
|
}
|
|
return handler;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initiate HTTClient
|
|
/// </summary>
|
|
/// <returns>Initiated HTTP Client</returns>
|
|
private void CreateHttpClient()
|
|
{
|
|
_client = new HttpClient(CreateHttpHandler());
|
|
_client.BaseAddress = _address;
|
|
_client.Timeout = TimeSpan.FromMilliseconds(Options.Timeout);
|
|
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
|
|
_client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
|
|
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true };
|
|
_client.DefaultRequestHeaders.Connection.Add("keep-alive");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Build, send HTTPClient and send request
|
|
/// </summary>
|
|
/// <param name="reqDoc"></param>
|
|
/// <param name="refObj"></param>
|
|
/// <param name="msgId"></param>
|
|
/// <exception cref="WsmanUnreachableException"></exception>
|
|
/// <exception cref="WsmanSenderException"></exception>
|
|
/// <exception cref="WsmanRecieverFault"></exception>
|
|
/// <exception cref="WsmanUnauthorizedException"></exception>
|
|
/// <exception cref="WsmanConnectionException"></exception>
|
|
private async Task<object> SendHttpClient(XmlDocument reqDoc, IManagedReference refObj, string msgId)
|
|
{
|
|
int retry = 2;
|
|
if (_changed && _client != null)
|
|
{
|
|
Close();
|
|
_changed = false;
|
|
}
|
|
if (_client == null)
|
|
{
|
|
CreateHttpClient();
|
|
}
|
|
while (retry > 0) //for PG use case
|
|
{
|
|
DateTime startTime = DateTime.Now;
|
|
string response = string.Empty;
|
|
HttpStatusCode statusCode = HttpStatusCode.OK;
|
|
try
|
|
{
|
|
XmlDocument resDoc;
|
|
using (var content = new StringContent(reqDoc.InnerXml, Encoding.UTF8, "application/soap+xml"))
|
|
using (var responseMessage = await _client.PostAsync(_address, content).ConfigureAwait(false))
|
|
{
|
|
response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
statusCode = responseMessage.StatusCode;
|
|
responseMessage.EnsureSuccessStatusCode();
|
|
resDoc = new XmlDocument();
|
|
resDoc.LoadXml(response);
|
|
}
|
|
|
|
TraceDocument(resDoc);
|
|
return GetResponseObject(refObj, msgId, resDoc);
|
|
}
|
|
catch (XmlException)
|
|
{
|
|
throw new WsmanConnectionException(response);
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
TimeSpan span = DateTime.Now.Subtract(startTime);
|
|
if (span.TotalMilliseconds > Options.Timeout)
|
|
{
|
|
return null;
|
|
}
|
|
retry = 1;
|
|
}
|
|
catch (HttpRequestException httpReqExp)
|
|
{
|
|
int ErrorCode = 0;
|
|
if (httpReqExp.InnerException is SocketException se1) // This exception is received when compiling for .net 6
|
|
{
|
|
ErrorCode = se1.ErrorCode;
|
|
}
|
|
else if (httpReqExp.InnerException?.InnerException is SocketException se2) // When compiling for .net framework 48, a webSocket exception is received, with SocketException as an inner exception
|
|
{
|
|
ErrorCode = se2.ErrorCode;
|
|
}
|
|
if (ErrorCode == CONNECTION_TIMEOUT || ErrorCode == CONNECTION_REFUSED)
|
|
{
|
|
TimeSpan span = DateTime.Now.Subtract(startTime);
|
|
if (span.TotalMilliseconds > Options.Timeout)
|
|
{
|
|
return null; //if we exceeded time out don't wait by try one more time
|
|
}
|
|
System.Threading.Thread.Sleep(1000);
|
|
retry--;
|
|
continue;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(response))
|
|
{
|
|
var exp = httpReqExp.InnerException ?? httpReqExp;
|
|
throw new WsmanConnectionException(exp.Message, exp);
|
|
}
|
|
switch (statusCode)
|
|
{
|
|
case HttpStatusCode.BadRequest when _lastError?.Subcode != null && _lastError.Subcode.Equals("DestinationUnreachable"):
|
|
throw new WsmanUnreachableException(WsmanFault.GetXmlFault(response).Reason, httpReqExp);
|
|
case HttpStatusCode.BadRequest:
|
|
throw new WsmanSenderException(WsmanFault.GetXmlFault(response).Reason, httpReqExp);
|
|
case HttpStatusCode.InternalServerError:
|
|
throw new WsmanRecieverFault(WsmanFault.GetXmlFault(response).Reason, httpReqExp);
|
|
case HttpStatusCode.Unauthorized:
|
|
throw new WsmanUnauthorizedException(statusCode.ToString(), httpReqExp);
|
|
}
|
|
throw new WsmanConnectionException(statusCode.ToString(), httpReqExp);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private object SendWebRequest(string msgId, XmlDocument reqDoc, IManagedReference refObj)
|
|
{
|
|
var output = Task.Run(()=>SendHttpClient(reqDoc, refObj, msgId)).GetAwaiter().GetResult();
|
|
if (output == null)
|
|
{
|
|
throw new WsmanConnectionException("Unable to connect", new WebException("Max Retry attempts reached", WebExceptionStatus.RequestCanceled));
|
|
}
|
|
return output;
|
|
}
|
|
|
|
private ServerResponse RetryLoop(XmlDocument reqDoc, out Exception resultExp)
|
|
{
|
|
ServerResponse result = null;
|
|
resultExp = null;
|
|
|
|
DateTime startTime = DateTime.Now;
|
|
int retryCount = 13;//limit the retry loop by count and time
|
|
while (retryCount > 0)
|
|
{
|
|
|
|
try
|
|
{
|
|
result = ReqObj.Send(reqDoc);
|
|
retryCount = 0;
|
|
}
|
|
catch (SocketException sockExp)
|
|
{
|
|
|
|
//WSATIMEOUT 10060 WSACONNECTIONREFUSED 10061
|
|
if (sockExp.ErrorCode == 10060 || sockExp.ErrorCode == 10061)//
|
|
{
|
|
retryCount--;
|
|
Close();
|
|
}
|
|
else
|
|
{
|
|
retryCount = 0;
|
|
resultExp = sockExp;
|
|
}
|
|
}
|
|
catch (Exception nonSockExp)
|
|
{
|
|
retryCount = 0;
|
|
resultExp = nonSockExp;
|
|
}
|
|
|
|
TimeSpan span = DateTime.Now.Subtract(startTime);
|
|
if (span.TotalSeconds > Options.Timeout)
|
|
retryCount = 0;
|
|
|
|
if (retryCount > 0)
|
|
System.Threading.Thread.Sleep(1000);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private ClientRequest CreateRequest()
|
|
{
|
|
ClientRequest reqObj = null;
|
|
reqObj = Options.InternetProtocol == InternetProtocolType.Ipv6
|
|
? new ClientRequest(_address, AddressFamily.InterNetworkV6)
|
|
: new ClientRequest(_address, AddressFamily.InterNetwork);
|
|
MpsManager mps = null;
|
|
try
|
|
{
|
|
if (Options.ClientCertificate != null)
|
|
{
|
|
X509Certificate2 cert2 = (X509Certificate2)Options.ClientCertificate;
|
|
|
|
reqObj.SetCertificate(cert2);
|
|
// We are assuming setup certificate is used
|
|
reqObj.SetCertificateOptions(false, false, true);
|
|
}
|
|
|
|
switch (_scheme)
|
|
{
|
|
case DIGEST_AUTH when Options.UseDigestMasterPassword:
|
|
GetDmp();
|
|
break;
|
|
case DIGEST_AUTH:
|
|
_dmp = null;
|
|
reqObj.SetDigestAuthorization(_username, Password.Copy());
|
|
break;
|
|
case KERBEROS_AUTH when Password == null || Password.Length == 0:
|
|
{
|
|
reqObj.SetWindowsAuthorization(null, null, new SecureString(), Options.ServiceName);
|
|
break;
|
|
}
|
|
case KERBEROS_AUTH:
|
|
{
|
|
string[] cred = _username.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
|
reqObj.SetWindowsAuthorization(cred[1], cred[0], Password.Copy(), Options.ServiceName);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
if (_scheme.EndsWith("Anonymous"))
|
|
{
|
|
reqObj.SetAnonymousAuthorization();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
mps = new MpsManager();
|
|
HttpProxy proxy = null;
|
|
if (!string.IsNullOrEmpty(Options.ProxyAddress))
|
|
{
|
|
proxy = new HttpProxy(Options.ProxyAddress, Options.ProxyUser, Options.ProxyPassword);
|
|
reqObj.SetProxy(proxy);
|
|
}
|
|
else if (mps.IsConnected(_address.Host))
|
|
{
|
|
proxy = new HttpProxy(mps.HttpProxy, mps.HttpUser, mps.HttpPassword);
|
|
reqObj.SetProxy(proxy);
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
reqObj.Dispose();
|
|
}
|
|
finally
|
|
{
|
|
mps?.Dispose();
|
|
}
|
|
|
|
return reqObj;
|
|
}
|
|
|
|
private void GetDmp()
|
|
{
|
|
ClientRequest reqObj = CreateRequest();
|
|
|
|
XmlDocument reqDoc = new XmlDocument();
|
|
reqDoc.LoadXml(WsmanResources.IDENTIFY_XML);
|
|
|
|
ServerResponse response = null;
|
|
try
|
|
{
|
|
response = ReqObj.Send(reqDoc);
|
|
}
|
|
catch (Exception exp)
|
|
{
|
|
throw new WsmanConnectionException(exp.Message, exp);
|
|
}
|
|
|
|
if (response != null && response.StatusCode >= 400)
|
|
{
|
|
switch (response.StatusCode)
|
|
{
|
|
case 400:
|
|
_lastError = WsmanFault.GetXmlFault(response.GetXml());
|
|
if (_lastError.Subcode.Equals("DestinationUnreachable"))
|
|
throw new WsmanUnreachableException(_lastError.Reason);
|
|
throw new WsmanException(_lastError.Reason);
|
|
case 401:
|
|
break;
|
|
case 500:
|
|
_lastError = WsmanFault.GetXmlFault(response.GetXml());
|
|
if (response.StatusCode == 500)
|
|
throw new WsmanRecieverFault(_lastError.Reason);
|
|
break;
|
|
default:
|
|
throw new WsmanException(response.StatusText);
|
|
}
|
|
}
|
|
|
|
string[] list = ((AnonymousAuthorization)reqObj.GetAuthorization()).GetAuthorizations();
|
|
foreach (string item in list)
|
|
{
|
|
if (item.StartsWith("WWW-Authenticate: Digest"))
|
|
{
|
|
string token = "realm=\"";
|
|
int pos1 = item.IndexOf(token);
|
|
|
|
if (pos1 > 0)
|
|
{
|
|
byte[] key = Password.ConvertToByteArray();
|
|
pos1 += token.Length;
|
|
int pos2 = item.IndexOf("\"", pos1);
|
|
string realm = item.Substring(pos1, pos2 - pos1);
|
|
|
|
if (string.IsNullOrEmpty(_username) || key == null)
|
|
{
|
|
throw new WsmanConnectionException("Username or password are null or empty.");
|
|
}
|
|
|
|
using (HMACSHA256 sha2 = new HMACSHA256(key))
|
|
{
|
|
byte[] hash = sha2.ComputeHash(Encoding.ASCII.GetBytes(realm + _username));
|
|
_dmp = hash.ConvertByteArrayToSecureString(); ;
|
|
|
|
reqObj.SetDigestAuthorization(_username, _dmp);
|
|
ReqObj = reqObj;
|
|
Array.Clear(hash, 0, hash.Length);
|
|
}
|
|
|
|
Array.Clear(key,0,key.Length);
|
|
}
|
|
}
|
|
}
|
|
if (ReqObj != reqObj)
|
|
reqObj.Dispose();
|
|
}
|
|
|
|
private void SetEpr(IManagedReference refObj, XmlDocument document, XmlNamespaceManager ns)
|
|
{
|
|
XmlNode node = document.DocumentElement.SelectSingleNode("s:Header/wsa:To", ns);
|
|
if (node != null)
|
|
node.InnerText = _address.ToString();
|
|
|
|
if (refObj != null)
|
|
{
|
|
node = document.DocumentElement.SelectSingleNode("s:Header/wsman:ResourceURI", ns);
|
|
if (node != null)
|
|
node.InnerText = refObj.ResourceUri;
|
|
|
|
node = document.DocumentElement.SelectSingleNode("s:Body/n:Enumerate", ns);
|
|
if (node == null)
|
|
{
|
|
node = document.DocumentElement.SelectSingleNode("s:Header/wsman:SelectorSet", ns);
|
|
|
|
if (node != null)
|
|
{
|
|
//add namespaces to parent
|
|
node.InnerXml = refObj.Xml;
|
|
|
|
// fix embedding
|
|
XmlNode setNode = node.SelectSingleNode("wsa:EndpointReference/wsa:ReferenceParameters/wsman:SelectorSet", ns);
|
|
node.InnerXml = setNode != null ? setNode.InnerXml : string.Empty;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MergeNamespaces(XmlDocument document, XmlNamespaceManager ns)
|
|
{
|
|
XmlNode addrNode = document.DocumentElement.SelectSingleNode("s:Header/wsa:To", ns);
|
|
XmlNode uriNode = document.DocumentElement.SelectSingleNode("s:Header/wsman:ResourceURI", ns);
|
|
|
|
//Merge Addressing Namespaces
|
|
if (addrNode != null && uriNode != null)
|
|
{
|
|
XmlNodeList list = document.DocumentElement.GetElementsByTagName("*");
|
|
foreach (XmlNode listNode in list)
|
|
{
|
|
if (listNode.NamespaceURI.Equals(addrNode.NamespaceURI))
|
|
listNode.Prefix = addrNode.Prefix;
|
|
else if (listNode.NamespaceURI.Equals(uriNode.NamespaceURI))
|
|
listNode.Prefix = uriNode.Prefix;
|
|
}
|
|
}
|
|
}
|
|
|
|
private object GetResponseObject(IManagedReference refObj, string reqId, XmlDocument document)
|
|
{
|
|
object result = null;
|
|
XmlNamespaceManager ns = LoadNamespaces(document);
|
|
|
|
// check request ID
|
|
XmlNode node = document.DocumentElement.SelectSingleNode("s:Header/wsa:RelatesTo", ns);
|
|
if (node != null)
|
|
if (node.InnerText != null && !node.InnerText.Equals(reqId))
|
|
throw new IOException(WsmanResources.ID_MISMATCH);
|
|
|
|
node = document.DocumentElement.SelectSingleNode("s:Header/wsa:Action", ns);
|
|
|
|
// process a Get response
|
|
if (node == null)
|
|
{
|
|
ns.AddNamespace("wsmid", "http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd");
|
|
node = document.DocumentElement.SelectSingleNode("s:Body/wsmid:IdentifyResponse", ns);
|
|
if (node != null)
|
|
result = new ManagedInstance(this, node.OuterXml);
|
|
}
|
|
else if (node.InnerText != null && node.InnerText.Equals(WsmanResources.GET_RESPONSE))
|
|
{
|
|
node = document.DocumentElement.SelectSingleNode("s:Body", ns);
|
|
if (node != null)
|
|
{
|
|
node = node.FirstChild;
|
|
if (node != null)
|
|
{
|
|
while (node != null && node.NodeType != XmlNodeType.Element)
|
|
node = node.NextSibling;
|
|
|
|
result = new ManagedInstance(this, node.OuterXml);
|
|
}
|
|
}
|
|
}
|
|
else if (node.InnerText != null && node.InnerText.Equals(WsmanResources.ENUM_RESPONSE))
|
|
{
|
|
// create a Wsman Pull document
|
|
XmlDocument pullDoc = new XmlDocument();
|
|
pullDoc.LoadXml(WsmanResources.PULL_XML);
|
|
XmlNamespaceManager pullNs = LoadNamespaces(pullDoc);
|
|
pullNs.AddNamespace("n", WsmanResources.ENUM_NS);
|
|
ns.AddNamespace("n", WsmanResources.ENUM_NS);
|
|
|
|
// set the EPR
|
|
SetEpr(refObj, pullDoc, pullNs);
|
|
|
|
// Set the enumeration context
|
|
node = document.DocumentElement.SelectSingleNode("s:Body/n:EnumerateResponse/n:EnumerationContext", ns);
|
|
XmlNode pullNode = pullDoc.DocumentElement.SelectSingleNode("s:Body/n:Pull/n:EnumerationContext", pullNs);
|
|
if (pullNode != null && node != null)
|
|
pullNode.InnerText = node.InnerText;
|
|
|
|
//set Max elements
|
|
pullNode = pullDoc.DocumentElement.SelectSingleNode("s:Body/n:Pull/n:MaxElements", pullNs);
|
|
if (pullNode != null)
|
|
pullNode.InnerText = Options.MaxElements.ToString();
|
|
|
|
result = new WsmanEnumeration(this, pullDoc, pullNs);
|
|
|
|
|
|
}
|
|
else if (node.InnerText != null && node.InnerText.Equals(WsmanResources.PULL_RESPONSE))
|
|
{
|
|
result = document;
|
|
}
|
|
else if (node.InnerText != null && node.InnerText.Equals(WsmanResources.CREATE_RESPONSE))
|
|
{
|
|
node = document.DocumentElement.SelectSingleNode("s:Body/wxf:ResourceCreated", ns);
|
|
if (node != null)
|
|
result = ManagedReference.GetEmbeddedRef(this, node, ns);
|
|
}
|
|
else if (node.InnerText != null && node.InnerText.Equals(WsmanResources.SUBSCRIBE_RESPONSE))
|
|
{
|
|
|
|
node = document.DocumentElement.SelectSingleNode("s:Body/wse:SubscribeResponse/wse:SubscriptionManager", ns);
|
|
if (node != null)
|
|
result = ManagedReference.GetEmbeddedRef(this, node, ns);
|
|
}
|
|
else
|
|
{
|
|
node = document.DocumentElement.SelectSingleNode("s:Body", ns);
|
|
if (node != null)
|
|
result = new ManagedInstance(this, node.InnerXml);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static XmlNamespaceManager LoadNamespaces(XmlDocument document)
|
|
{
|
|
XmlNamespaceManager ns = new XmlNamespaceManager(document.NameTable);
|
|
ns.AddNamespace("wsa", WsmanResources.ADDRESSING_NS);
|
|
ns.AddNamespace("wsman", WsmanResources.WSMAN_NS);
|
|
ns.AddNamespace("s", WsmanResources.SOAP_NS);
|
|
ns.AddNamespace("n", WsmanResources.ENUM_NS);
|
|
ns.AddNamespace("wxf", WsmanResources.WSTRANSFER_NS);
|
|
ns.AddNamespace("wse", WsmanResources.WSEVENTING_NS);
|
|
|
|
return ns;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This callback is used to allow the connection even if the certificate has errors (like hostname mismatch etc)
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="certificate"></param>
|
|
/// <param name="chain"></param>
|
|
/// <param name="errors"></param>
|
|
/// <returns></returns>
|
|
private bool CertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain,
|
|
SslPolicyErrors errors)
|
|
{
|
|
//raise the event only once
|
|
if (allowCertificateError == null)
|
|
{
|
|
allowCertificateError = Options.ServerCertificateValidationCallback(certificate, errors);
|
|
}
|
|
return allowCertificateError.Value;
|
|
}
|
|
|
|
private bool SelfSignedCertificateCallback(object sender, X509Certificate certificate, X509Chain chain,
|
|
SslPolicyErrors error)
|
|
{
|
|
//If certificate is self signed, ignore all errors
|
|
if (certificate.Subject.Equals(certificate.Issuer) && Options.AcceptSelfSignedCertificate)
|
|
{
|
|
return true;
|
|
}
|
|
if (error == SslPolicyErrors.None)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endregion //Private Methods
|
|
|
|
} // end WsmanConnection Object
|
|
|
|
}//end Intel.Management.Wsman namespace
|