Tommy 碎碎念

Tommy Wu's blog

« 上一篇 | 下一篇 »

讓 FileZilla 的 HTTP Proxy 支援 Digest 認證方式
post by tommy @ 28 元月, 2013 17:10

FileZilla 本身雖然支援 HTTP CONNECT 方式的 proxy, 不過, 它只使用 Basic 方式來認證, 如果碰到 Digest 方式的 proxy server, 就會得到 407 的錯誤而無法連線.

由於 Basic 的方式是把帳號密碼透過明碼的方式來傳送, 所以... 我不怎麼喜歡把認證方式由 Digest 改回 Basic, 所以... 就想辦法加上這個支援.

patch 如下:

diff --strip-trailing-cr -Nur a/src/engine/proxy.cpp b/src/engine/proxy.cpp
--- a/src/engine/proxy.cpp 2013-01-28 17:04:35.655438700 +0800
+++ b/src/engine/proxy.cpp 2013-01-28 17:04:44.290188300 +0800
@@ -1,6 +1,8 @@
#include <filezilla.h>
#include "proxy.h"
#include <errno.h>
+#include <wx/tokenzr.h>
+#include <nettle/md5-compat.h>
#include "ControlSocket.h"
 
enum handshake_state
@@ -25,6 +27,8 @@
m_pSendBuffer = 0;
m_pRecvBuffer = 0;
 
+ m_doAuth = false;
+
m_proxyType = unknown;
 
m_can_write = false;
@@ -39,6 +43,42 @@
delete [] m_pRecvBuffer;
}
 
+static void AppendSubHex(unsigned char val, wxString& result)
+{
+ if (val > 9)
+ {
+ result.Append((char)('a'+val-10));
+ }
+ else
+ {
+ result.Append((char)('0'+val));
+ }
+}
+
+static void AppendHex(unsigned char val, wxString& result)
+{
+ AppendSubHex((val >> 4) & 0xF, result);
+ AppendSubHex(val&0xF, result);
+}
+
+static wxString ComputeMD5(const char *buf, unsigned int len)
+{
+ MD5_CTX ctx;
+ unsigned char signature[16];
+
+ MD5Init(&ctx);
+ MD5Update(&ctx, (const unsigned char*)buf, len);
+ MD5Final(signature, &ctx);
+
+ wxString result;
+ for (int j = 0; j < (int)sizeof signature; j++)
+ {
+ AppendHex(signature[j], result);
+ }
+
+ return result;
+}
+
static wxString base64encode(const wxString& str)
{
// Code shamelessly taken from wxWidgets and adopted to encode UTF-8 strings.
@@ -96,39 +136,12 @@
{
m_handshakeState = http_wait;
 
-#if wxUSE_UNICODE
- wxWX2MBbuf challenge;
-#else
- const wxWX2MBbuf challenge;
-#endif
- int challenge_len;
- if (user != _T(""))
- {
- challenge = base64encode(user + _T(":") + pass).mb_str(wxConvUTF8);
- challenge_len = strlen(challenge);
- }
- else
- {
- challenge = (size_t)0;
- challenge_len = 0;
- }
-
// Bit oversized, but be on the safe side
- m_pSendBuffer = new char[70 + strlen(host_raw) * 2 + 2*5 + challenge_len + 23];
+ m_pSendBuffer = new char[70 + strlen(host_raw) * 2 + 2*5 + 23];
 
- if (!challenge)
- {
- m_sendBufferLen = sprintf(m_pSendBuffer, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nUser-Agent: FileZilla\r\n\r\n",
- (const char*)host_raw, port,
- (const char*)host_raw, port);
- }
- else
- {
- m_sendBufferLen = sprintf(m_pSendBuffer, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nProxy-Authorization: Basic %s\r\nUser-Agent: FileZilla\r\n\r\n",
- (const char*)host_raw, port,
- (const char*)host_raw, port,
- (const char*)challenge);
- }
+ m_sendBufferLen = sprintf(m_pSendBuffer, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nUser-Agent: FileZilla\r\n\r\n",
+ (const char*)host_raw, port,
+ (const char*)host_raw, port);
 
m_pRecvBuffer = new char[4096];
m_recvBufferLen = 4096;
@@ -302,12 +315,319 @@
if (!end)
continue;
 
+ unsigned long int nContentLength = 0;
+ bool bUseBasic = false;
+ bool bUseDigest = false;
+ wxString digestStr = _T("");
+ wxString authStr = _T("");
+ wxString header_str(m_pRecvBuffer, wxConvLocal, m_recvBufferPos);
+ wxStringTokenizer tkz(header_str, wxT("\r\n"));
+
+ while (tkz.HasMoreTokens())
+ {
+ wxString token = tkz.GetNextToken();
+ if (token.Mid(0, 15).Lower() == _T("content-length:"))
+ {
+ token.Mid(15).Trim(true).Trim(false).ToULong(&nContentLength);
+ continue;
+ }
+ if (token.Mid(0, 26).Lower() == _T("proxy-authenticate: basic "))
+ {
+ bUseBasic = true;
+ continue;
+ }
+ if (token.Mid(0, 27).Lower() == _T("proxy-authenticate: digest "))
+ {
+ authStr = token.Mid(27);
+ bUseDigest = true;
+ continue;
+ }
+ }
+
+ // read body
+ unsigned long int nBodyLength = 0;
+ char *pBody = new char[nContentLength];
+ while (nBodyLength < nContentLength) {
+ int read;
+
+ do_read = nContentLength - nBodyLength;
+
+ read = m_pSocket->Read(pBody + nBodyLength, do_read, error);
+ if (read == -1)
+ {
+ if (error != EAGAIN)
+ {
+ delete [] pBody;
+ m_proxyState = noconn;
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::close, error);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ }
+ else
+ m_can_read = false;
+ continue;
+ }
+ if (!read)
+ {
+ delete [] pBody;
+ m_proxyState = noconn;
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::close, ECONNABORTED);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ return;
+ }
+ if (m_pSendBuffer)
+ {
+ delete [] pBody;
+ m_proxyState = noconn;
+ m_pOwner->LogMessage(Debug_Warning, _T("Incoming data before requst fully sent"));
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::close, ECONNABORTED);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ return;
+ }
+ nBodyLength += read;
+ }
+
end = strchr(m_pRecvBuffer, '\r');
wxASSERT(end);
*end = 0;
wxString reply(m_pRecvBuffer, wxConvUTF8);
m_pOwner->LogMessage(Response, _("Proxy reply: %s"), reply.c_str());
 
