Add upstream proxy challenge logging
This commit is contained in:
111
Program.cs
111
Program.cs
@@ -479,8 +479,10 @@ class Program
|
|||||||
return resp;
|
return resp;
|
||||||
|
|
||||||
var challenges = ParseProxyAuthenticateValues(resp.Raw);
|
var challenges = ParseProxyAuthenticateValues(resp.Raw);
|
||||||
|
Log.Info($"[conn:{connectionId}] upstream challenges: {string.Join(", ", challenges)}");
|
||||||
if (code != 407 || challenges.Count == 0)
|
if (code != 407 || challenges.Count == 0)
|
||||||
return resp;
|
return resp;
|
||||||
|
await DrainResponseBodyIfAny(upstreamStream, resp.Raw, connectionId, "initial-407");
|
||||||
|
|
||||||
// 2) Basic fallback.
|
// 2) Basic fallback.
|
||||||
if (!string.IsNullOrEmpty(UPSTREAM_PROXY_AUTH_HEADER))
|
if (!string.IsNullOrEmpty(UPSTREAM_PROXY_AUTH_HEADER))
|
||||||
@@ -491,6 +493,8 @@ class Program
|
|||||||
return basicResp;
|
return basicResp;
|
||||||
resp = basicResp ?? resp;
|
resp = basicResp ?? resp;
|
||||||
challenges = ParseProxyAuthenticateValues(resp.Raw);
|
challenges = ParseProxyAuthenticateValues(resp.Raw);
|
||||||
|
Log.Info($"[conn:{connectionId}] upstream challenges after basic: {string.Join(", ", challenges)}");
|
||||||
|
await DrainResponseBodyIfAny(upstreamStream, resp.Raw, connectionId, "basic-407");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Negotiate/NTLM over SSPI (as browsers do for AD proxies).
|
// 3) Negotiate/NTLM over SSPI (as browsers do for AD proxies).
|
||||||
@@ -523,7 +527,9 @@ class Program
|
|||||||
return authResp ?? resp;
|
return authResp ?? resp;
|
||||||
|
|
||||||
challenges = ParseProxyAuthenticateValues(authResp.Raw);
|
challenges = ParseProxyAuthenticateValues(authResp.Raw);
|
||||||
|
Log.Info($"[conn:{connectionId}] upstream challenges after {scheme} step {step + 1}: {string.Join(", ", challenges)}");
|
||||||
inToken = ExtractSchemeToken(challenges, scheme);
|
inToken = ExtractSchemeToken(challenges, scheme);
|
||||||
|
await DrainResponseBodyIfAny(upstreamStream, authResp.Raw, connectionId, $"{scheme}-{step + 1}-407");
|
||||||
resp = authResp;
|
resp = authResp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,6 +580,111 @@ class Program
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async Task DrainResponseBodyIfAny(NetworkStream stream, string rawHeaders, long connectionId, string stage)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(rawHeaders))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int contentLength = 0;
|
||||||
|
bool chunked = false;
|
||||||
|
|
||||||
|
foreach (var line in rawHeaders.Split("\r\n", StringSplitOptions.None))
|
||||||
|
{
|
||||||
|
if (line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var value = line.Substring("Content-Length:".Length).Trim();
|
||||||
|
if (int.TryParse(value, out var len) && len > 0)
|
||||||
|
contentLength = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.StartsWith("Transfer-Encoding:", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& line.IndexOf("chunked", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
chunked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentLength > 0)
|
||||||
|
{
|
||||||
|
await ReadExactly(stream, contentLength);
|
||||||
|
Log.Info($"[conn:{connectionId}] drained {contentLength} response bytes ({stage})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunked)
|
||||||
|
{
|
||||||
|
int drained = await DrainChunkedBody(stream);
|
||||||
|
Log.Info($"[conn:{connectionId}] drained chunked body bytes={drained} ({stage})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task ReadExactly(Stream stream, int count)
|
||||||
|
{
|
||||||
|
var buffer = ArrayPool<byte>.Shared.Rent(2048);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int remain = count;
|
||||||
|
while (remain > 0)
|
||||||
|
{
|
||||||
|
int read = await stream.ReadAsync(buffer, 0, Math.Min(buffer.Length, remain));
|
||||||
|
if (read <= 0)
|
||||||
|
break;
|
||||||
|
remain -= read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<int> DrainChunkedBody(Stream stream)
|
||||||
|
{
|
||||||
|
int total = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
string sizeLine = await ReadAsciiLine(stream);
|
||||||
|
if (string.IsNullOrWhiteSpace(sizeLine))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string hex = sizeLine.Split(';', 2)[0].Trim();
|
||||||
|
if (!int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out int size))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
await ReadAsciiLine(stream); // trailing CRLF after last chunk/trailers simplified
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReadExactly(stream, size);
|
||||||
|
total += size;
|
||||||
|
await ReadExactly(stream, 2); // CRLF
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<string> ReadAsciiLine(Stream stream)
|
||||||
|
{
|
||||||
|
var bytes = new List<byte>(64);
|
||||||
|
var one = new byte[1];
|
||||||
|
while (bytes.Count < 8192)
|
||||||
|
{
|
||||||
|
int read = await stream.ReadAsync(one, 0, 1);
|
||||||
|
if (read <= 0)
|
||||||
|
break;
|
||||||
|
bytes.Add(one[0]);
|
||||||
|
int n = bytes.Count;
|
||||||
|
if (n >= 2 && bytes[n - 2] == '\r' && bytes[n - 1] == '\n')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.Count == 0)
|
||||||
|
return string.Empty;
|
||||||
|
return Encoding.ASCII.GetString(bytes.ToArray()).TrimEnd('\r', '\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SspiProxyAuthenticator : IDisposable
|
sealed class SspiProxyAuthenticator : IDisposable
|
||||||
|
|||||||
Reference in New Issue
Block a user