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
{
///
/// Implementation of the IWsmanConnection interface
///
[Guid("73131C56-E7EA-4c1d-A6E2-03B6350A9B60")]
[ComVisible(true)]
public class WsmanConnection : IWsmanConnection
{
#region Consts
///
/// consts defining username and password limits
///
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 _nsResolver;
private static readonly System.Diagnostics.TraceSource _trace = new System.Diagnostics.TraceSource("Intel.Management.Wsman");
internal bool? allowCertificateError = null;
#endregion
#region Events
///
/// Event that raised once we get the server certificate
///
public static event Action ServerCertRaised;
#endregion
#region Constructors
///
/// Initializes a new instance of the WsmanConnection class
///
public WsmanConnection()
{
// add default namespaces
lock (_nsLock)
{
if (_nsResolver == null)
{
_nsResolver = new Dictionary
{
{ "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
///
/// Disposes Client and Handler
///
~WsmanConnection()
{
Dispose(false);
}
#endregion
#region Properties
///
/// Gets or sets the username the connection will use to authenticate
///
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;
}
}
///
/// Gets or sets the password the connection will use to authenticate
///
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;
}
}
///
/// Gets or sets the address that will be associated with this connection
///
///
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;
}
}
///
/// Gets or sets the authentication scheme the connection will use
///
public string AuthenticationScheme
{
get
{
return _scheme;
}
set
{
_changed = _changed || _scheme != value;
_scheme = value;
ReqObj = null;
}
}
///
/// Gets the Connection Options
///
public IWsmanConnectionOptions Options => _options;
private ClientRequest ReqObj
{
get => _reqObj;
set
{
_reqObj?.Dispose();
_reqObj = value;
}
}
///
/// Gets the last error that occured when using the connection
///
public IWsmanFault LastError => _lastError;
public DateTime ServerTime => _serverTime;
#endregion
#region Public Methods
private bool _disposed = false;
///
/// Implement IDisposable method
///
///
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);
}
///
/// Resolves a class name prefix to a full resourceUri
///
///
/// The resoved resourceUri
public string ResolveNamespace(string prefix)
{
string result = null;
lock (_nsLock)
{
_nsResolver.TryGetValue(prefix, out result);
}
return result;
}
///
/// Registers a namespaceUri for a class name prefix
///
///
///
public void RegisterNamespace(string prefix, string ns)
{
lock (_nsLock)
{
_nsResolver[prefix] = ns;
}
}
///
/// Closes the connection
///
/// Forces future request to use a new network connection
public void Close()
{
ReqObj?.Dispose();
_dmp?.Dispose();
_client?.Dispose();
_client = null;
}
///
/// Tests the connection to see if the endpoint supports Wsman
///
public IManagedInstance Identify()
{
return (IManagedInstance)SubmitRequest(WsmanResources.IDENTIFY_XML, null, null);
}
///
/// Creates a reference to a object supported by a Wsman Service
///
/// A string describing the object reference
/// A IManagedReference pointing to the object
public IManagedReference NewReference(string resourceUri)
{
ManagedReference wsRef = new ManagedReference(this, resourceUri);
return wsRef;
}
///
/// Creates a new representation of a CIM object that can be exchanged with the server
///
/// A string describing the instance
/// A IManagedInstance representing the resource
public IManagedInstance NewInstance(string resourceUri)
{
ManagedInstance inst = new ManagedInstance(this, resourceUri);
return inst;
}
///
/// Executes a simple SQL type query
///
///
/// An IWsmanEnumeration containing resulting items
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;
}
///
/// Executes a simple SQL type query
///
///
/// An IManagedReference to the object described by the SQL query string
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;
}
///
/// Submits a Wsman command to the server
///
/// The string command to send
/// The reference the Wsman command will use
/// Message Parameters
/// An object representing the results of the command
public object SubmitRequest(string requestString, IManagedReference refObj, IManagedInstance input)
{
XmlDocument reqDoc = new XmlDocument();
reqDoc.LoadXml(requestString);
return SubmitRequest(reqDoc, refObj, input);
}
///
/// Submits a Wsman command to the server
///
/// The XML document to send
/// The reference the Wsman command will use
/// Message Parameters
/// An object representing the results of the command
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; }
}
///
/// Sets the host and port used by the connection
///
/// The host name or IP address of the Wsman service
/// The port the Wsman service is listening on
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();
}
///
/// Sets the connection credentials
///
/// The user name
/// Secure Password object
/// Use this for passing a System.Security.SecureString
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);
}
///
/// Initiate HTTPClient Handler
///
/// HTTPClient Handler
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;
}
///
/// Initiate HTTClient
///
/// Initiated HTTP Client
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");
}
///
/// Build, send HTTPClient and send request
///
///
///
///
///
///
///
///
///
private async Task