using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.IO; using System.Net; using System.Net.Sockets; using System.Security; using Common.Utils; namespace Intel.Management.Wsman { /// /// Implements the Http Transport for Wsman requests /// public class HttpTransport : IDisposable { /// /// TcpClient for send TLS Handshaking messages /// protected TcpClient _tcpClient; /// /// Authentication handshake to use /// protected ISecurityHandshake _authHandshake; /// /// Proxy Authentication handshake to use /// protected ISecurityHandshake _proxyHandshake; /// /// The Uri of the connection /// protected Uri _uri; /// /// The Network address type (IPV4/IPV6) /// protected AddressFamily _addressType; /// /// the maximum HTTP message/envolope Size /// protected int _maxEnvelopeSize = 153600; /// /// HttpProxy Object /// protected HttpProxy _proxy; /// /// Http Header trace /// protected static System.Diagnostics.TraceSource _trace = new System.Diagnostics.TraceSource("Intel.Management.Wsman.HttpTransport"); /// /// Constructs the HTTP for the given URI and address type /// /// /// protected HttpTransport(AddressFamily addressType, Uri address) { _uri = address; _addressType = addressType; _proxy = null; //System.Threading.Thread. } public static System.Diagnostics.TraceSource TraceSource { get { return _trace; } } /// /// Gets the name of the protocol /// public virtual string Name { get { return "Unknown"; } } /// /// Gets the underlying TCP client /// public TcpClient TcpClient { get { return _tcpClient; } } /// /// Gets or sets the proxy to go through /// public HttpProxy Proxy { get { return _proxy; } set { _proxy?.Dispose(); _proxy = value; } } /// /// The address of the protocol endpoint /// public Uri Address { get { return _uri; } } /// /// Returns the request-uri for the http method /// public virtual string GetRequestUri() { return "/"; } /// /// The security handshake the protocol will use /// public ISecurityHandshake Authorization { get { return _authHandshake; } set { _authHandshake = value; } } /// /// Gets a flag indicating if the network socket is keeped open /// protected virtual bool KeepAlive { get { return true; } } /// /// Closes the network socket /// public virtual void Close() { if (_tcpClient != null) { _tcpClient.GetStream().Close(); _tcpClient.Close(); _tcpClient = null; } } public virtual string GetProxyAuthorization(string method, string url, ServerResponse response) { if (_proxy != null && _proxyHandshake == null) { _proxyHandshake = _proxy.GetAuthorization(method, url, response.ProxyChallange.ToLower()); if(_proxyHandshake != null) { _proxyHandshake.GetInitialToken(); _proxyHandshake.SetReturnToken(response.ProxyChallange); } } if (_proxyHandshake != null) return _proxyHandshake.GetNextToken(); return null; } /// /// Reads a message from the network socket /// protected virtual int ReadMessage(byte[] data, int offset, int count) { return _tcpClient.GetStream().Read(data, offset, count); } /// /// Sends the message over the network socket /// /// The message to send protected virtual void SendMessage(string message) { byte[] data = Encoding.UTF8.GetBytes(message); _tcpClient.GetStream().Write(data, 0, data.Length); } /// /// Sets the credentials for the transport /// ///A client certificate public virtual void SetCredentials(X509Certificate2 cert) { } /// /// Sets the certificate validation options /// ///A client certificate ///Value indicates if the certifcate CN name must match the http host name ///Value indecates if the root certificate should be included in the client chain public virtual void SetCertificateValidationOptions(bool verifyServerCA, bool verifyHost, bool sendRoot) { } /// /// Initializes a new request /// public virtual void InitializeRequest() { if (_tcpClient == null) { _tcpClient = new TcpClient(_addressType); if (_proxy != null) _tcpClient.Connect(_proxy.Address.Host, _proxy.Address.Port); else _tcpClient.Connect(_uri.Host, _uri.Port); } } public virtual void SendMessageHeaders(string headers) { _trace.TraceInformation(headers); if (_tcpClient == null) { _tcpClient = new TcpClient(_addressType); _tcpClient.Connect(_uri.Host, _uri.Port); } SendMessage(headers); } public virtual void SendMessageBody(string body) { SendMessage(body); } public virtual ServerResponse GetResponse(string method) { MemoryStream inStream; ServerResponse response = null; using (inStream = new MemoryStream()) { StreamReader headerStream = null; //will be all zeros byte[] buffer = new byte[_tcpClient.ReceiveBufferSize]; //read http headers do { int br = ReadMessage(buffer, 0, buffer.Length); if (br == 0) throw new WsmanConnectionException("Server unexpectedly disconnected"); inStream.Write(buffer, 0, br); headerStream = GetHeaders(inStream); } while (headerStream == null); response = ParseHeaders(headerStream); if (response != null) { if (method.Equals("CONNECT") && response.StatusCode >= 200 && response.StatusCode < 300) { //connect proxy 200s are not going to have a body so we are done return response; } //done processing headers so swap buffer for reading body int eoh; try { eoh = GetEndOfMessage(inStream.GetBuffer(), 0, (int)inStream.Length);//Conversion from long to int } catch (System.OverflowException e) { throw new System.OverflowException(e.ToString()); } MemoryStream temp; using (temp = new MemoryStream()) { try { temp.Write(inStream.GetBuffer(), eoh + 1, (int)inStream.Length - eoh - 1);//Conversion from long to int } catch (System.OverflowException e) { throw new System.OverflowException(e.ToString()); } inStream.Close(); headerStream.Close(); inStream = temp; inStream.Seek(0, SeekOrigin.Begin); temp = null; } if (response != null && response.Chunked) { string line; while (true) { line = ReadLine(buffer, inStream); if (line == null) throw new WsmanException("Invalid server response"); int pos = line.IndexOf(';'); if (pos > 0) line = line.Substring(0, pos); int chunkLen = int.Parse(line, System.Globalization.NumberStyles.HexNumber); if (chunkLen == 0) //last chunks break; if ((response.GetResponse().Length + chunkLen) > _maxEnvelopeSize) throw new WsmanConnectionException("Response too big"); byte[] chunkData = new byte[chunkLen]; ReadChunk(chunkData, buffer, inStream); response.GetResponse().Write(chunkData, 0, chunkData.Length); } //read until empty line while (line != null && !line.Equals(string.Empty)) { line = ReadLine(buffer, inStream); } try { String msg = Encoding.UTF8.GetString( response.GetResponse().GetBuffer(), 0, (int)response.GetResponse().Length);//Conversion from long to int } catch (System.OverflowException e) { throw new System.OverflowException(e.ToString()); } } else if (response != null && response.ContentLength >= 0) { if (response.ContentLength > _maxEnvelopeSize) throw new WsmanConnectionException("Response too big"); int total = 0; do { int br = ReadMessage(buffer, 0, buffer.Length); if (br == 0) throw new WsmanConnectionException("Server unexpectedly disconnected"); total += br; response.GetResponse().Write(buffer, 0, br); } while (total < response.ContentLength); } else //read until connection closed { int total = 0; while (true) { int br = ReadMessage(buffer, 0, buffer.Length); if (br == 0) break; total += br; if (total > _maxEnvelopeSize) throw new WsmanConnectionException("Response to big"); response.GetResponse().Write(buffer, 0, br); } } //inStream.Close(); response.GetResponse().Seek(0, SeekOrigin.Begin); if (response.Closed) Close(); else if (!KeepAlive) Close(); } } return response; } protected ServerResponse ParseHeaders(StreamReader reader) { string realm = string.Empty; string server = string.Empty; string contentType = string.Empty; string wwwAuth = string.Empty; string proxyAuth = string.Empty; DateTime date = DateTime.UtcNow; int contentLen = -1; bool chunked = false; bool closed = true; string statusText = string.Empty; int statusCode = 500; string original = reader.ReadLine(); string line = original.ToLower(); while (line != null && !line.Equals(string.Empty)) { _trace.TraceInformation(line); if (line.StartsWith("http/1.1 ") || line.StartsWith("http/1.0")) { string[] status = original.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (status.Length > 1) { statusCode = int.Parse(status[1]); } if (status.Length > 2) { StringBuilder builder = new StringBuilder(); for (int i = 2; i < status.Length; i++) { if (i > 2) builder.Append(' '); builder.Append(status[i]); } statusText = builder.ToString(); } } else if (line.StartsWith("www-authenticate: digest") && _authHandshake.Name.Equals("Digest")) { wwwAuth = original; //_authHandshake.SetReturnToken(original); } else if (line.StartsWith("www-authenticate: negotiate") && _authHandshake.Name.Equals("Negotiate")) { wwwAuth = original; //_authHandshake.SetReturnToken(original); } else if (line.StartsWith("www-authenticate: ") && _authHandshake.Name.Equals("Anonymous")) { wwwAuth = original; //_authHandshake.SetReturnToken(original); } else if (line.StartsWith("connection: close")) { closed = true; } else if (line.StartsWith("connection: keep-alive")) { closed = false; } else if (line.StartsWith("server: ")) { server = line.Substring("server: ".Length); } else if (line.StartsWith("content-length: ")) { contentLen = int.Parse(line.Substring("content-length: ".Length)); } else if (line.StartsWith("transfer-encoding: chunked")) { chunked = true; } else if (line.StartsWith("date")) { date = DateTime.Parse(original.Substring("Date: ".Length)); } else if (line.StartsWith("proxy-authenticate: basic")) { if (string.IsNullOrEmpty(proxyAuth)) proxyAuth = original; } else if (line.StartsWith("proxy-authenticate: digest")) { proxyAuth = original; } else if (line.StartsWith("proxy-authenticate: negotiate")) { proxyAuth = original; } else if (line.StartsWith("proxy-connection: close")) { closed = true; } else if (line.StartsWith("proxy-connection: keep-alive")) { closed = false; } original = reader.ReadLine(); line = original.ToLower(); } //while headers return new ServerResponse(new MemoryStream(), contentType, contentLen, date, wwwAuth, proxyAuth, chunked, closed, realm, server, statusText, statusCode); } /// /// Gets the End of a message (two new lines) /// /// The data to search /// The index to start the search /// The length of the data /// -1 if no end to the message, otherwise the index that marks the end protected int GetEndOfMessage(byte[] data, int index, int len) { int eol = -1; while (index >= 0 && index < len) { eol = index = Array.IndexOf(data, (byte)'\n', index, len - index); if (index > 0 && index < len) index = Array.IndexOf(data, (byte)'\n', index + 1, len - index - 1); if (index == (eol + 2)) break; eol = -1; } if (eol >= 0) return index; return -1; } protected StreamReader GetHeaders(MemoryStream inStream) { byte[] data = inStream.GetBuffer(); int len; try { len = (int)inStream.Length;//Conversion from long to int } catch (System.OverflowException e) { throw new System.OverflowException(e.ToString()); } int index = GetEndOfMessage(data, 0, len); if (index > 0) { inStream.Seek(0, SeekOrigin.Begin); return new StreamReader(inStream, Encoding.UTF8); } return null;//no headers found yet } protected byte ReadByte(byte[] buffer, MemoryStream instream) { int nible = instream.ReadByte(); if (nible < 0) { int br = ReadMessage(buffer, 0, buffer.Length); instream.SetLength(0); instream.Write(buffer, 0, br); instream.Seek(0, SeekOrigin.Begin); nible = instream.ReadByte(); } return (byte)nible; } protected string ReadLine(byte[] buffer, MemoryStream instream) { List list = new List(8); string result = null; while (true) { byte nible = ReadByte(buffer, instream); if (nible == (byte)'\n') break; if (result != null) throw new WsmanException("Invalid server response"); if (nible != (byte)'\r' && nible != (byte)'\n') list.Add((byte)nible); else result = Encoding.ASCII.GetString(list.ToArray()); if (nible == (byte)'\n') break; }//while nibling return result; } protected void ReadChunk(byte[] chunkData, byte[] buffer, MemoryStream instream) { for (int i = 0; i < chunkData.Length; i++) chunkData[i] = ReadByte(buffer, instream); byte nible = ReadByte(buffer, instream); if (nible == (byte)'\r') nible = ReadByte(buffer, instream); if (nible != (byte)'\n') throw new WsmanException("Invalid response from server"); } #region IDisposable Members private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { Close(); _proxy?.Dispose(); } _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion }//End of HttpTransport /// /// Implements the Http Transport in plain Text /// public class PlainTextTransport : HttpTransport { public PlainTextTransport(AddressFamily family, Uri url) : base(family, url) { } public override string GetRequestUri() { if (_proxy != null) return _uri.ToString(); return _uri.LocalPath; } public override string Name { get { return "PlainText"; } } protected override void SendMessage(string message) { byte[] data = Encoding.UTF8.GetBytes(message); _tcpClient.GetStream().Write(data, 0, data.Length); } public void SendHeaders(string headers) { SendMessage(headers); } public void SendBody(string body) { SendMessage(body); } public StreamReader GetResponseReader() { return new StreamReader(_tcpClient.GetStream(), Encoding.UTF8); } }// end of PlainText reader /// /// Implements the Http Transport over TLS /// public class TlsTransport : PlainTextTransport, IDisposable { SchannelClient _schClient; X509Certificate2 _clientCert; bool _verifyCa; bool _verifyCn; bool _sendRoot; bool _clearChannel; public TlsTransport(AddressFamily family, Uri url) : base(family, url) { _verifyCa = true; _verifyCn = true; _sendRoot = false; _clearChannel = true; } private void Connect() { ISecurityHandshake proxy = null; string proxyAuth = null; int retry = 1; while (retry > 0) { using (StringWriter writer = new StringWriter()) { writer.Write("CONNECT "); writer.Write(_uri.Authority); writer.Write(" HTTP/1.1"); writer.WriteLine(); if (proxyAuth != null) { writer.Write("Proxy-Authorization: "); writer.Write(proxyAuth); writer.WriteLine(); } writer.Write("Host: "); writer.Write(_uri.Host); writer.WriteLine(); writer.WriteLine(); SendMessageHeaders(writer.ToString()); } ServerResponse response = GetResponse("CONNECT"); // we had a successful connect if (response.StatusCode >= 200 && response.StatusCode < 300) break; //assume we got a 407 if (response.StatusCode != 407) { string text = "Proxy Error "; if (!string.IsNullOrEmpty(response.StatusText)) text = response.StatusText; throw new WsmanConnectionException(response.StatusText + "(" + text + ")"); } proxyAuth = GetProxyAuthorization("CONNECT", "/", response); if (proxy == null) { proxy = _proxyHandshake; retry = _proxyHandshake.MaxAttempts; } else { proxy.SetReturnToken(response.ProxyChallange); } if (proxy.State != HandshakeState.ResponseNeeded) retry = 0; if (response.Closed) { _clearChannel = true; base.Close(); } base.InitializeRequest(); retry--; } base.InitializeRequest(); } public override string GetRequestUri() { return _uri.LocalPath; } protected override bool KeepAlive { get { return true; } } public override void Close() { _clearChannel = true; _proxyHandshake = null; if (_schClient != null && _tcpClient != null) _schClient.CloseNotify(); base.Close(); } public override string Name { get { return "TLS"; } } public override void SetCredentials(X509Certificate2 cert) { _clientCert = cert; } public X509Certificate2 GetCredentials() { return _clientCert; } public override void InitializeRequest() { base.InitializeRequest(); //send proxy connect if needed if (_proxy != null && _clearChannel == true) { Connect(); } //look for a cached channel if (_schClient == null) { _schClient = new SchannelClient(SchannelClient.SchannelPackageName); _schClient.Target = _uri.Host; _schClient.AcquireCredentials(_clientCert, _verifyCa, _verifyCn, _sendRoot); } _schClient.PerformHandshake(_tcpClient); _clearChannel = false; } public override void SetCertificateValidationOptions(bool verifyServerCA, bool verifyHost, bool sendRoot) { _verifyCa = verifyServerCA; _verifyCn = verifyHost; _sendRoot = sendRoot; } protected override void SendMessage(string message) { if (_clearChannel) base.SendMessage(message); else _schClient.WriteMessage(message); } protected override int ReadMessage(byte[] data, int offset, int count) { if (_clearChannel) return base.ReadMessage(data, offset, count); int len = _schClient.ReadMessage(data, offset, count); return len; } #region IDisposable Members private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _schClient?.Dispose(); } _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion }// end of TlsTransport /// /// The State of a security Handshake /// public enum HandshakeState { Uninitialized, CredentialsAquired, ResponseNeeded, Complete, Failed, } /// /// Interface that represents a security Handshake (e.g. ,Digest,Negotiate) /// /// public interface ISecurityHandshake { /// /// Gets the Initial Token of the handshake (starts the handshake) /// /// The first token (if any) string GetInitialToken(); /// /// Sets the return token from a handshake /// /// A security token returned from a handshake void SetReturnToken(string token); /// /// Gets the next token of the handshake(until complete) /// /// Next token to (if any) string GetNextToken(); /// /// Gets the number of Handshake attemps the protocol will use /// int MaxAttempts { get; } /// /// Gets the state of the Handshake process /// HandshakeState State { get; } /// /// Gets the name of the Protocol being implemented /// String Name { get; } } /// /// Implements HTTP Basic Handshaking Protocol /// public class BasicAuthorization : ISecurityHandshake { string _wwwAuth; HandshakeState _state; // the state of handshake /// /// Generates the initial security token(s) /// /// For Basic always returns null public string GetInitialToken() { _state = HandshakeState.ResponseNeeded; return null; } /// /// Sets the security token returned from a server /// public void SetReturnToken(string token) { _state = HandshakeState.ResponseNeeded; // there is no return token for Basic but to keep th loop we return response need } /// /// Generates the initial security token(s) /// /// Returns Basic security token public string GetNextToken() { return _wwwAuth; } /// /// Gets the maximum number of attemps /// public int MaxAttempts { get { return 2; } } /// /// Gets the state of the handshanking process /// public HandshakeState State { get { return _state; } } /// /// Gets the name of the Prototol /// public string Name { get { return "Basic"; } } } /// /// Implements HTTP Digest Handshaking Protocol /// public class DigestAuthorization : ISecurityHandshake,IDisposable { #region Consts private const string DEFAULT_USER = "admin"; private const string DEFAULT_PASSWORD = "admin"; private const string DEFAULT_URL = "/wsman"; private const string DEFAULT_METHOD = "POST"; #endregion #region Private Fields string _user; SecureString _password; string _url; string _wwwAuth; //Digest Challenge (returned token) string _nc;//nonce-count string _ncnonce;//unique console token string _a1;//MD5 handes A1 string _a2;//MD5 hashed A2 string _method; HandshakeState _state; // the state of handshake #endregion /// /// Creates a default digest object /// /// Digest user /// Digest password /// Url defaults to /wsman public DigestAuthorization() { _user = DEFAULT_USER; _password = DEFAULT_PASSWORD.ConvertToSecureString(); _url = DEFAULT_URL; _method = DEFAULT_METHOD; _state = HandshakeState.CredentialsAquired; } /// /// Creates a Digest Authorization Handshake object /// /// Digest user /// Digest password /// Url defaults to /wsman public DigestAuthorization(string user, SecureString password) { _user = user; _password = password ?? new SecureString(); _url = DEFAULT_URL; _method = DEFAULT_METHOD; _state = HandshakeState.CredentialsAquired; } /// /// Creates a Digest Authorization Handshake object /// /// Digest user /// Digest password /// Digest Url path /// Http Method public DigestAuthorization(string user, SecureString password, string url, string method) { _user = user; _password = password ?? new SecureString(); _url = url; _method = method; _state = HandshakeState.CredentialsAquired; } /// /// Gets a Digest Message Directive /// public string GetDirective(string message, string name) { int end = 0; int pos = message.IndexOf(name); string result = null; if (pos > 0) { pos = pos + name.Length; if (message.Substring(pos, 1).Equals("\"")) { pos += 1; end = message.IndexOf('\"', pos); } else { end = message.IndexOf(',', pos); } result = message.Substring(pos, end - pos); } return result; } public bool Stale { get { if (_wwwAuth != null) { const string STALE_TOKEN = "stale="; //optional string staleValue = GetDirective(_wwwAuth, STALE_TOKEN); if (staleValue != null && string.Compare(staleValue, "true") == 0) { return true; } } return false; } } public int MaxAttempts { get { return 2; } } /// /// Gets or sets the HTTP uri to use in generating digest response /// public string Uri { get { return _url; } set { _url = value; } } /// /// Generates the initial security token(s) /// /// For Digest always returns null public string GetInitialToken() { _wwwAuth = null; _nc = "00000000"; _ncnonce = null; _a1 = null; _a2 = null; GenerateCNonce(); _state = HandshakeState.ResponseNeeded; return null; } /// /// Sets the token returned from the server (WWW-Authenticate header) /// /// WWW-Authenticate header public void SetReturnToken(string token) { if (!string.IsNullOrEmpty(token) && _state == HandshakeState.ResponseNeeded) _state = HandshakeState.ResponseNeeded; _wwwAuth = token; } /// /// Generates the next token to send /// /// Authorization token public string GetNextToken() { if (string.IsNullOrEmpty(_wwwAuth)) { _state = HandshakeState.Failed; return null; } using (MD5 md5 = new MD5CryptoServiceProvider()) { int nc = int.Parse(_nc); nc++; _nc = nc.ToString("D8"); // server directives const string REALM_TOKEN = "realm="; const string NONCE_TOKEN = "nonce="; const string STALE_TOKEN = "stale="; //optional const string QOP_TOKEN = "qop="; //quality of protection "auth" const string OPAQUE_TOKEN = "opaque="; //optional string realmValue = GetDirective(_wwwAuth, REALM_TOKEN); string nonceValue = GetDirective(_wwwAuth, NONCE_TOKEN); string staleValue = GetDirective(_wwwAuth, STALE_TOKEN); string qopValue = GetDirective(_wwwAuth, QOP_TOKEN); string opaqueValue = GetDirective(_wwwAuth, OPAQUE_TOKEN); StringBuilder builder = null; byte[] hash = null; //calculate A1 if (_a1 == null) { builder = new StringBuilder(); builder.Append(_user); builder.Append(':'); builder.Append(realmValue); builder.Append(':'); builder.Append(_password.ConvertToString()); hash = md5.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString())); _a1 = BitConverter.ToString(hash); _a1 = _a1.Replace("-", string.Empty).ToLower(); } //calucate A2 if (_a2 == null) { builder = new StringBuilder(); builder.Append(_method); builder.Append(':'); builder.Append(_url); hash = md5.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString())); _a2 = BitConverter.ToString(hash); _a2 = _a2.Replace("-", string.Empty).ToLower(); } builder = new StringBuilder(); builder.Append(_a1); builder.Append(':'); builder.Append(nonceValue); builder.Append(':'); builder.Append(_nc); builder.Append(':'); builder.Append(_ncnonce); builder.Append(':'); builder.Append(qopValue); builder.Append(':'); builder.Append(_a2); hash = md5.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString())); string response = BitConverter.ToString(hash); response = response.Replace("-", string.Empty).ToLower(); builder = new StringBuilder(); builder.Append("Digest "); builder.Append("username=\""); builder.Append(_user); builder.Append("\","); builder.Append(REALM_TOKEN); builder.Append("\""); builder.Append(realmValue); builder.Append("\","); builder.Append(NONCE_TOKEN); builder.Append("\""); builder.Append(nonceValue); builder.Append("\","); builder.Append("uri=\""); builder.Append(_url); builder.Append("\","); builder.Append("algorithm=\"MD5\","); builder.Append("cnonce=\""); builder.Append(_ncnonce); builder.Append("\","); builder.Append("nc="); builder.Append(_nc); builder.Append(","); builder.Append(QOP_TOKEN); builder.Append("\""); builder.Append(qopValue); builder.Append("\","); builder.Append("response=\""); builder.Append(response); builder.Append("\""); if (opaqueValue != null) { builder.Append(","); builder.Append(OPAQUE_TOKEN); builder.Append("\""); builder.Append(opaqueValue); builder.Append("\""); } _state = HandshakeState.ResponseNeeded; return builder.ToString(); } } /// /// Gets the state of the handshaking process /// public HandshakeState State { get { return _state; } } /// /// Gets the name of the Prototol /// public string Name { get { return "Digest"; } } /// /// Generates a random Client Nonce using the secure RandomeNumber Generator /// private void GenerateCNonce() { if (_ncnonce == null) { byte[] rndData = new byte[16]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) { rng.GetBytes(rndData); _ncnonce = BitConverter.ToString(rndData); _ncnonce = _ncnonce.Replace("-", string.Empty).ToLower(); } } } #region IDisposableMembers private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _password?.Dispose(); } _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~DigestAuthorization() { Dispose(false); } #endregion }// end DigestAuthorization /// /// Implements a Anonymous Authorization Handshake /// public class AnonymousAuthorization : ISecurityHandshake { List _tokenList; public AnonymousAuthorization() { } public override int GetHashCode() { return Name.GetHashCode(); } public string GetInitialToken() { return string.Empty; } public void SetReturnToken(string token) { if (_tokenList == null) _tokenList = new List(); _tokenList.Add(token); } public string GetNextToken() { return string.Empty; } public HandshakeState State { get { return HandshakeState.Complete; } } public int MaxAttempts { get { return 1; } } public string Name { get { return "Anonymous"; } } public string[] GetAuthorizations() { if (_tokenList == null) return new string[0]; return _tokenList.ToArray(); } }// end AnonymousAuthorziation /// /// Implements the Windnows Authorization (Negotiate) Handshake /// public class WindowsAuthorization : ISecurityHandshake, IDisposable { SchannelClient _schClient; HandshakeState _state; string _token; public WindowsAuthorization(string user, string domain, SecureString password, string spn) { _state = HandshakeState.Uninitialized; _schClient = new SchannelClient("Negotiate"); _schClient.Target = spn; int status = _schClient.AcquireCredentials(user, domain, password); if (status == 0) _state = HandshakeState.CredentialsAquired; } public override int GetHashCode() { return base.GetHashCode(); } /// /// Initializes the Handshake /// /// Initial token (if any) public string GetInitialToken() { _state = HandshakeState.ResponseNeeded; return _schClient.GetToken(_token); } public int MaxAttempts { get { return int.MaxValue; } } public void SetReturnToken(string token) { //if the previous token was empty and the new token is empty then Unauthorized if (_state == HandshakeState.ResponseNeeded && string.Compare(token, "WWW-Authenticate: Negotiate", true) == 0) _state = HandshakeState.Failed; if (_state != HandshakeState.Failed && !string.IsNullOrEmpty(token)) _state = HandshakeState.ResponseNeeded; else _state = HandshakeState.Failed; _token = token; } public string GetNextToken() { return _schClient.GetToken(_token); } /// /// Gets the State of the HandShake /// public HandshakeState State { get { return _state; } } /// /// Gets the name of the SecurityHandShake /// public string Name { get { return "Negotiate"; } } #region IDisposable Members private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _schClient?.Dispose(); } _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion }// end WindowsAuthorziation /// /// The Response returned from a server /// public class ServerResponse { MemoryStream _stream; string _contentType; int _contentLength; DateTime _date; string _wwwAuth; string _proxyAuth; string _realm; string _server; string _statusText; int _statusCode; bool _chunked; bool _closed; public ServerResponse(MemoryStream stream, string contentType, int contentLen, DateTime date, string wwwAuth, string proxyAuth, bool chunked, bool closed, string realm, string server, string statusText, int statusCode) { _stream = stream; _contentType = contentType; _contentLength = contentLen; _date = date; _wwwAuth = wwwAuth; _proxyAuth = proxyAuth; _chunked = chunked; _closed = closed; _realm = realm; _server = server; _statusText = statusText; _statusCode = statusCode; } public MemoryStream GetResponse() { return _stream; } public DateTime Date { get { return _date; } } public string Challange { get { return _wwwAuth; } } public string ProxyChallange { get { return _proxyAuth; } } public int StatusCode { get { return _statusCode; } } public string StatusText { get { return _statusText; } } public string ContentType { get { return _contentType; } } public string Server { get { return _server; } } public bool Chunked { get { return _chunked; } } public int ContentLength { get { return _contentLength; } } public bool Closed { get { return _closed; } } public System.Xml.XmlDocument GetXml() { System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.Load(_stream); _stream.Seek(0, SeekOrigin.Begin); return doc; } public void Close() { _stream.Close(); } }// End ServerResponse /// /// Manages Proxy Connections /// interface IMpsManager: IDisposable { /// /// Gets the list of hosts that should be proxied /// /// This is a configued list and not live from the mps System.Collections.Specialized.StringCollection Hosts { get; } /// /// Get or sets a value indicating whether the MPS manager is enabled /// bool Enabled { get; set; } /// /// Get value indicating when the proxy manager is active /// DateTime ValidUntil { get; set; } /// /// Get or sets the hostname and port of the http proxy /// string HttpProxy { get; set; } /// /// Get or sets the http proxy user (optional) /// string HttpUser { get; set; } /// /// Get or sets the http proxy password (optional) /// SecureString HttpPassword { get; set; } /// /// Get or sets the hostname and port of the redirection proxy /// string SocksProxy { get; set; } /// /// Get or sets the socks proxy password (optional) /// string SocksUser { get; set; } /// /// Get or sets the socks proxy password (optional) /// SecureString SocksPassword { get; set; } /// /// Checks to see if the host should be proxied /// /// the host name to check /// True if the host should be proxied bool IsConnected(string host); /// /// Adds a host to the proxy list /// /// the host hame to add void AddHost(string host); /// /// Removes a host from the proxy list /// /// the host name to remove void RemoveHost(string host); /// /// Saves the settings so they can be used by other applications /// void Save(); /// /// Reloads MPS Settings /// void ReLoad(); /// /// Gets the list reported by the MPS /// /// This is a live host list System.Collections.Specialized.StringCollection ReportedHosts { get; } } public class MpsManager : IMpsManager { public MpsManager() { } public System.Collections.Specialized.StringCollection Hosts { get { if (Properties.Settings.Default.Hosts == null) { return new System.Collections.Specialized.StringCollection(); } return Properties.Settings.Default.Hosts; } } public bool Enabled { get { return Properties.Settings.Default.AutoProxyEnabled; } set { Properties.Settings.Default.AutoProxyEnabled = value; } } public DateTime ValidUntil { get { return Properties.Settings.Default.ValidUntil; } set { Properties.Settings.Default.ValidUntil = value; } } public string HttpProxy { get { return Properties.Settings.Default.HttpProxyUri; } set { Properties.Settings.Default.HttpProxyUri = value; } } public string SocksProxy { get { return Properties.Settings.Default.SocksUri; } set { Properties.Settings.Default.SocksUri = value; } } public string HttpUser { get { return Properties.Settings.Default.HttpProxyUser; } set { Properties.Settings.Default.HttpProxyUser = value; } } public string SocksUser { get { return Properties.Settings.Default.SocksUser; } set { Properties.Settings.Default.SocksUser = value; } } public SecureString HttpPassword { get { return Properties.Settings.Default.HttpProxyPassword; } set { Properties.Settings.Default.HttpProxyPassword = value; } } public SecureString SocksPassword { get { return Properties.Settings.Default.SocksPassword; } set { Properties.Settings.Default.SocksPassword = value; } } public bool IsConnected(string host) { if (Properties.Settings.Default.AutoProxyEnabled) { foreach (string entry in Properties.Settings.Default.Hosts) { if (string.Compare(entry, host, true) == 0) { return DateTime.Now.CompareTo(Properties.Settings.Default.ValidUntil) <= 0; } } } return false; } public void AddHost(string host) { bool bExists = false; System.Collections.Specialized.StringCollection hosts = Properties.Settings.Default.Hosts; if (hosts == null) hosts = new System.Collections.Specialized.StringCollection(); if (DateTime.Now.CompareTo(Properties.Settings.Default.ValidUntil) > 0) hosts.Clear(); foreach (string entry in hosts) { if (string.Compare(entry, host, true) == 0) { bExists = true; break; } } // the host was not already found in the list if (!bExists) hosts.Add(host); DateTime setValue = DateTime.Now.AddHours(8); if (setValue.CompareTo(Properties.Settings.Default.ValidUntil) > 0) Properties.Settings.Default.ValidUntil = setValue; Properties.Settings.Default.Hosts = hosts; } public void RemoveHost(string host) { if (Properties.Settings.Default.Hosts == null) Properties.Settings.Default.Hosts = new System.Collections.Specialized.StringCollection(); foreach (string entry in Properties.Settings.Default.Hosts) { if (string.Compare(entry, host, true) == 0) { Properties.Settings.Default.Hosts.Remove(entry); break; } } } public string DiscoveryAddress { get { return Properties.Settings.Default.DiscoveryUri; } set { Properties.Settings.Default.DiscoveryUri = value; } } public void Save() { Properties.Settings.Default.Save(); } public void ReLoad() { Properties.Settings.Default.Reload(); } public System.Collections.Specialized.StringCollection ReportedHosts { get { throw new NotImplementedException(); } } #region IDisposableMembers /// /// implement IDisposable interface /// private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { HttpPassword?.Dispose(); SocksPassword?.Dispose(); } _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~MpsManager() { Dispose(false); } #endregion }//end classs /// /// Defines connection options for an HTTP proxy /// public class HttpProxy : IDisposable { Uri _uri; string _proxyUser; SecureString _proxyPassword; public HttpProxy() { } public HttpProxy(string address) { if (address.ToLower().StartsWith("http://") == false) address = "http://" + address; _uri = new Uri(address); _proxyUser = string.Empty; _proxyPassword = new SecureString(); } public HttpProxy(string address, string username, SecureString password) { if (address.ToLower().StartsWith("http://") == false) address = "http://" + address; _uri = new Uri(address); _proxyUser = username; _proxyPassword = password ?? new SecureString(); } public ISecurityHandshake GetAuthorization(string method, string url, string challenge) { ISecurityHandshake result = null; if (challenge.StartsWith("proxy-authenticate: digest")) { DigestAuthorization auth = new DigestAuthorization(_proxyUser, _proxyPassword.Copy(), url, method); result = auth; } else if (challenge.StartsWith("proxy-authenticate: negotiate")) { //DigestAuthorization auth = new WindowsAuthorization(_proxyUser, _proxyPassword); //result = auth; } return result; } public Uri Address { get { return _uri; } } public string Username { get { return _proxyUser; } } public SecureString Password { get { return _proxyPassword; } } #region IDisposableMembers private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _proxyPassword?.Dispose(); } _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~HttpProxy() { Dispose(false); } #endregion } }//end namespace