From 3b206e11216b383fae0e860e0092f71301fd4425 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 30 Jun 2023 20:43:08 -0400 Subject: [PATCH] remote auth impl. up to QR code display --- .gitmodules | 3 + CMakeLists.txt | 18 +- src/abaddon.cpp | 13 ++ src/abaddon.hpp | 1 + src/discord/websocket.cpp | 5 +- src/remoteauth/remoteauthclient.cpp | 321 ++++++++++++++++++++++++++++ src/remoteauth/remoteauthclient.hpp | 64 ++++++ src/remoteauth/remoteauthdialog.cpp | 79 +++++++ src/remoteauth/remoteauthdialog.hpp | 23 ++ src/remoteauth/ssl.hpp | 31 +++ src/windows/mainwindow.cpp | 11 + src/windows/mainwindow.hpp | 4 + subprojects/qrcodegen | 1 + 13 files changed, 567 insertions(+), 7 deletions(-) create mode 100644 src/remoteauth/remoteauthclient.cpp create mode 100644 src/remoteauth/remoteauthclient.hpp create mode 100644 src/remoteauth/remoteauthdialog.cpp create mode 100644 src/remoteauth/remoteauthdialog.hpp create mode 100644 src/remoteauth/ssl.hpp create mode 160000 subprojects/qrcodegen diff --git a/.gitmodules b/.gitmodules index 27efb8e..aae3862 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "subprojects/miniaudio"] path = subprojects/miniaudio url = https://github.com/mackron/miniaudio +[submodule "subprojects/qrcodegen"] + path = subprojects/qrcodegen + url = https://github.com/nayuki/QR-Code-generator diff --git a/CMakeLists.txt b/CMakeLists.txt index de97c32..b85e6c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,12 @@ target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS}) target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS}) target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS}) +add_library(qrcodegen subprojects/qrcodegen/cpp/qrcodegen.hpp subprojects/qrcodegen/cpp/qrcodegen.cpp) +target_include_directories(qrcodegen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/subprojects/qrcodegen/cpp") +target_link_libraries(abaddon qrcodegen) + +target_include_directories(abaddon PUBLIC "subprojects/qrcodegen/cpp") + target_precompile_headers(abaddon PRIVATE src/abaddon.hpp src/util.hpp) if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR @@ -151,12 +157,12 @@ if (ENABLE_VOICE) target_link_libraries(abaddon ${CMAKE_DL_LIBS}) - if(APPLE) - target_link_libraries(abaddon "-framework CoreFoundation") - target_link_libraries(abaddon "-framework CoreAudio") - target_link_libraries(abaddon "-framework AudioToolbox") - endif() - + if (APPLE) + target_link_libraries(abaddon "-framework CoreFoundation") + target_link_libraries(abaddon "-framework CoreAudio") + target_link_libraries(abaddon "-framework AudioToolbox") + endif () + endif () if (${ENABLE_NOTIFICATION_SOUNDS}) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 92cc494..545a3c2 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -21,6 +21,7 @@ #include "windows/voicewindow.hpp" #include "startup.hpp" #include "notifications/notifications.hpp" +#include "remoteauth/remoteauthdialog.hpp" #ifdef WITH_LIBHANDY #include @@ -267,6 +268,7 @@ int Abaddon::StartGTK() { m_main_window->signal_action_connect().connect(sigc::mem_fun(*this, &Abaddon::ActionConnect)); m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect)); m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken)); + m_main_window->signal_action_login_qr().connect(sigc::mem_fun(*this, &Abaddon::ActionLoginQR)); m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS)); m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus)); m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient)); @@ -834,6 +836,16 @@ void Abaddon::ActionSetToken() { m_main_window->UpdateMenus(); } +void Abaddon::ActionLoginQR() { + RemoteAuthDialog dlg(*m_main_window); + auto response = dlg.run(); + if (response == Gtk::RESPONSE_OK) { + m_discord_token = dlg.GetToken(); + m_main_window->UpdateComponents(); + } + m_main_window->UpdateMenus(); +} + void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { if (!id.IsValid()) { m_discord.SetReferringChannel(Snowflake::Invalid); @@ -1142,6 +1154,7 @@ int main(int argc, char **argv) { auto log_audio = spdlog::stdout_color_mt("audio"); auto log_voice = spdlog::stdout_color_mt("voice"); auto log_discord = spdlog::stdout_color_mt("discord"); + auto log_ra = spdlog::stdout_color_mt("remote-auth"); Gtk::Main::init_gtkmm_internals(); // why??? return Abaddon::Get().StartGTK(); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 08070ae..85b2fa0 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -41,6 +41,7 @@ public: void ActionConnect(); void ActionDisconnect(); void ActionSetToken(); + void ActionLoginQR(); void ActionJoinGuildDialog(); void ActionChannelOpened(Snowflake id, bool expand_to = true); void ActionChatInputSubmit(ChatSubmitParams data); diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index f886e69..cdc4db1 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -26,7 +26,7 @@ void Websocket::StartConnection(const std::string &url) { m_websocket->disableAutomaticReconnection(); m_websocket->setUrl(url); m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); - m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works + m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent }, { "Origin", "https://discord.com" } }); // idk if this actually works m_websocket->start(); } @@ -81,6 +81,9 @@ void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { case ix::WebSocketMessageType::Message: { m_signal_message.emit(msg->str); } break; + case ix::WebSocketMessageType::Error: { + m_log->error("Websocket error: Status: {} Reason: {}", msg->errorInfo.http_status, msg->errorInfo.reason); + } break; default: break; } diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp new file mode 100644 index 0000000..1ebb7cb --- /dev/null +++ b/src/remoteauth/remoteauthclient.cpp @@ -0,0 +1,321 @@ +#include "remoteauthclient.hpp" +#include +#include + +struct RemoteAuthGatewayMessage { + std::string Opcode; + + friend void from_json(const nlohmann::json &j, RemoteAuthGatewayMessage &m) { + j.at("op").get_to(m.Opcode); + } +}; + +struct RemoteAuthHelloMessage { + int TimeoutMS; + int HeartbeatInterval; + + friend void from_json(const nlohmann::json &j, RemoteAuthHelloMessage &m) { + j.at("timeout_ms").get_to(m.TimeoutMS); + j.at("heartbeat_interval").get_to(m.HeartbeatInterval); + } +}; + +struct RemoteAuthHeartbeatMessage { + friend void to_json(nlohmann::json &j, const RemoteAuthHeartbeatMessage &m) { + j["op"] = "heartbeat"; + } +}; + +struct RemoteAuthInitMessage { + std::string EncodedPublicKey; + + friend void to_json(nlohmann::json &j, const RemoteAuthInitMessage &m) { + j["op"] = "init"; + j["encoded_public_key"] = m.EncodedPublicKey; + } +}; + +struct RemoteAuthNonceProofMessage { + std::string EncryptedNonce; + std::string Nonce; + + friend void from_json(const nlohmann::json &j, RemoteAuthNonceProofMessage &m) { + j.at("encrypted_nonce").get_to(m.EncryptedNonce); + } + + friend void to_json(nlohmann::json &j, const RemoteAuthNonceProofMessage &m) { + j["op"] = "nonce_proof"; + j["nonce"] = m.Nonce; + } +}; + +struct RemoteAuthFingerprintMessage { + std::string Fingerprint; + + friend void from_json(const nlohmann::json &j, RemoteAuthFingerprintMessage &m) { + j.at("fingerprint").get_to(m.Fingerprint); + } +}; + +struct RemoteAuthPendingTicketMessage { + std::string EncryptedUserPayload; + + friend void from_json(const nlohmann::json &j, RemoteAuthPendingTicketMessage &m) { + j.at("encrypted_user_payload").get_to(m.EncryptedUserPayload); + } +}; + +RemoteAuthClient::RemoteAuthClient() + : m_ws("remote-auth-ws") + , m_log(spdlog::get("remote-auth")) { + m_ws.signal_open().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketOpen)); + m_ws.signal_close().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketClose)); + m_ws.signal_message().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketMessage)); + + m_dispatcher.connect(sigc::mem_fun(*this, &RemoteAuthClient::OnDispatch)); +} + +RemoteAuthClient::~RemoteAuthClient() { + Stop(); +} + +void RemoteAuthClient::Start() { + if (IsConnected()) { + Stop(); + } + + m_connected = true; + m_heartbeat_waiter.revive(); + m_ws.StartConnection("wss://remote-auth-gateway.discord.gg/?v=2"); +} + +void RemoteAuthClient::Stop() { + if (!IsConnected()) { + m_log->warn("Requested stop while not connected"); + return; + } + + m_connected = false; + m_ws.Stop(1000); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); +} + +bool RemoteAuthClient::IsConnected() const noexcept { + return m_connected; +} + +void RemoteAuthClient::OnGatewayMessage(const std::string &str) { + auto j = nlohmann::json::parse(str); + RemoteAuthGatewayMessage msg = j; + if (msg.Opcode == "hello") { + HandleGatewayHello(j); + } else if (msg.Opcode == "nonce_proof") { + HandleGatewayNonceProof(j); + } else if (msg.Opcode == "pending_remote_init") { + HandleGatewayPendingRemoteInit(j); + } else if (msg.Opcode == "pending_ticket") { + HandleGatewayPendingTicket(j); + } +} + +void RemoteAuthClient::HandleGatewayHello(const nlohmann::json &j) { + RemoteAuthHelloMessage msg = j; + m_log->debug("Timeout: {}, Heartbeat: {}", msg.TimeoutMS, msg.HeartbeatInterval); + + m_heartbeat_msec = msg.HeartbeatInterval; + m_heartbeat_thread = std::thread(&RemoteAuthClient::HeartbeatThread, this); + + Init(); +} + +void RemoteAuthClient::HandleGatewayNonceProof(const nlohmann::json &j) { + RemoteAuthNonceProofMessage msg = j; + m_log->debug("Received encrypted nonce"); + + const auto encrypted_nonce = Glib::Base64::decode(msg.EncryptedNonce); + const auto proof = Decrypt(reinterpret_cast(encrypted_nonce.data()), encrypted_nonce.size()); + auto proof_encoded = Glib::Base64::encode(std::string(proof.begin(), proof.end())); + + std::replace(proof_encoded.begin(), proof_encoded.end(), '/', '_'); + std::replace(proof_encoded.begin(), proof_encoded.end(), '+', '-'); + proof_encoded.erase(std::remove(proof_encoded.begin(), proof_encoded.end(), '='), proof_encoded.end()); + + RemoteAuthNonceProofMessage reply; + reply.Nonce = proof_encoded; + m_ws.Send(reply); +} + +void RemoteAuthClient::HandleGatewayPendingRemoteInit(const nlohmann::json &j) { + RemoteAuthFingerprintMessage msg = j; + m_log->debug("Received fingerprint"); + + m_signal_fingerprint.emit(msg.Fingerprint); +} + +void RemoteAuthClient::HandleGatewayPendingTicket(const nlohmann::json &j) { + RemoteAuthPendingTicketMessage msg = j; + + const auto encrypted_payload = Glib::Base64::decode(msg.EncryptedUserPayload); + const auto payload = Decrypt(reinterpret_cast(encrypted_payload.data()), encrypted_payload.size()); + + m_log->trace("User payload: {}", std::string(payload.begin(), payload.end())); +} + +void RemoteAuthClient::Init() { + RemoteAuthInitMessage msg; + + GenerateKey(); + msg.EncodedPublicKey = GetEncodedPublicKey(); + if (msg.EncodedPublicKey.empty()) { + m_log->error("Something went wrong"); + // todo disconnect + return; + } + + m_ws.Send(msg); +} + +void RemoteAuthClient::GenerateKey() { + // you javascript people have it so easy + // check out this documentation https://www.openssl.org/docs/man1.1.1/man3/PEM_write_bio_PUBKEY.html + + m_pkey_ctx.reset(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + if (!m_pkey_ctx) { + m_log->error("Failed to create RSA context"); + return; + } + + if (EVP_PKEY_keygen_init(m_pkey_ctx.get()) <= 0) { + m_log->error("Failed to initialize RSA context"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(m_pkey_ctx.get(), 2048) <= 0) { + m_log->error("Failed to set keygen bits"); + return; + } + + EVP_PKEY *pkey_tmp = nullptr; + if (EVP_PKEY_keygen(m_pkey_ctx.get(), &pkey_tmp) <= 0) { + m_log->error("Failed to generate keypair"); + return; + } + m_pkey.reset(pkey_tmp); + + m_dec_ctx.reset(EVP_PKEY_CTX_new(m_pkey.get(), nullptr)); + if (EVP_PKEY_decrypt_init(m_dec_ctx.get()) <= 0) { + m_log->error("Failed to initialize RSA decrypt context"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_padding(m_dec_ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) { + m_log->error("EVP_PKEY_CTX_set_rsa_padding failed"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_oaep_md(m_dec_ctx.get(), EVP_sha256()) <= 0) { + m_log->error("EVP_PKEY_CTX_set_rsa_oaep_md failed"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_mgf1_md(m_dec_ctx.get(), EVP_sha256()) <= 0) { + m_log->error("EVP_PKEY_CTX_set_rsa_mgf1_md"); + return; + } +} + +std::string RemoteAuthClient::GetEncodedPublicKey() const { + auto bio = BIO_ptr(BIO_new(BIO_s_mem()), BIO_free); + if (!bio) { + m_log->error("Failed to create BIO"); + return {}; + } + + if (PEM_write_bio_PUBKEY(bio.get(), m_pkey.get()) <= 0) { + m_log->error("Failed to write public key to BIO"); + return {}; + } + + // i think this is freed when the bio is too + BUF_MEM *mem = nullptr; + if (BIO_get_mem_ptr(bio.get(), &mem) <= 0) { + m_log->error("Failed to get BIO mem buf"); + return {}; + } + + if (mem->data == nullptr || mem->length == 0) { + m_log->error("BIO mem buf is null or of zero length"); + return {}; + } + + std::string pem_pubkey(mem->data, mem->length); + // isolate key + pem_pubkey.erase(0, pem_pubkey.find("\n") + 1); + pem_pubkey.erase(pem_pubkey.rfind("\n-")); + size_t pos; + while ((pos = pem_pubkey.find("\n")) != std::string::npos) { + pem_pubkey.erase(pos, 1); + } + return pem_pubkey; +} + +std::vector RemoteAuthClient::Decrypt(const unsigned char *in, size_t inlen) const { + // get length + size_t outlen; + if (EVP_PKEY_decrypt(m_dec_ctx.get(), nullptr, &outlen, in, inlen) <= 0) { + m_log->error("Failed to get length when decrypting"); + return {}; + } + + std::vector ret(outlen); + if (EVP_PKEY_decrypt(m_dec_ctx.get(), ret.data(), &outlen, in, inlen) <= 0) { + m_log->error("Failed to decrypt"); + return {}; + } + ret.resize(outlen); + return ret; +} + +void RemoteAuthClient::OnWebsocketOpen() { + m_log->info("Websocket opened"); +} + +void RemoteAuthClient::OnWebsocketClose(const ix::WebSocketCloseInfo &info) { + if (info.remote) { + m_log->debug("Websocket closed (remote): {} ({})", info.code, info.reason); + } else { + m_log->debug("Websocket closed (local): {} ({})", info.code, info.reason); + } +} + +void RemoteAuthClient::OnWebsocketMessage(const std::string &data) { + m_dispatch_mutex.lock(); + m_dispatch_queue.push(data); + m_dispatcher.emit(); + m_dispatch_mutex.unlock(); +} + +void RemoteAuthClient::HeartbeatThread() { + while (true) { + if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) break; + + m_ws.Send(RemoteAuthHeartbeatMessage()); + } +} + +void RemoteAuthClient::OnDispatch() { + m_dispatch_mutex.lock(); + if (m_dispatch_queue.empty()) { + m_dispatch_mutex.unlock(); + return; + } + auto msg = std::move(m_dispatch_queue.front()); + m_dispatch_queue.pop(); + m_dispatch_mutex.unlock(); + OnGatewayMessage(msg); +} + +RemoteAuthClient::type_signal_fingerprint RemoteAuthClient::signal_fingerprint() { + return m_signal_fingerprint; +} diff --git a/src/remoteauth/remoteauthclient.hpp b/src/remoteauth/remoteauthclient.hpp new file mode 100644 index 0000000..178e10a --- /dev/null +++ b/src/remoteauth/remoteauthclient.hpp @@ -0,0 +1,64 @@ +#pragma once +#include +#include +#include +#include "ssl.hpp" +#include "discord/waiter.hpp" +#include "discord/websocket.hpp" + +class RemoteAuthClient { +public: + RemoteAuthClient(); + ~RemoteAuthClient(); + + void Start(); + void Stop(); + + [[nodiscard]] bool IsConnected() const noexcept; + +private: + void OnGatewayMessage(const std::string &str); + void HandleGatewayHello(const nlohmann::json &j); + void HandleGatewayNonceProof(const nlohmann::json &j); + void HandleGatewayPendingRemoteInit(const nlohmann::json &j); + void HandleGatewayPendingTicket(const nlohmann::json &j); + + void Init(); + + void GenerateKey(); + std::string GetEncodedPublicKey() const; + + std::vector Decrypt(const unsigned char *in, size_t inlen) const; + + void OnWebsocketOpen(); + void OnWebsocketClose(const ix::WebSocketCloseInfo &info); + void OnWebsocketMessage(const std::string &str); + + void HeartbeatThread(); + + int m_heartbeat_msec; + Waiter m_heartbeat_waiter; + std::thread m_heartbeat_thread; + + Glib::Dispatcher m_dispatcher; + std::queue m_dispatch_queue; + std::mutex m_dispatch_mutex; + + void OnDispatch(); + + Websocket m_ws; + bool m_connected = false; + + std::shared_ptr m_log; + + EVP_PKEY_CTX_ptr m_pkey_ctx; + EVP_PKEY_CTX_ptr m_dec_ctx; + EVP_PKEY_ptr m_pkey; + +public: + using type_signal_fingerprint = sigc::signal; + type_signal_fingerprint signal_fingerprint(); + +private: + type_signal_fingerprint m_signal_fingerprint; +}; diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp new file mode 100644 index 0000000..db154c4 --- /dev/null +++ b/src/remoteauth/remoteauthdialog.cpp @@ -0,0 +1,79 @@ +#include "remoteauthdialog.hpp" +#include + +RemoteAuthDialog::RemoteAuthDialog(Gtk::Window &parent) + : Gtk::Dialog("Login with QR Code", parent, true) + , m_layout(Gtk::ORIENTATION_VERTICAL) + , m_ok("OK") + , m_cancel("Cancel") + , m_bbox(Gtk::ORIENTATION_HORIZONTAL) { + set_default_size(300, 50); + get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); + + m_ok.signal_clicked().connect([&]() { + response(Gtk::RESPONSE_OK); + }); + + m_cancel.signal_clicked().connect([&]() { + response(Gtk::RESPONSE_CANCEL); + }); + + m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK); + m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK); + m_bbox.set_layout(Gtk::BUTTONBOX_END); + + m_ra.signal_fingerprint().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnFingerprint)); + + m_ra.Start(); + + m_image.set_size_request(256, 256); + + m_layout.add(m_image); + m_layout.add(m_bbox); + get_content_area()->add(m_layout); + + show_all_children(); +} + +void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { + const auto url = "https://discord.com/ra/" + fingerprint; + + const auto level = qrcodegen::QrCode::Ecc::QUARTILE; + const auto qr = qrcodegen::QrCode::encodeText(url.c_str(), level); + + int size = qr.getSize(); + const int border = 4; + + std::ostringstream sb; + sb << "\n"; + sb << "\n"; + sb << "\n"; + sb << "\t\n"; + sb << "\t\n"; + sb << "\n"; + + const auto svg = sb.str(); + + auto loader = Gdk::PixbufLoader::create(); + loader->write(reinterpret_cast(svg.data()), svg.size()); + loader->close(); + const auto pb = loader->get_pixbuf()->scale_simple(256, 256, Gdk::INTERP_NEAREST); + + m_image.property_pixbuf() = pb; +} + +std::string RemoteAuthDialog::GetToken() { + return m_token; +} diff --git a/src/remoteauth/remoteauthdialog.hpp b/src/remoteauth/remoteauthdialog.hpp new file mode 100644 index 0000000..24fad56 --- /dev/null +++ b/src/remoteauth/remoteauthdialog.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include "remoteauthclient.hpp" + +class RemoteAuthDialog : public Gtk::Dialog { +public: + RemoteAuthDialog(Gtk::Window &parent); + std::string GetToken(); + +protected: + Gtk::Image m_image; + Gtk::Box m_layout; + Gtk::Button m_ok; + Gtk::Button m_cancel; + Gtk::ButtonBox m_bbox; + +private: + RemoteAuthClient m_ra; + + void OnFingerprint(const std::string &fingerprint); + + std::string m_token; +}; diff --git a/src/remoteauth/ssl.hpp b/src/remoteauth/ssl.hpp new file mode 100644 index 0000000..1753bd3 --- /dev/null +++ b/src/remoteauth/ssl.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +struct EVP_PKEY_CTX_deleter { + void operator()(EVP_PKEY_CTX *ptr) const { + EVP_PKEY_CTX_free(ptr); + } +}; + +struct EVP_PKEY_deleter { + void operator()(EVP_PKEY *ptr) const { + EVP_PKEY_free(ptr); + } +}; + +struct EVP_MD_CTX_deleter { + void operator()(EVP_MD_CTX *ptr) const { + EVP_MD_CTX_free(ptr); + } +}; + +using EVP_PKEY_CTX_ptr = std::unique_ptr; +using EVP_PKEY_ptr = std::unique_ptr; +using EVP_MD_CTX_ptr = std::unique_ptr; +using BIO_ptr = std::unique_ptr; +using BUF_MEM_ptr = std::unique_ptr; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index bb09735..56d735e 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -206,6 +206,7 @@ void MainWindow::OnDiscordSubmenuPopup() { m_menu_discord_connect.set_sensitive(!token.empty() && !discord_active); m_menu_discord_disconnect.set_sensitive(discord_active); m_menu_discord_set_token.set_sensitive(!discord_active); + m_menu_discord_login_qr.set_sensitive(!discord_active); m_menu_discord_set_status.set_sensitive(discord_active); } @@ -247,12 +248,14 @@ void MainWindow::SetupMenu() { m_menu_discord_disconnect.set_label("Disconnect"); m_menu_discord_disconnect.set_sensitive(false); m_menu_discord_set_token.set_label("Set Token"); + m_menu_discord_login_qr.set_label("Login with QR Code"); m_menu_discord_set_status.set_label("Set Status"); m_menu_discord_set_status.set_sensitive(false); m_menu_discord_add_recipient.set_label("Add user to DM"); m_menu_discord_sub.append(m_menu_discord_connect); m_menu_discord_sub.append(m_menu_discord_disconnect); m_menu_discord_sub.append(m_menu_discord_set_token); + m_menu_discord_sub.append(m_menu_discord_login_qr); m_menu_discord_sub.append(m_menu_discord_set_status); m_menu_discord_sub.append(m_menu_discord_add_recipient); m_menu_discord.set_submenu(m_menu_discord_sub); @@ -331,6 +334,10 @@ void MainWindow::SetupMenu() { m_signal_action_set_token.emit(); }); + m_menu_discord_login_qr.signal_activate().connect([this] { + m_signal_action_login_qr.emit(); + }); + m_menu_file_reload_css.signal_activate().connect([this] { m_signal_action_reload_css.emit(); }); @@ -421,6 +428,10 @@ MainWindow::type_signal_action_set_token MainWindow::signal_action_set_token() { return m_signal_action_set_token; } +MainWindow::type_signal_action_login_qr MainWindow::signal_action_login_qr() { + return m_signal_action_login_qr; +} + MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css() { return m_signal_action_reload_css; } diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index e075c10..604043c 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -74,6 +74,7 @@ private: Gtk::MenuItem m_menu_discord_connect; Gtk::MenuItem m_menu_discord_disconnect; Gtk::MenuItem m_menu_discord_set_token; + Gtk::MenuItem m_menu_discord_login_qr; Gtk::MenuItem m_menu_discord_set_status; Gtk::MenuItem m_menu_discord_add_recipient; // move me somewhere else some day void OnDiscordSubmenuPopup(); @@ -103,6 +104,7 @@ public: typedef sigc::signal type_signal_action_connect; typedef sigc::signal type_signal_action_disconnect; typedef sigc::signal type_signal_action_set_token; + typedef sigc::signal type_signal_action_login_qr; typedef sigc::signal type_signal_action_reload_css; typedef sigc::signal type_signal_action_set_status; // this should probably be removed @@ -113,6 +115,7 @@ public: type_signal_action_connect signal_action_connect(); type_signal_action_disconnect signal_action_disconnect(); type_signal_action_set_token signal_action_set_token(); + type_signal_action_login_qr signal_action_login_qr(); type_signal_action_reload_css signal_action_reload_css(); type_signal_action_set_status signal_action_set_status(); type_signal_action_add_recipient signal_action_add_recipient(); @@ -123,6 +126,7 @@ private: type_signal_action_connect m_signal_action_connect; type_signal_action_disconnect m_signal_action_disconnect; type_signal_action_set_token m_signal_action_set_token; + type_signal_action_login_qr m_signal_action_login_qr; type_signal_action_reload_css m_signal_action_reload_css; type_signal_action_set_status m_signal_action_set_status; type_signal_action_add_recipient m_signal_action_add_recipient; diff --git a/subprojects/qrcodegen b/subprojects/qrcodegen new file mode 160000 index 0000000..22fac31 --- /dev/null +++ b/subprojects/qrcodegen @@ -0,0 +1 @@ +Subproject commit 22fac31bdf81da68730c177c0e931c93234d2a30