+ if (reply.Left(12) == _T("HTTP/1.1 401") || reply.Left(12) == _T("HTTP/1.1 407") ||
+ reply.Left(12) == _T("HTTP/1.0 401") || reply.Left(12) == _T("HTTP/1.0 407"))
+ {
+ if (m_doAuth || m_user == _T(""))
+ {
+ delete [] pBody;
+ m_pOwner->LogMessage(Debug_Warning, _("already m_doAuth (%d), or m_user is empty (%s)"), m_doAuth, m_user.c_str());
+ m_proxyState = noconn;
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::close, ECONNRESET);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ return;
+ }
+
+ if (bUseDigest)
+ {
+ wxString realm = _T("");
+ wxString nonce = _T("");
+ wxString qop = _T("");
+ wxStringTokenizer tkz2(authStr, wxT(","));
+ while (tkz2.HasMoreTokens())
+ {
+ wxString token2 = tkz2.GetNextToken();
+ wxString name = _T("");
+ wxString value = _T("");
+ wxStringTokenizer tkz3(token2, wxT("="));
+ while (tkz3.HasMoreTokens())
+ {
+ wxString token3 = tkz3.GetNextToken();
+ if (name == _T(""))
+ {
+ name = token3.Lower().Trim(true).Trim(false);
+ continue;
+ }
+ if (token3.Left(1) == _T("\""))
+ value = token3.Mid(1, token3.Len()-2).Trim(true).Trim(false);
+ else
+ value = token3.Trim(true).Trim(false);
+ break;
+ }
+ if (name == _T("realm"))
+ realm = value;
+ else if (name == _T("nonce"))
+ nonce = value;
+ else if (name == _T("qop"))
+ qop = value;
+ }
+ if (realm == _T("") || nonce == _T(""))
+ {
+ delete [] pBody;
+ m_pOwner->LogMessage(Debug_Warning, _T("no realm or nonce"));
+ m_proxyState = conn;
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::connection, 0);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ return;
+ }
+ // HA1 = md5(username:realm:password)
+ // if qop = auth or no qop
+ // HA2 = md5(method:digestURI)
+ // if qop = auth-int
+ // HA2 = md5(method:digestURI:md5(body))
+ // if qop = auth or auth-int
+ // response = md5(HA1:nonce:nonceCount:clientNonce:qop:HA2)
+ // if no qop
+ // response = md5(HA1:nonce:HA2)
+ wxString ha1, ha2, response;
+ wxString s;
+ bool bAuth = false;
+ bool bAuthInt = false;
+
+ if (qop != _T(""))
+ {
+ wxStringTokenizer tkz4(qop, wxT(","));
+ while (tkz4.HasMoreTokens())
+ {
+ wxString token4 = tkz4.GetNextToken();
+ if (token4 == _T("auth"))
+ bAuth = true;
+ if (token4 == _T("auth-int"))
+ bAuthInt = true;
+ }
+ }
+
+ s = wxString::Format(_T("%s:%s:%s"), m_user.c_str(), realm.c_str(), m_pass.c_str());
+ const wxWX2MBbuf s_raw = s.mb_str(wxConvUTF8);
+ ha1 = ComputeMD5((const char *)s_raw, strlen(s_raw));
+
+ wxString uri = _T("");
+ if (qop == _T("") || bAuth)
+ {
+ uri = wxString::Format(_T("%s:%u"), m_host.c_str(), m_port);
+ s = wxString::Format(_T("CONNECT:%s"), uri.c_str());
+ const wxWX2MBbuf s_raw = s.mb_str(wxConvUTF8);
+ ha2 = ComputeMD5((const char *)s_raw, strlen(s_raw));
+ }
+ else if (bAuthInt)
+ {
+ uri = wxString::Format(_T("%s:%u"), m_host.c_str(), m_port);
+ wxString bodyMD5 = ComputeMD5((const char *)pBody, nBodyLength);
+ s = wxString::Format(_T("CONNECT:%s:%s"), uri.c_str(), bodyMD5.c_str());
+ const wxWX2MBbuf s_raw = s.mb_str(wxConvLocal);
+ ha2 = ComputeMD5((const char *)s_raw, strlen(s_raw));
+ }
+ else
+ {
+ delete [] pBody;
+ m_pOwner->LogMessage(Debug_Warning, _T("unknown qop: %s"), qop.c_str());
+ m_proxyState = conn;
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::connection, 0);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ return;
+ }
+
+ // we don't need body now
+ delete [] pBody;
+ if (bAuth || bAuthInt)
+ {
+ wxString clientNonce = wxString::Format(_T("%04x%04x"), GetRandomNumber(0, 0xffff), GetRandomNumber(0, 0xffff));
+
+ s = wxString::Format(_T("%s:%s:%s:%s:%s:%s"),
+ ha1.c_str(),
+ nonce.c_str(),
+ _T("00000001"),
+ clientNonce.c_str(),
+ bAuth ? _T("auth") : _T("auth-int"),
+ ha2.c_str());
+ const wxWX2MBbuf s_raw = s.mb_str(wxConvLocal);
+ response = ComputeMD5((const char *)s_raw, strlen(s_raw));
+
+ digestStr = wxString::Format(_T("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", qop=%s, nc=00000001, cnonce=\"%s\""),
+ m_user.c_str(),
+ realm.c_str(),
+ nonce.c_str(),
+ uri.c_str(),
+ response.c_str(),
+ bAuth ? _T("auth") : _T("auth-int"),
+ clientNonce.c_str());
+ }
+ else
+ {
+ s = wxString::Format(_T("%s:%s:%s"),
+ ha1.c_str(),
+ nonce.c_str(),
+ ha2.c_str());
+ const wxWX2MBbuf s_raw = s.mb_str(wxConvLocal);
+ response = ComputeMD5((const char *)s_raw, strlen(s_raw));
+
+ digestStr = wxString::Format(_T("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\""),
+ m_user.c_str(),
+ realm.c_str(),
+ nonce.c_str(),
+ uri.c_str(),
+ response.c_str());
+ }
+
+ m_doAuth = true;
+ m_recvBufferPos = 0;
+
+ const wxWX2MBbuf host_raw = m_host.mb_str(wxConvUTF8);
+ const wxWX2MBbuf digest_raw = digestStr.mb_str(wxConvLocal);
+
+ int challenge_len = digestStr.Len();
+
+ m_pOwner->LogMessage(Status, _("Sending CONNECT with Digest Authorization"));
+
+ // Bit oversized, but be on the safe side
+ m_pSendBuffer = new char[70 + m_host.Len() * 2 + 2*5 + challenge_len * 2 + 23 + 40];
+
+ m_sendBufferLen = sprintf(m_pSendBuffer, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nProxy-Authorization: %s\r\nUser-Agent: FileZilla\r\n\r\n",
+ (const char*)host_raw, m_port,
+ (const char*)host_raw, m_port,
+ (const char*)digest_raw);
+
+ wxString host = m_pSocket->GetPeerIP();
+ unsigned int port = m_pSocket->GetRemotePort(error);
+
+ m_pSocket->Close();
+ int res = m_pSocket->Connect(host, port);
+ // Treat success same as EINPROGRESS, we wait for connect notification in any case
+ if (res && res != EINPROGRESS)
+ {
+ m_pOwner->LogMessage(Debug_Warning, _T("connect error?"));
+ m_proxyState = conn;
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::connection, 0);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ return;
+ }
+ return;
+ }
+
+ if (bUseBasic)
+ {
+ // we don't need body for this
+ delete [] pBody;
+ m_doAuth = true;
+ m_recvBufferPos = 0;
+
+#if wxUSE_UNICODE
+ wxWX2MBbuf challenge;
+#else
+ const wxWX2MBbuf challenge;
+#endif
+ const wxWX2MBbuf host_raw = m_host.mb_str(wxConvUTF8);
+ int challenge_len;
+
+ challenge = base64encode(m_user + _T(":") + m_pass).mb_str(wxConvUTF8);
+ challenge_len = strlen(challenge);
+
+ m_pOwner->LogMessage(Status, _("Sending CONNECT with Basic Authorization"));
+ // Bit oversized, but be on the safe side
+ m_pSendBuffer = new char[70 + strlen(host_raw) * 2 + 2*5 + challenge_len + 23];
+
+ m_sendBufferLen = sprintf(m_pSendBuffer, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nProxy-Authorization: Basic %s\r\nUser-Agent: FileZilla\r\n\r\n",
+ (const char*)host_raw, m_port,
+ (const char*)host_raw, m_port,
+ (const char*)challenge);
+
+ wxString host = m_pSocket->GetPeerIP();
+ unsigned int port = m_pSocket->GetRemotePort(error);
+
+ m_pSocket->Close();
+ int res = m_pSocket->Connect(host, port);
+ // Treat success same as EINPROGRESS, we wait for connect notification in any case
+ if (res && res != EINPROGRESS)
+ {
+ m_pOwner->LogMessage(Debug_Warning, _T("connect error?"));
+ m_proxyState = conn;
+ CSocketEvent *evt = new CSocketEvent(m_pEvtHandler, this, CSocketEvent::connection, 0);
+ CSocketEventDispatcher::Get().SendEvent(evt);
+ return;
+ }
+ return;
+ }
+ // other authentication method?
+ }
+ delete [] pBody;
+
if (reply.Left(10) != _T("HTTP/1.1 2") && reply.Left(10) != _T("HTTP/1.0 2"))
{
m_proxyState = noconn;
diff --strip-trailing-cr -Nur a/src/engine/proxy.h b/src/engine/proxy.h
--- a/src/engine/proxy.h 2013-01-28 17:04:35.657440800 +0800
+++ b/src/engine/proxy.h 2013-01-28 17:04:44.293190900 +0800
@@ -65,6 +65,8 @@
int m_recvBufferPos;
int m_recvBufferLen;
 
+ bool m_doAuth;
+
void OnSocketEvent(CSocketEvent& event);
void OnReceive();
void OnSend();

這個 patch 也有送給作者.... 所以我就先不放我自己編的檔案了, 有需要就自己編譯看看或等作者納入這個 patch 吧. (如果作者不接受, 就等下一版我再放入 filezillapv 中吧)

Del.icio.us Furl HEMiDEMi Technorati MyShare
迴響
暱稱:
標題:
個人網頁:
電子郵件:
迴響

  

Bad Behavior 已經阻擋了 91 個過去 7 天試圖闖關的垃圾迴響與引用。
Power by LifeType. Template design by JamesHuang. Valid XHTML and CSS