2136 lines
59 KiB
C#
2136 lines
59 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Implements the Http Transport for Wsman requests
|
|
/// </summary>
|
|
public class HttpTransport : IDisposable
|
|
{
|
|
|
|
/// <summary>
|
|
/// TcpClient for send TLS Handshaking messages
|
|
/// </summary>
|
|
protected TcpClient _tcpClient;
|
|
|
|
/// <summary>
|
|
/// Authentication handshake to use
|
|
/// </summary>
|
|
protected ISecurityHandshake _authHandshake;
|
|
|
|
/// <summary>
|
|
/// Proxy Authentication handshake to use
|
|
/// </summary>
|
|
protected ISecurityHandshake _proxyHandshake;
|
|
|
|
|
|
/// <summary>
|
|
/// The Uri of the connection
|
|
/// </summary>
|
|
protected Uri _uri;
|
|
|
|
/// <summary>
|
|
/// The Network address type (IPV4/IPV6)
|
|
/// </summary>
|
|
protected AddressFamily _addressType;
|
|
|
|
/// <summary>
|
|
/// the maximum HTTP message/envolope Size
|
|
/// </summary>
|
|
protected int _maxEnvelopeSize = 153600;
|
|
|
|
/// <summary>
|
|
/// HttpProxy Object
|
|
/// </summary>
|
|
protected HttpProxy _proxy;
|
|
|
|
/// <summary>
|
|
/// Http Header trace
|
|
/// </summary>
|
|
protected static System.Diagnostics.TraceSource _trace = new System.Diagnostics.TraceSource("Intel.Management.Wsman.HttpTransport");
|
|
|
|
/// <summary>
|
|
/// Constructs the HTTP for the given URI and address type
|
|
/// </summary>
|
|
/// <param name="addressType"></param>
|
|
/// <param name="address"></param>
|
|
protected HttpTransport(AddressFamily addressType, Uri address)
|
|
{
|
|
_uri = address;
|
|
_addressType = addressType;
|
|
_proxy = null;
|
|
//System.Threading.Thread.
|
|
}
|
|
|
|
public static System.Diagnostics.TraceSource TraceSource
|
|
{
|
|
get { return _trace; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the protocol
|
|
/// </summary>
|
|
public virtual string Name
|
|
{
|
|
get
|
|
{
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the underlying TCP client
|
|
/// </summary>
|
|
public TcpClient TcpClient
|
|
{
|
|
get { return _tcpClient; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets or sets the proxy to go through
|
|
/// </summary>
|
|
public HttpProxy Proxy
|
|
{
|
|
get { return _proxy; }
|
|
set
|
|
{
|
|
_proxy?.Dispose();
|
|
_proxy = value;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The address of the protocol endpoint
|
|
/// </summary>
|
|
public Uri Address
|
|
{
|
|
get { return _uri; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns the request-uri for the http method
|
|
/// </summary>
|
|
public virtual string GetRequestUri()
|
|
{
|
|
return "/";
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The security handshake the protocol will use
|
|
/// </summary>
|
|
public ISecurityHandshake Authorization
|
|
{
|
|
get { return _authHandshake; }
|
|
set { _authHandshake = value; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating if the network socket is keeped open
|
|
/// </summary>
|
|
protected virtual bool KeepAlive
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Closes the network socket
|
|
/// </summary>
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Reads a message from the network socket
|
|
/// </summary>
|
|
protected virtual int ReadMessage(byte[] data, int offset, int count)
|
|
{
|
|
return _tcpClient.GetStream().Read(data, offset, count);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sends the message over the network socket
|
|
/// </summary>
|
|
/// <param name="message">The message to send</param>
|
|
protected virtual void SendMessage(string message)
|
|
{
|
|
byte[] data = Encoding.UTF8.GetBytes(message);
|
|
_tcpClient.GetStream().Write(data, 0, data.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the credentials for the transport
|
|
/// </summary>
|
|
///<param name="cert">A client certificate</param>
|
|
public virtual void SetCredentials(X509Certificate2 cert)
|
|
{
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sets the certificate validation options
|
|
/// </summary>
|
|
///<param name="verifyServerCA">A client certificate</param>
|
|
///<param name="verifyHost">Value indicates if the certifcate CN name must match the http host name</param>
|
|
///<param name="sendRoot">Value indecates if the root certificate should be included in the client chain</param>
|
|
public virtual void SetCertificateValidationOptions(bool verifyServerCA, bool verifyHost, bool sendRoot)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a new request
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the End of a message (two new lines)
|
|
/// </summary>
|
|
/// <param name="data">The data to search</param>
|
|
/// <param name="index">The index to start the search</param>
|
|
/// <param name="len">The length of the data</param>
|
|
/// <returns>-1 if no end to the message, otherwise the index that marks the end</returns>
|
|
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<byte> list = new List<byte>(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
|
|
|
|
|
|
/// <summary>
|
|
/// Implements the Http Transport in plain Text
|
|
/// </summary>
|
|
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
|
|
|
|
|
|
/// <summary>
|
|
/// Implements the Http Transport over TLS
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// The State of a security Handshake
|
|
/// </summary>
|
|
public enum HandshakeState
|
|
{
|
|
Uninitialized,
|
|
CredentialsAquired,
|
|
ResponseNeeded,
|
|
Complete,
|
|
Failed,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interface that represents a security Handshake (e.g. ,Digest,Negotiate)
|
|
/// </summary>
|
|
/// <remarks></remarks>
|
|
public interface ISecurityHandshake
|
|
{
|
|
/// <summary>
|
|
/// Gets the Initial Token of the handshake (starts the handshake)
|
|
/// </summary>
|
|
/// <returns>The first token (if any)</returns>
|
|
string GetInitialToken();
|
|
|
|
/// <summary>
|
|
/// Sets the return token from a handshake
|
|
/// </summary>
|
|
/// <param name="token">A security token returned from a handshake</param>
|
|
void SetReturnToken(string token);
|
|
/// <summary>
|
|
/// Gets the next token of the handshake(until complete)
|
|
/// </summary>
|
|
/// <returns>Next token to (if any)</returns>
|
|
string GetNextToken();
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the number of Handshake attemps the protocol will use
|
|
/// </summary>
|
|
int MaxAttempts { get; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the state of the Handshake process
|
|
/// </summary>
|
|
HandshakeState State { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the name of the Protocol being implemented
|
|
/// </summary>
|
|
String Name { get; }
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Implements HTTP Basic Handshaking Protocol
|
|
/// </summary>
|
|
public class BasicAuthorization : ISecurityHandshake
|
|
{
|
|
string _wwwAuth;
|
|
HandshakeState _state; // the state of handshake
|
|
|
|
/// <summary>
|
|
/// Generates the initial security token(s)
|
|
/// </summary>
|
|
/// <returns>For Basic always returns null</returns>
|
|
public string GetInitialToken()
|
|
{
|
|
_state = HandshakeState.ResponseNeeded;
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the security token returned from a server
|
|
/// </summary>
|
|
public void SetReturnToken(string token)
|
|
{
|
|
_state = HandshakeState.ResponseNeeded;
|
|
// there is no return token for Basic but to keep th loop we return response need
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the initial security token(s)
|
|
/// </summary>
|
|
/// <returns>Returns Basic security token </returns>
|
|
public string GetNextToken()
|
|
{
|
|
|
|
return _wwwAuth;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the maximum number of attemps
|
|
/// </summary>
|
|
public int MaxAttempts
|
|
{
|
|
get { return 2; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the state of the handshanking process
|
|
/// </summary>
|
|
public HandshakeState State
|
|
{
|
|
get { return _state; }
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the Prototol
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get { return "Basic"; }
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements HTTP Digest Handshaking Protocol
|
|
/// </summary>
|
|
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
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a default digest object
|
|
/// </summary>
|
|
/// <param name="user">Digest user</param>
|
|
/// <param name="password">Digest password</param>
|
|
/// <remarks>Url defaults to /wsman</remarks>
|
|
public DigestAuthorization()
|
|
{
|
|
_user = DEFAULT_USER;
|
|
_password = DEFAULT_PASSWORD.ConvertToSecureString();
|
|
_url = DEFAULT_URL;
|
|
_method = DEFAULT_METHOD;
|
|
_state = HandshakeState.CredentialsAquired;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Digest Authorization Handshake object
|
|
/// </summary>
|
|
/// <param name="user">Digest user</param>
|
|
/// <param name="password">Digest password</param>
|
|
/// <remarks>Url defaults to /wsman</remarks>
|
|
public DigestAuthorization(string user, SecureString password)
|
|
{
|
|
_user = user;
|
|
_password = password ?? new SecureString();
|
|
_url = DEFAULT_URL;
|
|
_method = DEFAULT_METHOD;
|
|
_state = HandshakeState.CredentialsAquired;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Digest Authorization Handshake object
|
|
/// </summary>
|
|
/// <param name="user">Digest user</param>
|
|
/// <param name="password">Digest password</param>
|
|
/// <param name="url">Digest Url path</param>
|
|
/// <param name="method">Http Method</param>
|
|
public DigestAuthorization(string user, SecureString password, string url, string method)
|
|
{
|
|
_user = user;
|
|
_password = password ?? new SecureString();
|
|
_url = url;
|
|
_method = method;
|
|
_state = HandshakeState.CredentialsAquired;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a Digest Message Directive
|
|
/// </summary>
|
|
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; }
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Gets or sets the HTTP uri to use in generating digest response
|
|
/// </summary>
|
|
public string Uri
|
|
{
|
|
get { return _url; }
|
|
set { _url = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the initial security token(s)
|
|
/// </summary>
|
|
/// <returns>For Digest always returns null</returns>
|
|
public string GetInitialToken()
|
|
{
|
|
_wwwAuth = null;
|
|
_nc = "00000000";
|
|
_ncnonce = null;
|
|
_a1 = null;
|
|
_a2 = null;
|
|
|
|
GenerateCNonce();
|
|
|
|
_state = HandshakeState.ResponseNeeded;
|
|
return null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sets the token returned from the server (WWW-Authenticate header)
|
|
/// </summary>
|
|
/// <param name="token">WWW-Authenticate header</param>
|
|
public void SetReturnToken(string token)
|
|
{
|
|
|
|
if (!string.IsNullOrEmpty(token) && _state == HandshakeState.ResponseNeeded)
|
|
_state = HandshakeState.ResponseNeeded;
|
|
_wwwAuth = token;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Generates the next token to send
|
|
/// </summary>
|
|
/// <returns>Authorization token</returns>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the state of the handshaking process
|
|
/// </summary>
|
|
public HandshakeState State
|
|
{
|
|
get { return _state; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the name of the Prototol
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get { return "Digest"; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a random Client Nonce using the secure RandomeNumber Generator
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Implements a Anonymous Authorization Handshake
|
|
/// </summary>
|
|
public class AnonymousAuthorization : ISecurityHandshake
|
|
{
|
|
|
|
List<string> _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<string>();
|
|
|
|
_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
|
|
|
|
|
|
/// <summary>
|
|
/// Implements the Windnows Authorization (Negotiate) Handshake
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the Handshake
|
|
/// </summary>
|
|
/// <returns>Initial token (if any)</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the State of the HandShake
|
|
/// </summary>
|
|
public HandshakeState State
|
|
{
|
|
get { return _state; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the SecurityHandShake
|
|
/// </summary>
|
|
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
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// The Response returned from a server
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Manages Proxy Connections
|
|
/// </summary>
|
|
interface IMpsManager: IDisposable
|
|
{
|
|
|
|
/// <summary>
|
|
/// Gets the list of hosts that should be proxied
|
|
/// </summary>
|
|
/// <remarks>This is a configued list and not live from the mps</remarks>
|
|
System.Collections.Specialized.StringCollection Hosts { get; }
|
|
/// <summary>
|
|
/// Get or sets a value indicating whether the MPS manager is enabled
|
|
/// </summary>
|
|
bool Enabled { get; set; }
|
|
/// <summary>
|
|
/// Get value indicating when the proxy manager is active
|
|
/// </summary>
|
|
DateTime ValidUntil { get; set; }
|
|
/// <summary>
|
|
/// Get or sets the hostname and port of the http proxy
|
|
/// </summary>
|
|
string HttpProxy { get; set; }
|
|
/// <summary>
|
|
/// Get or sets the http proxy user (optional)
|
|
/// </summary>
|
|
string HttpUser { get; set; }
|
|
/// <summary>
|
|
/// Get or sets the http proxy password (optional)
|
|
/// </summary>
|
|
SecureString HttpPassword { get; set; }
|
|
/// <summary>
|
|
/// Get or sets the hostname and port of the redirection proxy
|
|
/// </summary>
|
|
string SocksProxy { get; set; }
|
|
/// <summary>
|
|
/// Get or sets the socks proxy password (optional)
|
|
/// </summary>
|
|
string SocksUser { get; set; }
|
|
/// <summary>
|
|
/// Get or sets the socks proxy password (optional)
|
|
/// </summary>
|
|
SecureString SocksPassword { get; set; }
|
|
/// <summary>
|
|
/// Checks to see if the host should be proxied
|
|
/// </summary>
|
|
/// <param name="host">the host name to check</param>
|
|
/// <returns>True if the host should be proxied</returns>
|
|
bool IsConnected(string host);
|
|
/// <summary>
|
|
/// Adds a host to the proxy list
|
|
/// </summary>
|
|
/// <param name="host">the host hame to add</param>
|
|
void AddHost(string host);
|
|
/// <summary>
|
|
/// Removes a host from the proxy list
|
|
/// </summary>
|
|
/// <param name="host">the host name to remove</param>
|
|
void RemoveHost(string host);
|
|
/// <summary>
|
|
/// Saves the settings so they can be used by other applications
|
|
/// </summary>
|
|
void Save();
|
|
|
|
/// <summary>
|
|
/// Reloads MPS Settings
|
|
/// </summary>
|
|
void ReLoad();
|
|
/// <summary>
|
|
/// Gets the list reported by the MPS
|
|
/// </summary>
|
|
/// <remarks>This is a live host list</remarks>
|
|
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
|
|
|
|
/// <summary>
|
|
/// implement IDisposable interface
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Defines connection options for an HTTP proxy
|
|
/// </summary>
|
|
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
|