Add upstream proxy challenge logging
This commit is contained in:
111
Program.cs
111
Program.cs
@@ -479,8 +479,10 @@ class Program
|
||||
return resp;
|
||||
|
||||
var challenges = ParseProxyAuthenticateValues(resp.Raw);
|
||||
Log.Info($"[conn:{connectionId}] upstream challenges: {string.Join(", ", challenges)}");
|
||||
if (code != 407 || challenges.Count == 0)
|
||||
return resp;
|
||||
await DrainResponseBodyIfAny(upstreamStream, resp.Raw, connectionId, "initial-407");
|
||||
|
||||
// 2) Basic fallback.
|
||||
if (!string.IsNullOrEmpty(UPSTREAM_PROXY_AUTH_HEADER))
|
||||
@@ -491,6 +493,8 @@ class Program
|
||||
return basicResp;
|
||||
resp = basicResp ?? resp;
|
||||
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).
|
||||
@@ -523,7 +527,9 @@ class Program
|
||||
return authResp ?? resp;
|
||||
|
||||
challenges = ParseProxyAuthenticateValues(authResp.Raw);
|
||||
Log.Info($"[conn:{connectionId}] upstream challenges after {scheme} step {step + 1}: {string.Join(", ", challenges)}");
|
||||
inToken = ExtractSchemeToken(challenges, scheme);
|
||||
await DrainResponseBodyIfAny(upstreamStream, authResp.Raw, connectionId, $"{scheme}-{step + 1}-407");
|
||||
resp = authResp;
|
||||
}
|
||||
|
||||
@@ -574,6 +580,111 @@ class Program
|
||||
}
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user