Open
Description
The following patch, against the 0.3.0-alpha4 release, adds support for NTLM proxy authentication on Windows. I guess this probably isn't quite right for inclusion as-is, but I can change it round if you provide guidance on how you would like it structured.
Patch for connection.hpp:
--- WebSocketPP/websocketpp-drop/websocketpp/transport/asio/connection.hpp 2013-12-06 08:18:29.415002400 +0000
+++ ThirdParty/WebSocketPP/include/websocketpp/transport/asio/connection.hpp 2014-03-07 13:55:22.297122400 +0000
@@ -38,6 +38,7 @@
#include <websocketpp/base64/base64.hpp>
#include <websocketpp/error.hpp>
+#include <websocketpp/impl/windows_authenticate.hpp>
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
@@ -361,6 +369,8 @@
m_proxy_data->req.set_uri(authority);
m_proxy_data->req.replace_header("Host",authority);
+ m_proxy_data->req.replace_header("Connection","keep-alive");
+ m_proxy_data->req.replace_header("Proxy-Connection","keep-alive");
return lib::error_code();
}
@@ -502,12 +512,12 @@
// Set a timer so we don't wait forever for the proxy to respond
m_proxy_data->timer = this->set_timer(
m_proxy_data->timeout_proxy,
- lib::bind(
+ m_strand->wrap(lib::bind(
&type::handle_proxy_timeout,
get_shared(),
callback,
lib::placeholders::_1
- )
+ ))
);
// Send proxy request
@@ -586,7 +596,7 @@
m_proxy_data->read_buf,
"\r\n\r\n",
m_strand->wrap(lib::bind(
- &type::handle_proxy_read,
+ &type::handle_proxy_read_headers,
get_shared(),
callback,
lib::placeholders::_1,
@@ -595,80 +605,200 @@
);
}
- void handle_proxy_read(init_handler callback, const
+ void handle_proxy_read_headers(init_handler callback, const
boost::system::error_code& ec, size_t bytes_transferred)
{
if (m_alog.static_test(log::alevel::devel)) {
- m_alog.write(log::alevel::devel,"asio connection handle_proxy_read");
+ m_alog.write(log::alevel::devel,"asio connection handle_proxy_read_headers");
}
// Timer expired or the operation was aborted for some reason.
// Whatever aborted it will be issuing the callback so we are safe to
// return
- if (ec == boost::asio::error::operation_aborted ||
+ if (ec == boost::asio::error::operation_aborted ||
m_proxy_data->timer->expires_from_now().is_negative())
{
m_elog.write(log::elevel::devel,"read operation aborted");
return;
}
+
+ if (ec) {
+ m_elog.write(log::elevel::info,
+ "asio handle_proxy_read_headers error: "+ec.message());
+ m_proxy_data->timer->cancel();
+ callback(make_error_code(error::pass_through));
+ return;
+ }
- // At this point there is no need to wait for the timer anymore
- m_proxy_data->timer->cancel();
+ if (!m_proxy_data) {
+ m_elog.write(log::elevel::library,
+ "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read_headers");
+ m_proxy_data->timer->cancel();
+ callback(make_error_code(error::general));
+ return;
+ }
+
+ std::istream input(&m_proxy_data->read_buf);
+ m_proxy_data->res.consume(input);
+
+ if (!m_proxy_data->res.headers_ready()) {
+ // we read until the headers were done in theory but apparently
+ // they aren't. Internal endpoint error.
+ m_proxy_data->timer->cancel();
+ callback(make_error_code(error::general));
+ return;
+ }
+
+ // Read the body of the proxy response, if any
+ int content_length = atoi(m_proxy_data->res.get_header("Content-Length").c_str());
+ if (content_length > 0) {
+ boost::asio::async_read(
+ socket_con_type::get_next_layer(),
+ m_proxy_data->read_buf,
+ boost::asio::transfer_at_least(1),
+ m_strand->wrap(lib::bind(
+ &type::handle_proxy_read_body,
+ get_shared(),
+ callback,
+ lib::placeholders::_1,
+ lib::placeholders::_2
+ ))
+ );
+ } else {
+ handle_proxy_read_body(callback, ec, 0);
+ }
+ }
+
+ void handle_proxy_read_body(init_handler callback, const
+ boost::system::error_code& ec, size_t bytes_transferred)
+ {
+ if (m_alog.static_test(log::alevel::devel)) {
+ m_alog.write(log::alevel::devel,"asio connection handle_proxy_read_body");
+ }
+ // Timer expired or the operation was aborted for some reason.
+ // Whatever aborted it will be issuing the callback so we are safe to
+ // return
+ if (ec == boost::asio::error::operation_aborted ||
+ m_proxy_data->timer->expires_from_now().is_negative())
+ {
+ m_elog.write(log::elevel::devel,"read operation aborted");
+ return;
+ }
+
if (ec) {
m_elog.write(log::elevel::info,
- "asio handle_proxy_read error: "+ec.message());
- callback(make_error_code(error::pass_through));
- } else {
- if (!m_proxy_data) {
- m_elog.write(log::elevel::library,
- "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read");
- callback(make_error_code(error::general));
- return;
- }
+ "asio handle_proxy_read_body error: "+ec.message());
+ m_proxy_data->timer->cancel();
+ callback(make_error_code(error::pass_through));
+ return;
+ }
+ if (bytes_transferred > 0) {
std::istream input(&m_proxy_data->read_buf);
-
m_proxy_data->res.consume(input);
-
- if (!m_proxy_data->res.headers_ready()) {
- // we read until the headers were done in theory but apparently
- // they aren't. Internal endpoint error.
- callback(make_error_code(error::general));
+ if (!m_proxy_data->res.ready()) {
+ // Read more of the proxy response
+ boost::asio::async_read(
+ socket_con_type::get_next_layer(),
+ m_proxy_data->read_buf,
+ boost::asio::transfer_at_least(1),
+ m_strand->wrap(lib::bind(
+ &type::handle_proxy_read_body,
+ get_shared(),
+ callback,
+ lib::placeholders::_1,
+ lib::placeholders::_2
+ ))
+ );
return;
}
+ }
+
+ // At this point there is no need to wait for the timer anymore
+ m_proxy_data->timer->cancel();
- m_alog.write(log::alevel::devel,m_proxy_data->res.raw());
+ m_alog.write(log::alevel::devel,m_proxy_data->res.raw());
- if (m_proxy_data->res.get_status_code() != http::status_code::ok) {
- // got an error response back
- // TODO: expose this error in a programmatically accessible way?
- // if so, see below for an option on how to do this.
- std::stringstream s;
- s << "Proxy connection error: "
- << m_proxy_data->res.get_status_code()
- << " ("
- << m_proxy_data->res.get_status_msg()
- << ")";
- m_elog.write(log::elevel::info,s.str());
- callback(make_error_code(error::proxy_failed));
+ http::status_code::value status_code = m_proxy_data->res.get_status_code();
+ if (status_code == http::status_code::proxy_authentication_required) {
+ if (proxy_authenticate(callback)) {
return;
}
+ }
+ if (status_code != http::status_code::ok) {
+ // got an error response back
+ // TODO: expose this error in a programmatically accessible way?
+ // if so, see below for an option on how to do this.
+ std::stringstream s;
+ s << "Proxy connection error: "
+ << m_proxy_data->res.get_status_code()
+ << " ("
+ << m_proxy_data->res.get_status_msg()
+ << ")";
+ m_elog.write(log::elevel::info,s.str());
+ callback(make_error_code(error::proxy_failed));
+ return;
+ }
+
+ // we have successfully established a connection to the proxy, now
+ // we can continue and the proxy will transparently forward the
+ // WebSocket connection.
+
+ // TODO: decide if we want an on_proxy callback that would allow
+ // access to the proxy response.
+
+ // free the proxy buffers and req/res objects as they aren't needed
+ // anymore
+ m_proxy_data.reset();
- // we have successfully established a connection to the proxy, now
- // we can continue and the proxy will transparently forward the
- // WebSocket connection.
-
- // TODO: decide if we want an on_proxy callback that would allow
- // access to the proxy response.
-
- // free the proxy buffers and req/res objects as they aren't needed
- // anymore
- m_proxy_data.reset();
+ // Continue with post proxy initialization
+ post_init(callback);
+ }
- // Continue with post proxy initialization
- post_init(callback);
+ void proxy_authenticate_ntlm(init_handler callback, const std::string& challenge) {
+ lib::error_code ec;
+ if (challenge.empty()) {
+ m_proxy_data->authenticate.reset(new security::authenticate());
+ ec = m_proxy_data->authenticate->init("NTLM");
+ if (ec) {
+ callback(ec);
+ return;
+ }
}
+
+ std::string response = m_proxy_data->authenticate->challenge_response(base64_decode(challenge),ec);
+ if (ec) {
+ callback(ec);
+ return;
+ }
+ m_proxy_data->res = response_type();
+ m_proxy_data->req.replace_header("Proxy-Authorization","NTLM "+base64_encode(response));
+ proxy_write(callback);
+ }
+
+ bool proxy_authenticate(init_handler callback) {
+ std::string auth = m_proxy_data->res.get_header("Proxy-Authenticate");
+
+ m_alog.write(log::alevel::connect,"Proxy-Authenticate: "+auth);
+
+ // Look for a supported authentication protocol
+ std::string::iterator current = auth.begin(), end = auth.end();
+ while (current != end)
+ {
+ std::string::iterator current_end = std::find(current,end,' ');
+ std::string protocol(current,current_end);
+ current = current_end;
+ if (current != end) {
+ current++;
+ }
+
+ if (protocol == "NTLM") {
+ proxy_authenticate_ntlm(callback,std::string(current,end));
+ return true;
+ }
+ }
+ return false;
}
/// read at least num_bytes bytes into buf and then call handler.
@@ -913,6 +1043,7 @@
boost::asio::streambuf read_buf;
long timeout_proxy;
timer_ptr timer;
+ lib::shared_ptr<security::authenticate> authenticate;
};
std::string m_proxy;
New file to implement the NTLM challenge-response, which I put in impl/windows_authenticate.hpp:
#ifndef WEBSOCKETPP_IMPL_WIN_AUTHENTICATE_HPP
#define WEBSOCKETPP_IMPL_WIN_AUTHENTICATE_HPP
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef SECURITY_WIN32
#define SECURITY_WIN32
#endif
#include <security.h>
#pragma comment(lib,"secur32.lib")
#include <boost/system/error_code.hpp>
namespace websocketpp {
namespace security {
class authenticate {
CredHandle m_credentials;
CtxtHandle m_context;
public:
authenticate() {
::ZeroMemory(&m_credentials,sizeof m_credentials);
::ZeroMemory(&m_context,sizeof m_context);
}
~authenticate() {
::FreeCredentialsHandle(&m_credentials);
::DeleteSecurityContext(&m_context);
}
lib::error_code init(const std::string& package) {
TimeStamp lifetime;
SECURITY_STATUS status = ::AcquireCredentialsHandleA(0,const_cast<LPSTR>(package.c_str()),
SECPKG_CRED_OUTBOUND,0,0,0,0,&m_credentials,&lifetime);
return lib::error_code(status,boost::system::get_system_category());
}
std::string challenge_response(const std::string& challenge, lib::error_code& ec) {
SecBuffer inBuffer[1] = {{ challenge.size(),SECBUFFER_TOKEN,const_cast<char*>(challenge.c_str()) }};
SecBufferDesc inBuffers = { SECBUFFER_VERSION,1,inBuffer };
SecBuffer outBuffer[1] = {{ 0,SECBUFFER_TOKEN,0 }};
SecBufferDesc outBuffers = { SECBUFFER_VERSION,1,outBuffer };
bool first = challenge.empty();
ULONG attrs = 0;
SECURITY_STATUS status = ::InitializeSecurityContext(
&m_credentials,
first ? 0 : &m_context,
0,ISC_REQ_ALLOCATE_MEMORY,0,SECURITY_NETWORK_DREP,
first ? 0 : &inBuffers,
0,&m_context,&outBuffers,&attrs,NULL);
if ((status == SEC_E_OK) || (status == SEC_I_CONTINUE_NEEDED)) {
std::string response(static_cast<char*>(outBuffer[0].pvBuffer),outBuffer[0].cbBuffer);
::FreeContextBuffer(outBuffer[0].pvBuffer);
return response;
} else {
ec = lib::error_code(status,boost::system::get_system_category());
return std::string();
}
}
};
} // namespace security
} // namespace websocketpp
#endif // WEBSOCKETPP_IMPL_WIN_AUTHENTICATE_HPP