Commit 7b3b1f96 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #249 from justcoding121/develop

Beta for Win Auth
parents 2a7c1f91 4aad40e9
......@@ -13,7 +13,10 @@ namespace Titanium.Web.Proxy.Examples.Basic
private readonly ProxyServer proxyServer;
//share requestBody outside handlers
private readonly Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>();
//Using a dictionary is not a good idea since it can cause memory overflow
//ideally the data should be moved out of memory
//private readonly Dictionary<Guid, string> requestBodyHistory
// = new Dictionary<Guid, string>();
public ProxyTestController()
{
......@@ -124,7 +127,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
requestBodyHistory[e.Id] = bodyString;
//requestBodyHistory[e.Id] = bodyString;
}
////To cancel a request with a custom HTML content
......@@ -152,11 +155,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
if (requestBodyHistory.ContainsKey(e.Id))
{
//access request body by looking up the shared dictionary using requestId
var requestBody = requestBodyHistory[e.Id];
}
//if (requestBodyHistory.ContainsKey(e.Id))
//{
// //access request body by looking up the shared dictionary using requestId
// var requestBody = requestBodyHistory[e.Id];
//}
//read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders;
......
......@@ -18,6 +18,7 @@ Features
* Support mutual SSL authentication
* Fully asynchronous proxy
* Supports proxy authentication & automatic proxy detection
* Kerberos/NTLM authentication over HTTP protocols for windows domain
Usage
=====
......@@ -203,7 +204,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
```
Future road map (Pull requests are welcome!)
============
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0
* Support SOCKS protocol
......
......@@ -33,6 +33,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
......@@ -59,6 +60,7 @@
<Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyServerTests.cs" />
<Compile Include="WinAuthTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
......
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Titanium.Web.Proxy.Network.WinAuth;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class WinAuthTests
{
[TestMethod]
public void Test_Acquire_Client_Token()
{
var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid());
Assert.IsTrue(token.Length > 1);
}
}
}
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -14,17 +15,23 @@ namespace Titanium.Web.Proxy.Decompression
using (var stream = new MemoryStream(compressedArray))
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = new byte[bufferSize];
using (var output = new MemoryStream())
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
using (var output = new MemoryStream())
{
output.Write(buffer, 0, read);
}
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray();
return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
}
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -13,16 +14,23 @@ namespace Titanium.Web.Proxy.Decompression
{
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{
var buffer = new byte[bufferSize];
using (var output = new MemoryStream())
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
using (var output = new MemoryStream())
{
output.Write(buffer, 0, read);
}
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray();
return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
}
......
......@@ -32,6 +32,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
private Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool reRequest;
/// <summary>
/// Holds a reference to client
/// </summary>
......@@ -43,10 +48,24 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public Guid Id => WebSession.RequestId;
/// <summary>
/// Should we send a rerequest
/// Should we send the request again
/// </summary>
public bool ReRequest { get; set; }
public bool ReRequest
{
get { return reRequest; }
set
{
if (WebSession.Response.ResponseStatusCode == null)
{
throw new Exception("Response status code is null. Cannot request again a request "
+ "which was never send to server.");
}
reRequest = value;
}
}
/// <summary>
/// Does this session uses SSL
......@@ -117,12 +136,12 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
await ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream,
WebSession.Request.ContentLength);
}
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue);
}
}
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
......@@ -135,6 +154,18 @@ namespace Titanium.Web.Proxy.EventArguments
}
}
/// <summary>
/// reinit response object
/// </summary>
internal async Task ClearResponse()
{
//siphon out the body
await ReadResponseBody();
WebSession.Response.Dispose();
WebSession.Response = new Response();
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
......@@ -155,12 +186,12 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Response.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream,
WebSession.Response.ContentLength);
}
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || WebSession.Response.ContentLength == -1)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue);
}
}
......@@ -420,27 +451,29 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.CancelRequest = true;
}
/// <summary>
/// Before request is made to server 
/// <summary>
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and ignore the request 
/// </summary>
/// <param name="html"></param>
/// <param name="status"></param>
/// </summary>
/// <param name="html"></param>
/// <param name="status"></param>
/// <returns></returns>
public async Task GenericResponse(string html, HttpStatusCode status)
{
await GenericResponse(html, null, status);
}
/// <summary>
/// Before request is made to server 
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and the specified status
/// and ignore the request 
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
/// <param name="status"></param>
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
/// <param name="status"></param>
/// <returns></returns>
public async Task GenericResponse(string html, Dictionary<string, HttpHeader> headers, HttpStatusCode status)
{
if (WebSession.Request.RequestLocked)
......
using System;
using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -17,33 +17,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <returns></returns>
internal static Encoding GetEncoding(this Request request)
{
try
{
//return default if not specified
if (request.ContentType == null)
{
return Encoding.GetEncoding("ISO-8859-1");
}
//extract the encoding by finding the charset
var contentTypes = request.ContentType.Split(ProxyConstants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
}
}
catch
{
//parsing errors
// ignored
}
//return default if not specified
return Encoding.GetEncoding("ISO-8859-1");
return HttpHelper.GetEncodingFromContentType(request.ContentType);
}
}
}
using System;
using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -14,33 +14,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <returns></returns>
internal static Encoding GetResponseCharacterEncoding(this Response response)
{
try
{
//return default if not specified
if (response.ContentType == null)
{
return Encoding.GetEncoding("ISO-8859-1");
}
//extract the encoding by finding the charset
var contentTypes = response.ContentType.Split(ProxyConstants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
}
}
catch
{
//parsing errors
// ignored
}
//return default if not specified
return Encoding.GetEncoding("ISO-8859-1");
return HttpHelper.GetEncodingFromContentType(response.ContentType);
}
}
}
......@@ -34,34 +34,31 @@ namespace Titanium.Web.Proxy.Extensions
/// copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="streamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead)
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
{
var totalbytesRead = 0;
long bytesToRead = totalBytesToRead < bufferSize ? totalBytesToRead : bufferSize;
byte[] buffer = streamReader.Buffer;
long remainingBytes = totalBytesToRead;
while (totalbytesRead < totalBytesToRead)
while (remainingBytes > 0)
{
var buffer = await streamReader.ReadBytesAsync(bytesToRead);
if (buffer.Length == 0)
int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
{
break;
bytesToRead = (int)remainingBytes;
}
totalbytesRead += buffer.Length;
var remainingBytes = totalBytesToRead - totalbytesRead;
if (remainingBytes < bytesToRead)
int bytesRead = await streamReader.ReadBytesAsync(buffer, bytesToRead);
if (bytesRead == 0)
{
bytesToRead = remainingBytes;
break;
}
await stream.WriteAsync(buffer, 0, buffer.Length);
remainingBytes -= bytesRead;
await stream.WriteAsync(buffer, 0, bytesRead);
}
}
......@@ -80,8 +77,8 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0)
{
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length);
await CopyBytesToStream(clientStreamReader, stream, chunkSize);
//chunk trail
await clientStreamReader.ReadLineAsync();
}
......@@ -132,30 +129,7 @@ namespace Titanium.Web.Proxy.Extensions
contentLength = long.MaxValue;
}
int bytesToRead = bufferSize;
if (contentLength < bufferSize)
{
bytesToRead = (int)contentLength;
}
var buffer = new byte[bufferSize];
var bytesRead = 0;
var totalBytesRead = 0;
while ((bytesRead += await inStreamReader.BaseStream.ReadAsync(buffer, 0, bytesToRead)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == contentLength)
break;
bytesRead = 0;
var remainingBytes = contentLength - totalBytesRead;
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
}
await CopyBytesToStream(inStreamReader, outStream, contentLength);
}
else
{
......@@ -178,14 +152,11 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0)
{
var buffer = await inStreamReader.ReadBytesAsync(chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await outStream.WriteAsync(buffer, 0, chunkSize);
await CopyBytesToStream(inStreamReader, outStream, chunkSize);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.ReadLineAsync();
......
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
internal static class BufferPool
{
private static readonly ConcurrentQueue<byte[]> buffers = new ConcurrentQueue<byte[]>();
internal static byte[] GetBuffer(int bufferSize)
{
byte[] buffer;
if (!buffers.TryDequeue(out buffer) || buffer.Length != bufferSize)
{
buffer = new byte[bufferSize];
}
return buffer;
}
internal static void ReturnBuffer(byte[] buffer)
{
if (buffer != null)
{
buffers.Enqueue(buffer);
}
}
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
......@@ -16,30 +14,22 @@ namespace Titanium.Web.Proxy.Helpers
{
private readonly CustomBufferedStream stream;
private readonly int bufferSize;
private readonly byte[] staticBuffer;
private readonly Encoding encoding;
private static readonly ConcurrentQueue<byte[]> buffers
= new ConcurrentQueue<byte[]>();
private volatile bool disposed;
internal byte[] Buffer { get; }
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
if (!buffers.TryDequeue(out staticBuffer) || staticBuffer.Length != bufferSize)
{
staticBuffer = new byte[bufferSize];
}
Buffer = BufferPool.GetBuffer(bufferSize);
this.bufferSize = bufferSize;
//default to UTF-8
encoding = Encoding.UTF8;
}
internal Stream BaseStream => stream;
/// <summary>
/// Read a line from the byte stream
/// </summary>
......@@ -51,7 +41,7 @@ namespace Titanium.Web.Proxy.Helpers
int bufferDataLength = 0;
// try to use the thread static buffer, usually it is enough
var buffer = staticBuffer;
var buffer = Buffer;
while (stream.DataAvailable || await stream.FillBufferAsync())
{
......@@ -63,6 +53,7 @@ namespace Titanium.Web.Proxy.Helpers
{
return encoding.GetString(buffer, 0, bufferDataLength - 1);
}
//end of stream
if (newChar == '\0')
{
......@@ -80,6 +71,11 @@ namespace Titanium.Web.Proxy.Helpers
}
}
if (bufferDataLength == 0)
{
return null;
}
return encoding.GetString(buffer, 0, bufferDataLength);
}
......@@ -95,6 +91,7 @@ namespace Titanium.Web.Proxy.Helpers
{
requestLines.Add(tmpLine);
}
return requestLines;
}
......@@ -110,49 +107,14 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Read the specified number of raw bytes from the base stream
/// Read the specified number (or less) of raw bytes from the base stream to the given buffer
/// </summary>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead)
/// <param name="buffer"></param>
/// <param name="bytesToRead"></param>
/// <returns>The number of bytes read</returns>
internal Task<int> ReadBytesAsync(byte[] buffer, int bytesToRead)
{
int bytesToRead = bufferSize;
var buffer = staticBuffer;
if (totalBytesToRead < bufferSize)
{
bytesToRead = (int)totalBytesToRead;
buffer = new byte[bytesToRead];
}
int bytesRead;
var totalBytesRead = 0;
while ((bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead)) > 0)
{
totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead)
break;
var remainingBytes = totalBytesToRead - totalBytesRead;
bytesToRead = Math.Min(bufferSize, (int)remainingBytes);
if (totalBytesRead + bytesToRead > buffer.Length)
{
ResizeBuffer(ref buffer, Math.Min(totalBytesToRead, buffer.Length * 2));
}
}
if (totalBytesRead != buffer.Length)
{
//Normally this should not happen. Resize the buffer anyway
var newBuffer = new byte[totalBytesRead];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, totalBytesRead);
buffer = newBuffer;
}
return buffer;
return stream.ReadAsync(buffer, 0, bytesToRead);
}
public void Dispose()
......@@ -160,7 +122,7 @@ namespace Titanium.Web.Proxy.Helpers
if (!disposed)
{
disposed = true;
buffers.Enqueue(staticBuffer);
BufferPool.ReturnBuffer(Buffer);
}
}
......@@ -172,7 +134,7 @@ namespace Titanium.Web.Proxy.Helpers
private void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
System.Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
}
......
......@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Helpers
{
private readonly Stream baseStream;
private readonly byte[] streamBuffer;
private byte[] streamBuffer;
private int bufferLength;
......@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Helpers
public CustomBufferedStream(Stream baseStream, int bufferSize)
{
this.baseStream = baseStream;
streamBuffer = new byte[bufferSize];
streamBuffer = BufferPool.GetBuffer(bufferSize);
}
/// <summary>
......@@ -146,14 +146,6 @@ namespace Titanium.Web.Proxy.Helpers
return baseStream.BeginWrite(buffer, offset, count, callback, state);
}
/// <summary>
/// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed.
/// </summary>
public override void Close()
{
baseStream.Close();
}
/// <summary>
/// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token.
/// </summary>
......@@ -238,14 +230,24 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests.
/// Asynchronously reads a sequence of bytes from the current stream,
/// advances the position within the stream by the number of bytes read,
/// and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which to begin writing data from the stream.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which
/// to begin writing data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.
/// The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous read operation. The value of the <paramref name="TResult" /> parameter contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached.
/// A task that represents the asynchronous read operation.
/// The value of the parameter contains the total
/// number of bytes read into the buffer.
/// The result value can be less than the number of bytes
/// requested if the number of bytes currently available is
/// less than the requested number, or it can be 0 (zero)
/// if the end of the stream has been reached.
/// </returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
......@@ -330,9 +332,10 @@ namespace Titanium.Web.Proxy.Helpers
protected override void Dispose(bool disposing)
{
baseStream.Dispose();
BufferPool.ReturnBuffer(streamBuffer);
streamBuffer = null;
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary>
......
......@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Add Firefox settings.
/// </summary>
internal void AddFirefox()
internal void UseSystemProxy()
{
try
{
......@@ -19,48 +19,30 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd();
myReader.Close();
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);")) return;
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents.Replace("user_pref(\"network.proxy.type\", 0);", "");
File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents);
}
catch (Exception)
{
// Only exception should be a read/write error because the user opened up FireFox so they can be ignored.
}
}
if (!File.Exists(myFfPrefFile))
{
return;
}
/// <summary>
/// Remove firefox settings.
/// </summary>
internal void RemoveFirefox()
{
try
{
var myProfileDirectory =
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd();
myReader.Close();
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);"))
for (int i = 0; i <= 4; i++)
{
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents + "\n\r" + "user_pref(\"network.proxy.type\", 0);";
var searchStr = $"user_pref(\"network.proxy.type\", {i});";
File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents);
if (myPrefContents.Contains(searchStr))
{
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents.Replace(searchStr,
"user_pref(\"network.proxy.type\", 5);");
}
}
File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents);
}
catch (Exception)
{
......
using System;
using System.Text;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
internal static class HttpHelper
{
private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1");
public static Encoding GetEncodingFromContentType(string contentType)
{
try
{
//return default if not specified
if (contentType == null)
{
return defaultEncoding;
}
//extract the encoding by finding the charset
var parameters = contentType.Split(ProxyConstants.SemiColonSplit);
foreach (var parameter in parameters)
{
var encodingSplit = parameter.Split(ProxyConstants.EqualSplit, 2);
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
string value = encodingSplit[1];
if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase))
{
//todo: what is this?
continue;
}
if (value.Length > 2 && value[0] == '"' && value[value.Length - 1] == '"')
{
value = value.Substring(1, value.Length - 2);
}
return Encoding.GetEncoding(value);
}
}
}
catch
{
//parsing errors
// ignored
}
//return default if not specified
return defaultEncoding;
}
/// <summary>
/// Tries to get root domain from a given hostname
/// Adapted from below answer
/// https://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net
/// </summary>
/// <param name="hostname"></param>
/// <returns></returns>
internal static string GetWildCardDomainName(string hostname)
{
//only for subdomains we need wild card
//example www.google.com or gstatic.google.com
//but NOT for google.com
if (hostname.Split(ProxyConstants.DotSplit).Length > 2)
{
int idx = hostname.IndexOf(ProxyConstants.DotSplit);
var rootDomain = hostname.Substring(idx + 1);
return "*." + rootDomain;
}
//return as it is
return hostname;
}
}
}
......@@ -7,13 +7,15 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
internal class RunTime
{
private static Lazy<bool> isMonoRuntime
= new Lazy<bool>(()=> Type.GetType("Mono.Runtime") != null);
/// <summary>
/// Checks if current run time is Mono
/// </summary>
/// <returns></returns>
internal static bool IsRunningOnMono()
{
return Type.GetType("Mono.Runtime") != null;
return isMonoRuntime.Value;
}
}
}
......@@ -179,12 +179,14 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="isHttps"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <param name="connection"></param>
/// <returns></returns>
internal static async Task SendRaw(ProxyServer server,
string remoteHostName, int remotePort,
string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
Stream clientStream, TcpConnectionFactory tcpConnectionFactory,
TcpConnection connection = null)
{
//prepare the prefix content
StringBuilder sb = null;
......@@ -210,10 +212,23 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(ProxyConstants.NewLine);
}
var tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort,
httpVersion, isHttps,
null, null, clientStream);
bool connectionCreated = false;
TcpConnection tcpConnection;
//create new connection if connection is null
if (connection == null)
{
tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort,
httpVersion, isHttps,
null, null);
connectionCreated = true;
}
else
{
tcpConnection = connection;
}
try
{
......@@ -228,8 +243,14 @@ namespace Titanium.Web.Proxy.Helpers
}
finally
{
tcpConnection.Dispose();
Interlocked.Decrement(ref server.serverConnectionCount);
//if connection was null
//then a new connection was created
//so dispose the new connection
if (connectionCreated)
{
tcpConnection.Dispose();
Interlocked.Decrement(ref server.serverConnectionCount);
}
}
}
}
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
......@@ -160,7 +161,13 @@ namespace Titanium.Web.Proxy.Http
//return if this is already read
if (Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
string line = await ServerConnection.StreamReader.ReadLineAsync();
if (line == null)
{
throw new IOException();
}
var httpResult = line.Split(ProxyConstants.SpaceSplit, 3);
if (string.IsNullOrEmpty(httpResult[0]))
{
......@@ -168,7 +175,7 @@ namespace Titanium.Web.Proxy.Http
httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
}
var httpVersion = httpResult[0].Trim().ToLower();
var httpVersion = httpResult[0];
var version = HttpHeader.Version11;
if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
......
......@@ -242,8 +242,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
internal string RequestBodyString { get; set; }
/// <summary>
/// Request body was read by user?
/// </summary>
internal bool RequestBodyRead { get; set; }
/// <summary>
/// Request is ready to be sent (user callbacks are complete?)
/// </summary>
internal bool RequestLocked { get; set; }
/// <summary>
......
......@@ -213,7 +213,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
internal string ResponseBodyString { get; set; }
/// <summary>
/// Was response body read by user
/// </summary>
internal bool ResponseBodyRead { get; set; }
/// <summary>
/// Is response is no more modifyable by user (user callbacks complete?)
/// </summary>
internal bool ResponseLocked { get; set; }
/// <summary>
......
......@@ -26,13 +26,19 @@ namespace Titanium.Web.Proxy.Models
EnableSsl = enableSsl;
}
/// <summary>
/// underlying TCP Listener object
/// </summary>
internal TcpListener Listener { get; set; }
/// <summary>
/// Ip Address.
/// Ip Address we are listening.
/// </summary>
public IPAddress IpAddress { get; internal set; }
/// <summary>
/// Port.
/// Port we are listening.
/// </summary>
public int Port { get; internal set; }
......@@ -48,7 +54,6 @@ namespace Titanium.Web.Proxy.Models
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
internal TcpListener Listener { get; set; }
}
/// <summary>
......@@ -64,6 +69,12 @@ namespace Titanium.Web.Proxy.Models
internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// Remote HTTPS ports we are allowed to communicate with
/// CONNECT request to ports other than these will not be decrypted
/// </summary>
public List<int> RemoteHttpsPorts { get; set; }
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
......@@ -112,6 +123,8 @@ namespace Titanium.Web.Proxy.Models
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{
//init to well known HTTPS ports
RemoteHttpsPorts = new List<int> { 443, 8443 };
}
}
......
......@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal Version Version { get; set; }
internal TcpClient TcpClient { get; set; }
internal TcpClient TcpClient { private get; set; }
/// <summary>
/// used to read lines from server
......@@ -54,7 +54,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
public void Dispose()
{
Stream?.Close();
Stream?.Dispose();
StreamReader?.Dispose();
......
......@@ -28,17 +28,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="isHttps"></param>
/// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream)
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
{
TcpClient client;
CustomBufferedStream stream;
bool useHttpProxy = false;
//check if external proxy is set for HTTP
if (!isHttps && externalHttpProxy != null
......@@ -71,88 +66,86 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
}
if (isHttps)
{
SslStream sslStream = null;
TcpClient client = null;
CustomBufferedStream stream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy)
try
{
if (isHttps)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine })
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy)
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine })
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
var result = await reader.ReadLineAsync();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
var result = await reader.ReadLineAsync();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await reader.ReadAndIgnoreAllLinesAsync();
await reader.ReadAndIgnoreAllLinesAsync();
}
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
try
{
sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
server.SelectClientCertificate);
var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, server.BufferSize);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
stream = new CustomBufferedStream(sslStream, server.BufferSize);
}
catch
else
{
sslStream?.Close();
sslStream?.Dispose();
throw;
if (useHttpProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
}
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
}
else
catch (Exception)
{
if (useHttpProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
stream?.Dispose();
client?.Close();
throw;
}
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
Interlocked.Increment(ref server.serverConnectionCount);
return new TcpConnection
......
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Runtime.InteropServices;
internal class Common
{
#region Private constants
private const int ISC_REQ_REPLAY_DETECT = 0x00000004;
private const int ISC_REQ_SEQUENCE_DETECT = 0x00000008;
private const int ISC_REQ_CONFIDENTIALITY = 0x00000010;
private const int ISC_REQ_CONNECTION = 0x00000800;
#endregion
internal static uint NewContextAttributes = 0;
internal static Common.SecurityInteger NewLifeTime = new SecurityInteger(0);
#region internal constants
internal const int StandardContextAttributes = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONNECTION;
internal const int SecurityNativeDataRepresentation = 0x10;
internal const int MaximumTokenSize = 12288;
internal const int SecurityCredentialsOutbound = 2;
internal const int SuccessfulResult = 0;
internal const int IntermediateResult = 0x90312;
#endregion
#region internal enumerations
internal enum SecurityBufferType
{
SECBUFFER_VERSION = 0,
SECBUFFER_EMPTY = 0,
SECBUFFER_DATA = 1,
SECBUFFER_TOKEN = 2
}
[Flags]
internal enum NtlmFlags : int
{
// The client sets this flag to indicate that it supports Unicode strings.
NegotiateUnicode = 0x00000001,
// This is set to indicate that the client supports OEM strings.
NegotiateOem = 0x00000002,
// This requests that the server send the authentication target with the Type 2 reply.
RequestTarget = 0x00000004,
// Indicates that NTLM authentication is supported.
NegotiateNtlm = 0x00000200,
// When set, the client will send with the message the name of the domain in which the workstation has membership.
NegotiateDomainSupplied = 0x00001000,
// Indicates that the client is sending its workstation name with the message.
NegotiateWorkstationSupplied = 0x00002000,
// Indicates that communication between the client and server after authentication should carry a "dummy" signature.
NegotiateAlwaysSign = 0x00008000,
// Indicates that this client supports the NTLM2 signing and sealing scheme; if negotiated, this can also affect the response calculations.
NegotiateNtlm2Key = 0x00080000,
// Indicates that this client supports strong (128-bit) encryption.
Negotiate128 = 0x20000000,
// Indicates that this client supports medium (56-bit) encryption.
Negotiate56 = (unchecked((int)0x80000000))
}
internal enum NtlmAuthLevel
{
/* Use LM and NTLM, never use NTLMv2 session security. */
LM_and_NTLM,
/* Use NTLMv2 session security if the server supports it,
* otherwise fall back to LM and NTLM. */
LM_and_NTLM_and_try_NTLMv2_Session,
/* Use NTLMv2 session security if the server supports it,
* otherwise fall back to NTLM. Never use LM. */
NTLM_only,
/* Use NTLMv2 only. */
NTLMv2_only,
}
#endregion
#region internal structures
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityHandle
{
internal IntPtr LowPart;
internal IntPtr HighPart;
internal SecurityHandle(int dummy)
{
LowPart = HighPart = IntPtr.Zero;
}
/// <summary>
/// Resets all internal pointers to default value
/// </summary>
internal void Reset()
{
LowPart = HighPart = IntPtr.Zero;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityInteger
{
internal uint LowPart;
internal int HighPart;
internal SecurityInteger(int dummy)
{
LowPart = 0;
HighPart = 0;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityBuffer : IDisposable
{
internal int cbBuffer;
internal int cbBufferType;
internal IntPtr pvBuffer;
internal SecurityBuffer(int bufferSize)
{
cbBuffer = bufferSize;
cbBufferType = (int)SecurityBufferType.SECBUFFER_TOKEN;
pvBuffer = Marshal.AllocHGlobal(bufferSize);
}
internal SecurityBuffer(byte[] secBufferBytes)
{
cbBuffer = secBufferBytes.Length;
cbBufferType = (int)SecurityBufferType.SECBUFFER_TOKEN;
pvBuffer = Marshal.AllocHGlobal(cbBuffer);
Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
}
internal SecurityBuffer(byte[] secBufferBytes, SecurityBufferType bufferType)
{
cbBuffer = secBufferBytes.Length;
cbBufferType = (int)bufferType;
pvBuffer = Marshal.AllocHGlobal(cbBuffer);
Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
}
public void Dispose()
{
if (pvBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(pvBuffer);
pvBuffer = IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityBufferDesciption : IDisposable
{
internal int ulVersion;
internal int cBuffers;
internal IntPtr pBuffers; //Point to SecBuffer
internal SecurityBufferDesciption(int bufferSize)
{
ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION;
cBuffers = 1;
Common.SecurityBuffer ThisSecBuffer = new Common.SecurityBuffer(bufferSize);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false);
}
internal SecurityBufferDesciption(byte[] secBufferBytes)
{
ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION;
cBuffers = 1;
Common.SecurityBuffer ThisSecBuffer = new Common.SecurityBuffer(secBufferBytes);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false);
}
public void Dispose()
{
if (pBuffers != IntPtr.Zero)
{
if (cBuffers == 1)
{
Common.SecurityBuffer ThisSecBuffer = (Common.SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(Common.SecurityBuffer));
ThisSecBuffer.Dispose();
}
else
{
for (int Index = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//What we need to do here is to grab a hold of the pvBuffer allocate by the individual
//SecBuffer and release it...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer));
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.FreeHGlobal(SecBufferpvBuffer);
}
}
Marshal.FreeHGlobal(pBuffers);
pBuffers = IntPtr.Zero;
}
}
internal byte[] GetBytes()
{
byte[] Buffer = null;
if (pBuffers == IntPtr.Zero)
{
throw new InvalidOperationException("Object has already been disposed!!!");
}
if (cBuffers == 1)
{
Common.SecurityBuffer ThisSecBuffer = (Common.SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(Common.SecurityBuffer));
if (ThisSecBuffer.cbBuffer > 0)
{
Buffer = new byte[ThisSecBuffer.cbBuffer];
Marshal.Copy(ThisSecBuffer.pvBuffer, Buffer, 0, ThisSecBuffer.cbBuffer);
}
}
else
{
int BytesToAllocate = 0;
for (int Index = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//What we need to do here calculate the total number of bytes we need to copy...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer));
BytesToAllocate += Marshal.ReadInt32(pBuffers, CurrentOffset);
}
Buffer = new byte[BytesToAllocate];
for (int Index = 0, BufferIndex = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//Now iterate over the individual buffers and put them together into a
//byte array...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer));
int BytesToCopy = Marshal.ReadInt32(pBuffers, CurrentOffset);
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.Copy(SecBufferpvBuffer, Buffer, BufferIndex, BytesToCopy);
BufferIndex += BytesToCopy;
}
}
return (Buffer);
}
}
#endregion
}
}
//
// Mono.Security.BitConverterLE.cs
// Like System.BitConverter but always little endian
//
// Author:
// Bernie Solomon
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
internal sealed class LittleEndian
{
private LittleEndian ()
{
}
unsafe private static byte[] GetUShortBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes[0], bytes[1] };
}
else
{
return new byte[] { bytes[1], bytes[0] };
}
}
unsafe private static byte[] GetUIntBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes[0], bytes[1], bytes[2], bytes[3] };
}
else
{
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
}
}
unsafe private static byte[] GetULongBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes [0], bytes [1], bytes [2], bytes [3],
bytes [4], bytes [5], bytes [6], bytes [7] };
}
else
{
return new byte[] { bytes [7], bytes [6], bytes [5], bytes [4],
bytes [3], bytes [2], bytes [1], bytes [0] };
}
}
unsafe internal static byte[] GetBytes (bool value)
{
return new byte [] { value ? (byte)1 : (byte)0 };
}
unsafe internal static byte[] GetBytes (char value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (short value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (int value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (long value)
{
return GetULongBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (ushort value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (uint value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (ulong value)
{
return GetULongBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (float value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (double value)
{
return GetULongBytes ((byte *) &value);
}
unsafe private static void UShortFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian)
{
dst [0] = src [startIndex];
dst [1] = src [startIndex + 1];
}
else
{
dst [0] = src [startIndex + 1];
dst [1] = src [startIndex];
}
}
unsafe private static void UIntFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian)
{
dst [0] = src [startIndex];
dst [1] = src [startIndex + 1];
dst [2] = src [startIndex + 2];
dst [3] = src [startIndex + 3];
}
else
{
dst [0] = src [startIndex + 3];
dst [1] = src [startIndex + 2];
dst [2] = src [startIndex + 1];
dst [3] = src [startIndex];
}
}
unsafe private static void ULongFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian) {
for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + i];
} else {
for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + (7 - i)];
}
}
unsafe internal static bool ToBoolean (byte[] value, int startIndex)
{
return value [startIndex] != 0;
}
unsafe internal static char ToChar (byte[] value, int startIndex)
{
char ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static short ToInt16 (byte[] value, int startIndex)
{
short ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static int ToInt32 (byte[] value, int startIndex)
{
int ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static long ToInt64 (byte[] value, int startIndex)
{
long ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
{
ushort ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static uint ToUInt32 (byte[] value, int startIndex)
{
uint ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static ulong ToUInt64 (byte[] value, int startIndex)
{
ulong ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static float ToSingle (byte[] value, int startIndex)
{
float ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static double ToDouble (byte[] value, int startIndex)
{
double ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
}
}
//
// Nancy.Authentication.Ntlm.Protocol.Type3Message - Authentication
//
// Author:
// Sebastien Pouliot <sebastien@ximian.com>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// References
// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
// http://www.innovation.ch/java/ntlm.html
// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
// http://davenport.sourceforge.net/ntlm.html
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Text;
internal class Message
{
static private byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 };
internal Message (byte[] message)
{
_type = 3;
Decode (message);
}
/// <summary>
/// Domain name
/// </summary>
internal string Domain
{
get;
private set;
}
/// <summary>
/// Username
/// </summary>
internal string Username
{
get;
private set;
}
private int _type;
private Common.NtlmFlags _flags;
internal Common.NtlmFlags Flags
{
get { return _flags; }
set { _flags = value; }
}
// methods
private void Decode (byte[] message)
{
//base.Decode (message);
if (message == null)
throw new ArgumentNullException("message");
if (message.Length < 12)
{
string msg = "Minimum Type3 message length is 12 bytes.";
throw new ArgumentOutOfRangeException("message", message.Length, msg);
}
if (!CheckHeader(message))
{
string msg = "Invalid Type3 message header.";
throw new ArgumentException(msg, "message");
}
if (LittleEndian.ToUInt16 (message, 56) != message.Length)
{
string msg = "Invalid Type3 message length.";
throw new ArgumentException (msg, "message");
}
if (message.Length >= 64)
{
Flags = (Common.NtlmFlags)LittleEndian.ToUInt32(message, 60);
}
else
{
Flags = (Common.NtlmFlags)0x8201;
}
int dom_len = LittleEndian.ToUInt16 (message, 28);
int dom_off = LittleEndian.ToUInt16 (message, 32);
this.Domain = DecodeString (message, dom_off, dom_len);
int user_len = LittleEndian.ToUInt16 (message, 36);
int user_off = LittleEndian.ToUInt16 (message, 40);
this.Username = DecodeString (message, user_off, user_len);
}
string DecodeString (byte[] buffer, int offset, int len)
{
if ((Flags & Common.NtlmFlags.NegotiateUnicode) != 0)
{
return Encoding.Unicode.GetString(buffer, offset, len);
}
else
{
return Encoding.ASCII.GetString(buffer, offset, len);
}
}
protected bool CheckHeader(byte[] message)
{
for (int i = 0; i < header.Length; i++)
{
if (message[i] != header[i])
return false;
}
return (LittleEndian.ToUInt32(message, 8) == _type);
}
}
}
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
/// <summary>
/// Status of authenticated session
/// </summary>
internal class State
{
internal State()
{
this.Credentials = new Common.SecurityHandle(0);
this.Context = new Common.SecurityHandle(0);
this.LastSeen = DateTime.Now;
}
/// <summary>
/// Credentials used to validate NTLM hashes
/// </summary>
internal Common.SecurityHandle Credentials;
/// <summary>
/// Context will be used to validate HTLM hashes
/// </summary>
internal Common.SecurityHandle Context;
/// <summary>
/// Timestamp needed to calculate validity of the authenticated session
/// </summary>
internal DateTime LastSeen;
internal void ResetHandles()
{
this.Credentials.Reset();
this.Context.Reset();
}
internal void UpdatePresence()
{
this.LastSeen = DateTime.Now;
}
}
}
// http://pinvoke.net/default.aspx/secur32/InitializeSecurityContext.html
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
using static Common;
using System.Threading.Tasks;
internal class WinAuthEndPoint
{
/// <summary>
/// Keep track of auth states for reuse in final challenge response
/// </summary>
private static IDictionary<Guid, State> authStates
= new ConcurrentDictionary<Guid, State>();
/// <summary>
/// Acquire the intial client token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <returns></returns>
internal static byte[] AcquireInitialSecurityToken(string hostname,
string authScheme, Guid requestId)
{
byte[] token = null;
//null for initial call
SecurityBufferDesciption serverToken
= new SecurityBufferDesciption();
SecurityBufferDesciption clientToken
= new SecurityBufferDesciption(MaximumTokenSize);
try
{
int result;
var state = new State();
result = AcquireCredentialsHandle(
WindowsIdentity.GetCurrent().Name,
authScheme,
SecurityCredentialsOutbound,
IntPtr.Zero,
IntPtr.Zero,
0,
IntPtr.Zero,
ref state.Credentials,
ref NewLifeTime);
if (result != SuccessfulResult)
{
// Credentials acquire operation failed.
return null;
}
result = InitializeSecurityContext(ref state.Credentials,
IntPtr.Zero,
hostname,
StandardContextAttributes,
0,
SecurityNativeDataRepresentation,
ref serverToken,
0,
out state.Context,
out clientToken,
out NewContextAttributes,
out NewLifeTime);
if (result != IntermediateResult)
{
// Client challenge issue operation failed.
return null;
}
token = clientToken.GetBytes();
authStates.Add(requestId, state);
}
finally
{
clientToken.Dispose();
serverToken.Dispose();
}
return token;
}
/// <summary>
/// Acquire the final token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="serverChallenge"></param>
/// <param name="requestId"></param>
/// <returns></returns>
internal static byte[] AcquireFinalSecurityToken(string hostname,
byte[] serverChallenge, Guid requestId)
{
byte[] token = null;
//user server challenge
SecurityBufferDesciption serverToken
= new SecurityBufferDesciption(serverChallenge);
SecurityBufferDesciption clientToken
= new SecurityBufferDesciption(MaximumTokenSize);
try
{
int result;
var state = authStates[requestId];
state.UpdatePresence();
result = InitializeSecurityContext(ref state.Credentials,
ref state.Context,
hostname,
StandardContextAttributes,
0,
SecurityNativeDataRepresentation,
ref serverToken,
0,
out state.Context,
out clientToken,
out NewContextAttributes,
out NewLifeTime);
if (result != SuccessfulResult)
{
// Client challenge issue operation failed.
return null;
}
authStates.Remove(requestId);
token = clientToken.GetBytes();
}
finally
{
clientToken.Dispose();
serverToken.Dispose();
}
return token;
}
/// <summary>
/// Clear any hanging states
/// </summary>
/// <param name="stateCacheTimeOutMinutes"></param>
internal static async void ClearIdleStates(int stateCacheTimeOutMinutes)
{
var cutOff = DateTime.Now.AddMinutes(-1 * stateCacheTimeOutMinutes);
var outdated = authStates
.Where(x => x.Value.LastSeen < cutOff)
.ToList();
foreach (var cache in outdated)
{
authStates.Remove(cache.Key);
}
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
}
#region Native calls to secur32.dll
[DllImport("secur32.dll", SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle
IntPtr phContext, //PCtxtHandle
string pszTargetName,
int fContextReq,
int Reserved1,
int TargetDataRep,
ref SecurityBufferDesciption pInput, //PSecBufferDesc SecBufferDesc
int Reserved2,
out SecurityHandle phNewContext, //PCtxtHandle
out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!!
out SecurityInteger ptsExpiry); //PTimeStamp
[DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle
ref SecurityHandle phContext, //PCtxtHandle
string pszTargetName,
int fContextReq,
int Reserved1,
int TargetDataRep,
ref SecurityBufferDesciption SecBufferDesc, //PSecBufferDesc SecBufferDesc
int Reserved2,
out SecurityHandle phNewContext, //PCtxtHandle
out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!!
out SecurityInteger ptsExpiry); //PTimeStamp
[DllImport("secur32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern int AcquireCredentialsHandle(
string pszPrincipal, //SEC_CHAR*
string pszPackage, //SEC_CHAR* //"Kerberos","NTLM","Negotiative"
int fCredentialUse,
IntPtr PAuthenticationID, //_LUID AuthenticationID,//pvLogonID, //PLUID
IntPtr pAuthData, //PVOID
int pGetKeyFn, //SEC_GET_KEY_FN
IntPtr pvGetKeyArgument, //PVOID
ref Common.SecurityHandle phCredential, //SecHandle //PCtxtHandle ref
ref Common.SecurityInteger ptsExpiry); //PTimeStamp //TimeStamp ref
#endregion
}
}
using Titanium.Web.Proxy.Network.WinAuth.Security;
using System;
namespace Titanium.Web.Proxy.Network.WinAuth
{
/// <summary>
/// A handler for NTLM/Kerberos windows authentication challenge from server
/// NTLM process details below
/// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/
/// </summary>
public static class WinAuthHandler
{
/// <summary>
/// Get the initial client token for server
/// using credentials of user running the proxy server process
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <returns></returns>
public static string GetInitialAuthToken(string serverHostname,
string authScheme, Guid requestId)
{
var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
/// <summary>
/// Get the final token given the server challenge token
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="serverToken"></param>
/// <param name="requestId"></param>
/// <returns></returns>
public static string GetFinalAuthToken(string serverHostname,
string serverToken, Guid requestId)
{
var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname,
Convert.FromBase64String(serverToken), requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
}
}
......@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy
{
......@@ -60,12 +61,13 @@ namespace Titanium.Web.Proxy
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
#if !DEBUG
/// <summary>
/// Set firefox to use default system proxy
/// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager();
#endif
private FireFoxProxySettingsManager firefoxProxySettingsManager
= new FireFoxProxySettingsManager();
/// <summary>
/// Buffer size used throughout this proxy
......@@ -196,6 +198,15 @@ namespace Titanium.Web.Proxy
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man
/// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process)
/// </summary>
public bool EnableWinAuth { get; set; } = true;
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
......@@ -235,7 +246,7 @@ namespace Titanium.Web.Proxy
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; }
/// <summary>
/// A list of IpAddress & port this proxy is listening to
/// A list of IpAddress and port this proxy is listening to
/// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; }
......@@ -277,9 +288,7 @@ namespace Titanium.Web.Proxy
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager();
#if !DEBUG
new FireFoxProxySettingsManager();
#endif
CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null)
......@@ -353,9 +362,9 @@ namespace Titanium.Web.Proxy
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), endPoint.Port);
endPoint.IsSystemHttpProxy = true;
#if !DEBUG
firefoxProxySettingsManager.AddFirefox();
#endif
firefoxProxySettingsManager.UseSystemProxy();
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTP Proxy", endPoint.IpAddress, endPoint.Port);
}
......@@ -397,9 +406,8 @@ namespace Titanium.Web.Proxy
endPoint.IsSystemHttpsProxy = true;
#if !DEBUG
firefoxProxySettingsManager.AddFirefox();
#endif
firefoxProxySettingsManager.UseSystemProxy();
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port);
}
......@@ -466,6 +474,11 @@ namespace Titanium.Web.Proxy
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
if (!RunTime.IsRunningOnMono())
{
WinAuthEndPoint.ClearIdleStates(2);
}
proxyRunning = true;
}
......@@ -485,9 +498,6 @@ namespace Titanium.Web.Proxy
if (setAsSystemProxy)
{
systemProxySettingsManager.DisableAllProxy();
#if !DEBUG
firefoxProxySettingsManager.RemoveFirefox();
#endif
}
foreach (var endPoint in ProxyEndPoints)
......
......@@ -87,7 +87,8 @@ namespace Titanium.Web.Proxy
List<HttpHeader> connectRequestHeaders = null;
//Client wants to create a secure tcp tunnel (its a HTTPS request)
if (httpVerb == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
if (httpVerb == "CONNECT" && !excluded
&& endPoint.RemoteHttpsPorts.Contains(httpRemoteUri.Port))
{
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
connectRequestHeaders = new List<HttpHeader>();
......@@ -111,10 +112,12 @@ namespace Titanium.Web.Proxy
try
{
sslStream = new SslStream(clientStream, true);
sslStream = new SslStream(clientStream);
var certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);
var certificate = endPoint.GenericCertificate ??
CertificateManager.CreateCertificate(httpRemoteUri.Host, false);
CertificateManager.CreateCertificate(certName, false);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false,
......@@ -191,7 +194,7 @@ namespace Titanium.Web.Proxy
{
if (endPoint.EnableSsl)
{
var sslStream = new SslStream(clientStream, true);
var sslStream = new SslStream(clientStream);
clientStream = new CustomBufferedStream(sslStream, BufferSize);
//implement in future once SNI supported by SSL stream, for now use the same certificate
......@@ -331,7 +334,7 @@ namespace Titanium.Web.Proxy
await TcpHelper.SendRaw(this,
httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
clientStream, tcpConnectionFactory);
clientStream, tcpConnectionFactory, connection);
args.Dispose();
break;
......@@ -542,8 +545,7 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.HttpVersion,
args.IsHttps,
customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy,
args.ProxyClient.ClientStream);
customUpStreamHttpsProxy ?? UpStreamHttpsProxy);
}
......@@ -599,7 +601,7 @@ namespace Titanium.Web.Proxy
//send the request body bytes to server
if (args.WebSession.Request.ContentLength > 0)
{
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(BufferSize, postStream, args.WebSession.Request.ContentLength);
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength);
}
//Need to revist, find any potential bugs
//send the request body bytes to server in chunks
......
......@@ -36,6 +36,19 @@ namespace Titanium.Web.Proxy
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
}
//check for windows authentication
if(EnableWinAuth
&& !RunTime.IsRunningOnMono()
&& args.WebSession.Response.ResponseStatusCode == "401")
{
var disposed = await Handle401UnAuthorized(args);
if(disposed)
{
return true;
}
}
args.ReRequest = false;
//If user requested call back then do it
......@@ -44,16 +57,13 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeParallelAsync(this, args);
}
//if user requested to send request again
//likely after making modifications from User Response Handler
if (args.ReRequest)
{
if (args.WebSession.ServerConnection != null)
{
args.WebSession.ServerConnection.Dispose();
Interlocked.Decrement(ref serverConnectionCount);
}
var connection = await GetServerConnection(args);
var disposed = await HandleHttpSessionRequestInternal(null, args, true);
//clear current response
await args.ClearResponse();
var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed;
}
......
......@@ -7,9 +7,12 @@ namespace Titanium.Web.Proxy.Shared
/// </summary>
internal class ProxyConstants
{
internal static readonly char DotSplit = '.';
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine);
......
......@@ -37,6 +37,7 @@
<DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
......@@ -74,7 +75,9 @@
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Helpers\HttpHelper.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\BufferPool.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" />
......@@ -103,6 +106,12 @@
<Compile Include="Network\Tcp\TcpConnectionFactory.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Network\WinAuth\Security\Common.cs" />
<Compile Include="Network\WinAuth\Security\WinAuthEndPoint.cs" />
<Compile Include="Network\WinAuth\Security\LittleEndian.cs" />
<Compile Include="Network\WinAuth\Security\Message.cs" />
<Compile Include="Network\WinAuth\Security\State.cs" />
<Compile Include="Network\WinAuth\WinAuthHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="RequestHandler.cs" />
......@@ -117,6 +126,7 @@
<Compile Include="Shared\ProxyConstants.cs" />
<Compile Include="Network\Tcp\TcpRow.cs" />
<Compile Include="Network\Tcp\TcpTable.cs" />
<Compile Include="WinAuthHandler.cs" />
</ItemGroup>
<ItemGroup>
<COMReference Include="CERTENROLLLib">
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
//possible header names
private static List<string> authHeaderNames
= new List<string>() {
"WWW-Authenticate",
//IIS 6.0 messed up names below
"WWWAuthenticate",
"NTLMAuthorization",
"NegotiateAuthorization",
"KerberosAuthorization"
};
private static List<string> authSchemes
= new List<string>() {
"NTLM",
"Negotiate",
"Kerberos"
};
/// <summary>
/// Handle windows NTLM authentication
/// Can expand this for Kerberos in future
/// Note: NTLM/Kerberos cannot do a man in middle operation
/// we do for HTTPS requests.
/// As such we will be sending local credentials of current
/// User to server to authenticate requests.
/// To disable this set ProxyServer.EnableWinAuth to false
/// </summary>
internal async Task<bool> Handle401UnAuthorized(SessionEventArgs args)
{
string headerName = null;
HttpHeader authHeader = null;
//check in non-unique headers first
var header = args.WebSession.Response
.NonUniqueResponseHeaders
.FirstOrDefault(x =>
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>()))
{
headerName = header.Key;
}
if (headerName != null)
{
authHeader = args.WebSession.Response
.NonUniqueResponseHeaders[headerName]
.Where(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault();
}
//check in unique headers
if (authHeader == null)
{
//check in non-unique headers first
var uHeader = args.WebSession.Response
.ResponseHeaders
.FirstOrDefault(x =>
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>()))
{
headerName = uHeader.Key;
}
if (headerName != null)
{
authHeader = authSchemes.Any(x => args.WebSession.Response
.ResponseHeaders[headerName].Value.StartsWith(x, StringComparison.OrdinalIgnoreCase)) ?
args.WebSession.Response.ResponseHeaders[headerName] : null;
}
}
if (authHeader != null)
{
var scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
//initial value will match exactly any of the schemes
if (scheme != null)
{
var clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id);
args.WebSession.Request.RequestHeaders.Add("Authorization", new HttpHeader("Authorization", string.Concat(scheme, clientToken)));
}
//challenge value will start with any of the scheme selected
else
{
scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase));
var serverToken = authHeader.Value.Substring(scheme.Length + 1);
var clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id);
args.WebSession.Request.RequestHeaders["Authorization"]
= new HttpHeader("Authorization", string.Concat(scheme, clientToken));
}
//clear current response
await args.ClearResponse();
var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed;
}
return false;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment