diff --git a/src/discord/Frontend.hpp b/src/discord/Frontend.hpp index e8392b5..d99d45e 100644 --- a/src/discord/Frontend.hpp +++ b/src/discord/Frontend.hpp @@ -64,8 +64,8 @@ class Frontend // Called by WebSocketClient, dispatches to relevant places including DiscordInstance virtual void OnWebsocketMessage(int gatewayID, const std::string& payload) = 0; - virtual void OnWebsocketClose(int gatewayID, int errorCode) = 0; - virtual void OnWebsocketFail(int gatewayID, int errorCode) = 0; + virtual void OnWebsocketClose(int gatewayID, int errorCode, const std::string& message) = 0; + virtual void OnWebsocketFail(int gatewayID, int errorCode, const std::string& message, bool isTLSError) = 0; // Heartbeat interval virtual void SetHeartbeatInterval(int timeMs) = 0; diff --git a/src/discord/LocalSettings.cpp b/src/discord/LocalSettings.cpp index aa015a6..f1b92a3 100644 --- a/src/discord/LocalSettings.cpp +++ b/src/discord/LocalSettings.cpp @@ -71,6 +71,9 @@ bool LocalSettings::Load() if (j.contains("StartMaximized")) m_bStartMaximized = j["StartMaximized"]; + if (j.contains("EnableTLSVerification")) + m_bEnableTLSVerification = j["EnableTLSVerification"]; + if (m_bSaveWindowSize) { if (j.contains("WindowWidth")) @@ -112,6 +115,7 @@ bool LocalSettings::Save() j["StartMaximized"] = m_bStartMaximized; j["SaveWindowSize"] = m_bSaveWindowSize; j["CheckUpdates"] = m_bCheckUpdates; + j["EnableTLSVerification"] = m_bEnableTLSVerification; j["RemindUpdateCheckOn"] = (long long)(m_remindUpdatesOn); if (m_bSaveWindowSize) { j["WindowWidth"] = m_width; diff --git a/src/discord/LocalSettings.hpp b/src/discord/LocalSettings.hpp index e57dcce..2ad40f8 100644 --- a/src/discord/LocalSettings.hpp +++ b/src/discord/LocalSettings.hpp @@ -85,6 +85,12 @@ class LocalSettings bool AskToCheckUpdates() const { return m_bAskToCheckUpdates; } + bool EnableTLSVerification() const { + return m_bEnableTLSVerification; + } + void SetEnableTLSVerification(bool b) { + m_bEnableTLSVerification = b; + } void StopUpdateCheckTemporarily(); private: @@ -99,6 +105,7 @@ class LocalSettings bool m_bIsFirstStart = false; bool m_bCheckUpdates = false; bool m_bAskToCheckUpdates = false; + bool m_bEnableTLSVerification = true; time_t m_remindUpdatesOn = 0; int m_width = 1000; int m_height = 700; diff --git a/src/discord/WebsocketClient.cpp b/src/discord/WebsocketClient.cpp index a1a85a0..1f15258 100644 --- a/src/discord/WebsocketClient.cpp +++ b/src/discord/WebsocketClient.cpp @@ -1,9 +1,15 @@ #include "WebsocketClient.hpp" #include "Frontend.hpp" #include "Util.hpp" +#include "LocalSettings.hpp" + +#include static WebsocketClient g_WSCSingleton; +// Doing the same thing as cpp-httplib +void LoadSystemCertsOnWindows(asio::ssl::context& ctx); + WebsocketClient* GetWebsocketClient() { return &g_WSCSingleton; @@ -25,8 +31,30 @@ void WSConnectionMetadata::OnFail(WSClient* c, websocketpp::connection_hdl hdl) m_server = pConn->get_response_header("Server"); m_errorReason = pConn->get_ec().message(); - DbgPrintF("Failed to connect: %s (server %s)\n", m_errorReason.c_str(), m_server.c_str()); - GetFrontend()->OnWebsocketFail(m_id, pConn->get_remote_close_code()); + auto xportEc = pConn->get_transport_ec(); + + DbgPrintF("Failed to connect: %s (server '%s'). Transport error code 0x%x, message %s\n", + m_errorReason.c_str(), m_server.c_str(), xportEc.value(), xportEc.message().c_str()); + + std::string guiMessage = m_errorReason; + if (!m_server.empty()) + guiMessage += " (server: " + m_server + ")"; + if (xportEc) + guiMessage += " (transport error " + xportEc.message() + ")"; + + namespace SocketErrors = websocketpp::transport::asio::socket::error; + bool isTLSError = false; + switch (pConn->get_ec().value()) { + case SocketErrors::missing_tls_init_handler: + case SocketErrors::tls_failed_sni_hostname: + case SocketErrors::tls_handshake_timeout: + case SocketErrors::tls_handshake_failed: + case SocketErrors::invalid_tls_context: + case SocketErrors::security: + isTLSError = true; + } + + GetFrontend()->OnWebsocketFail(m_id, pConn->get_ec().value(), guiMessage, isTLSError); } void WSConnectionMetadata::OnClose(WSClient* c, websocketpp::connection_hdl hdl) @@ -43,7 +71,7 @@ void WSConnectionMetadata::OnClose(WSClient* c, websocketpp::connection_hdl hdl) m_errorReason = s.str(); - GetFrontend()->OnWebsocketClose(m_id, pConn->get_remote_close_code()); + GetFrontend()->OnWebsocketClose(m_id, pConn->get_remote_close_code(), s.str()); } void WSConnectionMetadata::OnMessage(websocketpp::connection_hdl hdl, WSClient::message_ptr msg) @@ -72,16 +100,22 @@ WebsocketClient::~WebsocketClient() AsioSslContextSharedPtr WebsocketClient::HandleTLSInit(websocketpp::connection_hdl hdl) { // establishes a SSL connection - AsioSslContextSharedPtr ctx = std::make_shared(AsioSslContext::sslv23); + AsioSslContextSharedPtr ctx = std::make_shared(AsioSslContext::tls); try { ctx->set_options( websocketpp::lib::asio::ssl::context::default_workarounds | - websocketpp::lib::asio::ssl::context::no_sslv2 | - websocketpp::lib::asio::ssl::context::no_sslv3 | websocketpp::lib::asio::ssl::context::single_dh_use ); + + if (GetLocalSettings()->EnableTLSVerification()) + { + ctx->set_default_verify_paths(); +#ifdef _WIN32 + LoadSystemCertsOnWindows(*ctx); +#endif + } } catch (std::exception& e) { @@ -91,6 +125,20 @@ AsioSslContextSharedPtr WebsocketClient::HandleTLSInit(websocketpp::connection_h return ctx; } +void WebsocketClient::HandleSocketInit(websocketpp::connection_hdl hdl, AsioSocketType& socket) +{ + WSClient::connection_ptr pConn = m_endpoint.get_con_from_hdl(hdl); + + if (GetLocalSettings()->EnableTLSVerification()) + { + socket.set_verify_mode(websocketpp::lib::asio::ssl::verify_peer); + + if (!SSL_set_tlsext_host_name(reinterpret_cast(socket.native_handle()), "gateway.discord.gg")) { + DbgPrintF("Failed to set SNI host name... this might go awry"); + } + } +} + void WebsocketClient::Init() { m_endpoint.clear_access_channels(websocketpp::log::alevel::all); @@ -102,6 +150,12 @@ void WebsocketClient::Init() this, websocketpp::lib::placeholders::_1 )); + m_endpoint.set_socket_init_handler(websocketpp::lib::bind( + &WebsocketClient::HandleSocketInit, + this, + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2 + )); m_thread.reset(new websocketpp::lib::thread(&WSClient::run, &m_endpoint)); m_bKilled = false; } diff --git a/src/discord/WebsocketClient.hpp b/src/discord/WebsocketClient.hpp index 02f9029..9c72ec3 100644 --- a/src/discord/WebsocketClient.hpp +++ b/src/discord/WebsocketClient.hpp @@ -28,6 +28,7 @@ typedef websocketpp::client WSClient; typedef websocketpp::lib::shared_ptr WSThreadSharedPtr; typedef websocketpp::lib::asio::ssl::context AsioSslContext; typedef websocketpp::lib::shared_ptr AsioSslContextSharedPtr; +typedef websocketpp::transport::asio::tls_socket::connection::socket_type AsioSocketType; class WSConnectionMetadata { @@ -54,7 +55,7 @@ class WSConnectionMetadata void OnFail(WSClient* c, websocketpp::connection_hdl hdl); void OnClose(WSClient* c, websocketpp::connection_hdl hdl); void OnMessage(websocketpp::connection_hdl hdl, WSClient::message_ptr msg); - + websocketpp::connection_hdl GetHDL() const { return m_hdl; @@ -118,6 +119,9 @@ class WebsocketClient // Handle TLS initialization. AsioSslContextSharedPtr HandleTLSInit(websocketpp::connection_hdl hdl); + + // Handle socket initialization. + void HandleSocketInit(websocketpp::connection_hdl hdl, AsioSocketType& socketType); }; WebsocketClient* GetWebsocketClient(); diff --git a/src/resource.h b/src/resource.h index 7e431cb..d0af1c8 100644 --- a/src/resource.h +++ b/src/resource.h @@ -273,6 +273,11 @@ #define IDC_UPDATE 867 #define IDC_ICON_WARN_CONNECT 868 #define IDC_ICON_WARN_WEBSOCKETSTUFF 869 +#define IDC_ICON_WARN_WEBSOCKETSTUFF2 870 +#define IDC_CHECK1 871 +#define IDC_ENABLE_TLS_CHECKS 871 +#define IDC_CHECK2 872 +#define IDC_CHECK_UPDATES 872 #define ID_FILE_PREFERENCES 1001 #define ID_FILE_STOPALLSPEECH 1002 #define ID_FILE_EXIT 1003 @@ -351,7 +356,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 71 #define _APS_NEXT_COMMAND_VALUE 1069 -#define _APS_NEXT_CONTROL_VALUE 869 +#define _APS_NEXT_CONTROL_VALUE 873 #define _APS_NEXT_SYMED_VALUE 40000 #endif #endif diff --git a/src/resource.rc b/src/resource.rc index 21b9bb7..eb54712 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -398,7 +398,7 @@ BEGIN CONTROL "",IDC_STATIC_PROFILE_IMAGE,"Static",SS_BITMAP | SS_CENTERIMAGE,12,18,24,22 END -IDD_DIALOG_APPEARANCE DIALOGEX 0, 0, 260, 200 +IDD_DIALOG_APPEARANCE DIALOGEX 0, 0, 260, 242 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD FONT 8, "MS Shell Dlg", 400, 0, 0x0 BEGIN @@ -527,21 +527,30 @@ BEGIN GROUPBOX "Search Results",IDC_QUICK_GROUP,7,38,294,99 END -IDD_DIALOG_CONNECTION DIALOGEX 0, 0, 260, 200 +IDD_DIALOG_CONNECTION DIALOGEX 0, 0, 260, 242 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD FONT 8, "MS Shell Dlg", 400, 0, 0x0 BEGIN - GROUPBOX "Discord API && CDN",IDC_STATIC,6,7,245,133 + GROUPBOX "Discord API && CDN",IDC_STATIC,6,7,245,128 LTEXT "Discord API URL:",IDC_STATIC,12,20,55,8 LTEXT "Discord CDN URL:",IDC_STATIC,12,35,58,8 EDITTEXT IDC_EDIT_DISCORDAPI,72,17,174,14,ES_AUTOHSCROLL EDITTEXT IDC_EDIT_DISCORDCDN,72,33,174,14,ES_AUTOHSCROLL - PUSHBUTTON "Revert to official Discord backend",IDC_REVERTTODEFAULT,11,120,118,14 - PUSHBUTTON "Update",IDC_UPDATE,197,120,50,14 - ICON IDI_WARNING_IC,IDC_ICON_WARN_CONNECT,11,55,21,20,SS_REALSIZEIMAGE + PUSHBUTTON "Revert to official Discord backend",IDC_REVERTTODEFAULT,11,116,118,14 + PUSHBUTTON "Update",IDC_UPDATE,197,116,50,14 + ICON IDI_WARNING_IC,IDC_ICON_WARN_CONNECT,11,55,20,20,SS_REALSIZEIMAGE LTEXT "For security reasons, when you update the URLs that Discord Messenger uses to interface with Discord's backend, you will be logged out. (your token is forgotten but not revoked)",IDC_STATIC,30,54,214,31 - ICON IDI_WARNING_IC,IDC_ICON_WARN_WEBSOCKETSTUFF,12,87,21,20,SS_REALSIZEIMAGE - LTEXT "Wondering where the websocket gateway URL is located? Well, Discord Messenger issues a request to API_URL/gateway, which tells it the websocket URL to use.",IDC_STATIC,30,87,214,31 + ICON IDI_WARNING_IC,IDC_ICON_WARN_WEBSOCKETSTUFF,12,87,20,20,SS_REALSIZEIMAGE + LTEXT "Wondering where the websocket gateway URL is located? Well, Discord Messenger issues a request to API_URL/gateway, which tells it the websocket URL to use.",IDC_STATIC,30,87,214,29 + GROUPBOX "Security Settings",IDC_STATIC,6,139,245,48 + ICON IDI_WARNING_IC,IDC_ICON_WARN_WEBSOCKETSTUFF2,12,166,20,20,SS_REALSIZEIMAGE + LTEXT "WARNING: Disabling TLS certificate verification could expose you to MITM (man-in-the-middle) attacks via certificate spoofing!",IDC_STATIC,30,165,214,19 + CONTROL "Enable TLS certificate verification",IDC_ENABLE_TLS_CHECKS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,151,230,10 + GROUPBOX "Check for Updates",IDC_STATIC,6,194,245,40 + CONTROL "Check for updates using the GitHub API",IDC_CHECK_UPDATES, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,204,232,10 + LTEXT "iProgramInCpp cannot see your IP address via the update service.",IDC_STATIC,12,219,231,10 END @@ -643,7 +652,7 @@ BEGIN LEFTMARGIN, 6 RIGHTMARGIN, 251 TOPMARGIN, 7 - BOTTOMMARGIN, 192 + BOTTOMMARGIN, 234 END END #endif // APSTUDIO_INVOKED diff --git a/src/windows/Frontend_Win32.cpp b/src/windows/Frontend_Win32.cpp index 44adda7..85a9362 100644 --- a/src/windows/Frontend_Win32.cpp +++ b/src/windows/Frontend_Win32.cpp @@ -296,7 +296,7 @@ void Frontend_Win32::OnWebsocketMessage(int gatewayID, const std::string& payloa SendMessage(g_Hwnd, WM_WEBSOCKETMESSAGE, 0, (LPARAM) pParm); } -void Frontend_Win32::OnWebsocketClose(int gatewayID, int errorCode) +void Frontend_Win32::OnWebsocketClose(int gatewayID, int errorCode, const std::string& message) { if (GetDiscordInstance()->GetGatewayID() == gatewayID) GetDiscordInstance()->GatewayClosed(errorCode); @@ -306,9 +306,41 @@ void Frontend_Win32::OnWebsocketClose(int gatewayID, int errorCode) DbgPrintW("Unknown gateway connection %d closed: %d", gatewayID, errorCode); } -void Frontend_Win32::OnWebsocketFail(int gatewayID, int errorCode) +void Frontend_Win32::OnWebsocketFail(int gatewayID, int errorCode, const std::string& message, bool isTLSError) { - DbgPrintW("TODO: Frontend_Win32::OnWebsocketFail: GatewayID %d ErrorCode %d", gatewayID, errorCode); + static TCHAR buffer[8192]; + LPTSTR msg = ConvertCppStringToTString(message); + WAsnprintf( + buffer, + _countof(buffer), + TEXT("ERROR: Could not connect to the websocket gateway.\nConnection was closed with code: %d\n\nMessage: %s"), + errorCode, + msg + ); + + if (isTLSError) { + _tcscat( + buffer, + TEXT("WARNING: Your connection may not be private\n\nDiscord Messenger could not verify that it is connecting to a Websocket service ") + TEXT("required to use Discord Messenger in real time.") + TEXT("\n\nThis could be because:") + TEXT("\no Your computer does not trust the certificate that the gateway has declared because it is wrong,") + TEXT("\no Your computer does not trust the certificate that the gateway has declared because it doesn't trust the root certificate,") + TEXT("\no Your computer does not trust the certificate that the gateway has declared because your date and time are incorrect,") + TEXT("\no The website reported a protocol which the client is outdated for and cannot handle, or") + TEXT("\no SOMEONE IS EAVESDROPPING ON YOU RIGHT NOW! (man-in-the-middle attack)") + TEXT("\n\nIt is not recommended to proceed if you see this error. Please ensure you can connect to Discord using a ") + TEXT("capable device on the same network as this one. If you are able to connect, export the root certificate from ") + TEXT("discord.com on that device and import it into Windows, if the OS supports it.") + TEXT("\n\nYou may also disable SSL server verification, and then restart the application. However, you have to ") + TEXT("take into account the risks that this entails.") + ); + } + + free(msg); + + MessageBox(g_Hwnd, buffer, TmGetTString(IDS_PROGRAM_NAME), (isTLSError ? MB_ICONWARNING : MB_ICONERROR) | MB_OK); + SendMessage(g_Hwnd, WM_CONNECTERROR, 0, 0); } void Frontend_Win32::RequestQuit() diff --git a/src/windows/Frontend_Win32.hpp b/src/windows/Frontend_Win32.hpp index fb34255..5f9b0f3 100644 --- a/src/windows/Frontend_Win32.hpp +++ b/src/windows/Frontend_Win32.hpp @@ -45,8 +45,8 @@ class Frontend_Win32 : public Frontend void RefreshMembers(const std::set& members) override; void JumpToMessage(Snowflake messageInCurrentChannel) override; void OnWebsocketMessage(int gatewayID, const std::string& payload) override; - void OnWebsocketClose(int gatewayID, int errorCode) override; - void OnWebsocketFail(int gatewayID, int errorCode) override; + void OnWebsocketClose(int gatewayID, int errorCode, const std::string& message) override; + void OnWebsocketFail(int gatewayID, int errorCode, const std::string& message, bool isTLSError) override; void SetHeartbeatInterval(int timeMs) override; void LaunchURL(const std::string& url) override; void RegisterIcon(Snowflake sf, const std::string& avatarlnk) override; diff --git a/src/windows/Main.cpp b/src/windows/Main.cpp index e958bd5..1776179 100644 --- a/src/windows/Main.cpp +++ b/src/windows/Main.cpp @@ -347,6 +347,38 @@ void UpdateMainWindowTitle(HWND hWnd) free(tstr); } +int OnSSLError(const std::string& url) +{ + LPTSTR urlt = ConvertCppStringToTString(url); + static TCHAR buffer[8192]; + _tcscpy(buffer, TEXT("WARNING: Your connection may not be private\n\nDiscord Messenger could not verify that it is connecting to the following URL: ")); + _tcscat(buffer, urlt); + _tcscat(buffer, + TEXT("\n\nThis could be because:") + TEXT("\no Your computer does not trust the certificate that the service has declared because it is wrong,") + TEXT("\no Your computer does not trust the certificate that the service has declared because it doesn't trust the root certificate,") + TEXT("\no Your computer does not trust the certificate that the service has declared because your date and time are incorrect,") + TEXT("\no The website reported a protocol which the client is outdated for and cannot handle, or") + TEXT("\no SOMEONE IS EAVESDROPPING ON YOU RIGHT NOW! (man-in-the-middle attack)") + TEXT("\n\nIt is not recommended to proceed if you see this error. Please ensure you can connect to the URL using a ") + TEXT("capable device on the same network as this one. If you are able to connect, export the root certificate from ") + TEXT("that device and import it into Windows, so that you won't get this error again.") + TEXT("\n\nYou may also disable SSL server verification, and then restart the application. However, you have to ") + TEXT("take into account the risks that this entails.") + TEXT("\n\nUse one of the following buttons to determine how to proceed:") + TEXT("\no ABORT: Declare this HTTP request a failure.") + TEXT("\no RETRY: Retry this HTTP request. Keep in mind that you will need to restart the app if you have installed a new certificate.") + TEXT("\no IGNORE: This error and future SSL errors will be ignored. This option is RISKY because it could expose you to man-in-the-middle attacks. ") + TEXT("If you wish to enable SSL server verification again after clicking Ignore, you can go to the File > Preferences menu, Connection tab, and check ") + TEXT("the relevant checkbox.") + ); + free(urlt); + + size_t l = _tcslen(buffer); + + return MessageBox(g_Hwnd, buffer, TEXT("Discord Messenger - SECURITY ALERT"), MB_ABORTRETRYIGNORE | MB_ICONWARNING); +} + BOOL HandleCommand(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (LOWORD(wParam)) @@ -536,6 +568,18 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { + case WM_SSLERROR: + { + return OnSSLError(CutOutURLPath(std::string((const char*)lParam))); + } + case WM_FORCERESTART: + { + MessageBox(hWnd, TEXT("The application will now restart due to a change in the configuration."), TEXT("Setting Modification"), MB_OK | MB_ICONINFORMATION); + if (InSendMessage()) + ReplyMessage(0); + SendMessage(hWnd, WM_DESTROY, 0, 0); + return 0; + } case WM_UPDATEMESSAGELENGTH: { g_pStatusBar->UpdateCharacterCounter(int(lParam), MAX_MESSAGE_SIZE); @@ -884,6 +928,7 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) SendMessage(hWnd, WM_LOGINAGAIN, 0, 0); break; } + case WM_CONNECTERROR: case WM_CONNECTED: { g_pLoadingMessage->Hide(); break; diff --git a/src/windows/NetworkerThread.cpp b/src/windows/NetworkerThread.cpp index 71678d5..adec08b 100644 --- a/src/windows/NetworkerThread.cpp +++ b/src/windows/NetworkerThread.cpp @@ -1,10 +1,10 @@ #include "NetworkerThread.hpp" #include "WinUtils.hpp" +#include "WindowMessages.hpp" #include "../discord/DiscordRequest.hpp" +#include "../discord/LocalSettings.hpp" #include "../discord/Frontend.hpp" -#define CPPHTTPLIB_OPENSSL_SUPPORT - #ifndef __MINGW32__ #define __MINGW32__ // so that it doesn't use inet_pton #endif @@ -13,6 +13,9 @@ extern HWND g_Hwnd; +static NetworkerThread::nmutex g_sslErrorMutex; +static bool g_bQuittingFromSSLError; + int NetRequest::Priority() const { int prio = 0; @@ -55,11 +58,47 @@ int NetRequest::Priority() const return prio; } -void NetworkerThread::ProcessResult(NetRequest& req, const httplib::Result& res) +bool NetworkerThread::ProcessResult(NetRequest& req, const httplib::Result& res) { using namespace httplib; - if (!res) + if (res.error() == Error::SSLServerVerification) + { + g_sslErrorMutex.lock(); + if (g_bQuittingFromSSLError) { + // we're actually quitting. Ignore + g_sslErrorMutex.unlock(); + return false; + } + + int result = (int) SendMessage(g_Hwnd, WM_SSLERROR, 0, (LPARAM)req.url.c_str()); + + if (result == IDABORT) + { + // Declare it a failure + req.result = -1; + req.response = to_string(res.error()); + + g_sslErrorMutex.unlock(); + } + else if (result == IDIGNORE) + { + GetLocalSettings()->SetEnableTLSVerification(false); + PrepareQuit(); + SendMessage(g_Hwnd, WM_FORCERESTART, 0, 0); + + g_bQuittingFromSSLError = true; + g_sslErrorMutex.unlock(); + return false; + } + // return true to retry + else + { + g_sslErrorMutex.unlock(); + return true; + } + } + else if (!res) { req.result = -1; req.response = to_string(res.error()); @@ -76,6 +115,9 @@ void NetworkerThread::ProcessResult(NetRequest& req, const httplib::Result& res) // Call the handler function. // N.B. Don't return unless you're absolutely done with the request! req.pFunc(&req); + + // Return false to let the runner know that it shouldn't retry. + return false; } void NetworkerThread::IdleWait() @@ -108,7 +150,7 @@ void NetworkerThread::FulfillRequest(NetRequest& req) // Probably outdated certs. I mean, this would allow attackers to host // a self-instance of Discord to intercept packets, but this is fine // for now..... - client.enable_server_certificate_verification(false); + client.enable_server_certificate_verification(GetLocalSettings()->EnableTLSVerification()); // Follow redirects. Used by GitHub auto-update service client.set_follow_location(true); @@ -121,55 +163,60 @@ void NetworkerThread::FulfillRequest(NetRequest& req) headers.insert(std::make_pair("Authorization", req.authorization)); } - switch (req.type) + bool retry = false; + do { - // no default constructor for httplib::Result?? this SUCKS! - case NetRequest::POST: + switch (req.type) { - const Result res = client.Post(path, headers, req.params, "application/x-www-form-urlencoded"); - ProcessResult(req, res); - break; + // no default constructor for httplib::Result?? this SUCKS! + case NetRequest::POST: + { + const Result res = client.Post(path, headers, req.params, "application/x-www-form-urlencoded"); + retry = ProcessResult(req, res); + break; + } + case NetRequest::POST_JSON: + { + const Result res = client.Post(path, headers, req.params, "application/json"); + retry = ProcessResult(req, res); + break; + } + case NetRequest::PUT: + { + const Result res = client.Put(path, headers, req.params, "application/x-www-form-urlencoded"); + retry = ProcessResult(req, res); + break; + } + case NetRequest::PUT_OCTETS: + { + const Result res = client.Put(path, headers, (const char*) req.params_bytes.data(), req.params_bytes.size(), "application/octet-stream"); + retry = ProcessResult(req, res); + break; + } + case NetRequest::GET: + { + const Result res = client.Get(path, headers); + retry = ProcessResult(req, res); + break; + } + case NetRequest::PATCH: + { + const Result res = client.Patch(path, headers, req.params, "application/json"); + retry = ProcessResult(req, res); + break; + } + case NetRequest::DELETE_: + { + const Result res = client.Delete(path, headers, req.params, "application/json"); + retry = ProcessResult(req, res); + break; + } + default: + assert(!"Don't know how to handle that type of request!"); + break; } - case NetRequest::POST_JSON: - { - const Result res = client.Post(path, headers, req.params, "application/json"); - ProcessResult(req, res); - break; - } - case NetRequest::PUT: - { - const Result res = client.Put(path, headers, req.params, "application/x-www-form-urlencoded"); - ProcessResult(req, res); - break; - } - case NetRequest::PUT_OCTETS: - { - const Result res = client.Put(path, headers, (const char*) req.params_bytes.data(), req.params_bytes.size(), "application/octet-stream"); - ProcessResult(req, res); - break; - } - case NetRequest::GET: - { - const Result res = client.Get(path, headers); - ProcessResult(req, res); - break; - } - case NetRequest::PATCH: - { - const Result res = client.Patch(path, headers, req.params, "application/json"); - ProcessResult(req, res); - break; - } - case NetRequest::DELETE_: - { - const Result res = client.Delete(path, headers, req.params, "application/json"); - ProcessResult(req, res); - break; - } - default: - assert(!"Don't know how to handle that type of request!"); - break; } + while (retry); } void NetworkerThread::Run() @@ -243,8 +290,7 @@ void NetworkerThread::PrepareQuit() while (!m_requests.empty()) m_requests.pop(); - NetRequest rq(0, 0, 0, NetRequest::QUIT); - m_requests.push(rq); + m_requests.push(NetRequest(0, 0, 0, NetRequest::QUIT)); m_requestLock.unlock(); } diff --git a/src/windows/NetworkerThread.hpp b/src/windows/NetworkerThread.hpp index 29234f1..2ed87ce 100644 --- a/src/windows/NetworkerThread.hpp +++ b/src/windows/NetworkerThread.hpp @@ -32,20 +32,20 @@ struct NetworkResponse class NetworkerThread { public: -private: #ifdef MINGW_SPECIFIC_HACKS using nmutex = iprog::mutex; #else using nmutex = std::mutex; #endif +private: std::priority_queue m_requests; nmutex m_requestLock; HANDLE m_ThreadHandle; DWORD m_ThreadID; - void ProcessResult(NetRequest& req, const httplib::Result& res); + bool ProcessResult(NetRequest& req, const httplib::Result& res); void IdleWait(); diff --git a/src/windows/OptionsDialog.cpp b/src/windows/OptionsDialog.cpp index 563a780..24ff75a 100644 --- a/src/windows/OptionsDialog.cpp +++ b/src/windows/OptionsDialog.cpp @@ -173,6 +173,9 @@ void WINAPI OnChildDialogInit(HWND hwndDlg) SetDlgItemText(hwndDlg, IDC_EDIT_DISCORDAPI, tstrAPI); SetDlgItemText(hwndDlg, IDC_EDIT_DISCORDCDN, tstrCDN); + CheckDlgButton(hwndDlg, IDC_ENABLE_TLS_CHECKS, GetLocalSettings()->EnableTLSVerification() ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_CHECK_UPDATES, GetLocalSettings()->CheckUpdates() ? BST_CHECKED : BST_UNCHECKED); + free(tstrAPI); free(tstrCDN); @@ -299,6 +302,34 @@ INT_PTR CALLBACK ChildDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa { switch (wParam) { + case IDC_ENABLE_TLS_CHECKS: + { + bool state = IsDlgButtonChecked(hWnd, IDC_ENABLE_TLS_CHECKS); + if (!state) { + if (MessageBox( + hWnd, + TEXT("WARNING:\n\nYou are about to change the TLS certificate verification setting. This may expose you to MITM (man-in-the-middle) ") + TEXT("attacks. It is recommended you instead import the required certificates into Windows.\n\nAre you sure you want to do this? ") + TEXT("(you can re-enable it anytime in the preferences menu)"), + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONWARNING | MB_YESNO + ) != IDYES) + { + CheckDlgButton(hWnd, IDC_ENABLE_TLS_CHECKS, BST_CHECKED); + break; + } + } + + GetLocalSettings()->SetEnableTLSVerification(state); + break; + } + + case IDC_CHECK_UPDATES: + { + GetLocalSettings()->SetCheckUpdates(IsDlgButtonChecked(hWnd, IDC_CHECK_UPDATES)); + break; + } + case IDC_REVERTTODEFAULT: case IDC_UPDATE: { diff --git a/src/windows/WinUtils.cpp b/src/windows/WinUtils.cpp index 4328f49..5b37cb4 100644 --- a/src/windows/WinUtils.cpp +++ b/src/windows/WinUtils.cpp @@ -19,6 +19,8 @@ #include "../discord/Util.hpp" #endif +#include "../discord/WebsocketClient.hpp" + #define DEFAULT_DPI (96) extern HWND g_Hwnd; // main.hpp @@ -880,9 +882,41 @@ bool GetDataFromBitmap(HDC hdc, HBITMAP hbm, BYTE*& pBytes, int& width, int& hei return true; } +std::string CutOutURLPath(const std::string& url) +{ + const char* pData = url.c_str(); + + // All this is to cut out the part after the URL. (so https://discord.com/test -> https://discord.com) + int slashesToSkip = 1; + + const char* startLookUp = strstr(pData, "://"); + if (startLookUp) + slashesToSkip = 3; + else + startLookUp = pData; + + size_t i = 0; + for (; startLookUp[i] != '\0'; i++) { + if (startLookUp[i] == '/') + slashesToSkip--; + if (!slashesToSkip) + break; + } + + std::string actualUrl; + if (slashesToSkip != 0) { + actualUrl = std::string(pData); + } + else { + actualUrl = std::string(pData, i + (startLookUp - pData)); + } + + return actualUrl; +} + int HandleGestureMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, float mulDeltas) { - // N.B. If WINVER is less than Windows 7, then MWAS will take pn winuser's duties + // N.B. If WINVER is less than Windows 7, then MWAS will take on winuser's duties // of defining the structs and constants used. GESTUREINFO gi = { 0 }; gi.cbSize = sizeof(GESTUREINFO); @@ -919,3 +953,15 @@ int HandleGestureMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, flo (void)mulDeltas; return DefWindowProc(hWnd, uMsg, wParam, lParam); } + +#include + +void LoadSystemCertsOnWindows(asio::ssl::context& ctx) +{ + X509_STORE* store = X509_STORE_new(); + + // cpp-httplib + httplib::detail::load_system_certs_on_windows(store); + + SSL_CTX_set_cert_store(ctx.native_handle(), store); +} diff --git a/src/windows/WinUtils.hpp b/src/windows/WinUtils.hpp index e4ea33a..3aba3ee 100644 --- a/src/windows/WinUtils.hpp +++ b/src/windows/WinUtils.hpp @@ -16,6 +16,8 @@ #define WAsnprintf _snprintf #endif +#define CPPHTTPLIB_OPENSSL_SUPPORT + struct NetRequest; #if _WIN32_WINNT < 0x0600 @@ -75,6 +77,7 @@ void DrawLoadingBox(HDC hdc, RECT rect); void DrawErrorBox(HDC hdc, RECT rect); bool GetDataFromBitmap(HDC hdc, HBITMAP hbm, BYTE*& pBytes, int& width, int& height, int& bpp); int HandleGestureMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, float mulDeltas = 1.0f); +std::string CutOutURLPath(const std::string& url); #ifdef USE_DEBUG_PRINTS void DbgPrintW(const char* fmt, ...); diff --git a/src/windows/WindowMessages.hpp b/src/windows/WindowMessages.hpp index c072fbb..bb25456 100644 --- a/src/windows/WindowMessages.hpp +++ b/src/windows/WindowMessages.hpp @@ -51,4 +51,7 @@ enum eWmUserMsgs WM_STARTEDITING, // Snowflake[1] WM_UPDATETEXTSIZE, // used by the MessageEditor WM_UPDATEMESSAGELENGTH, + WM_SSLERROR, + WM_FORCERESTART, + WM_CONNECTERROR, };