From 172e54f824b24908f6018fa68a30fcc10d0984e8 Mon Sep 17 00:00:00 2001 From: Kamil Holubicki Date: Mon, 6 Jul 2026 11:27:38 +0200 Subject: [PATCH] PBS-32: handle auth method switch negotiation https://perconadev.atlassian.net/browse/PBS-32 Implement AuthSwitchRequest handling when the client replies with an authentication plugin that differs from the server account plugin. The server now sends the switch request, reads the client auth switch response, updates the connection context, and continues normal authentication. Format the auth switch plugin data through a helper so caching_sha2_password keeps the historical trailing NULL filler expected by MySQL clients, while leaving future plugins free to define their own payload shape. --- src/minimysql/connection_context.cpp | 49 ++++++++++++++++++++++++++++ src/minimysql/connection_context.hpp | 4 +++ src/minimysql/network_service.cpp | 24 ++++++++++++-- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/minimysql/connection_context.cpp b/src/minimysql/connection_context.cpp index f859bac..a459668 100644 --- a/src/minimysql/connection_context.cpp +++ b/src/minimysql/connection_context.cpp @@ -178,6 +178,43 @@ void connection_context::parse_client_greeting( client_attributes_ = client_greeting.attributes(); } +[[nodiscard]] network_buffer_type +connection_context::generate_encoded_auth_method_switch() { + std::string result_buffer{}; + + classic_protocol::message::server::AuthMethodSwitch auth_method_switch{ + get_server_auth_method(), generate_server_auth_method_switch_data()}; + using auth_method_switch_frame = classic_protocol::frame::Frame< + classic_protocol::message::server::AuthMethodSwitch>; + auto encode_result{classic_protocol::encode( + {generate_sequence_number(), auth_method_switch}, + get_shared_capabilities(), boost::asio::dynamic_buffer(result_buffer))}; + + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } + return result_buffer; +} + +void connection_context::parse_client_auth_method_data( + const network_buffer_type &payload) { + auto buffer{boost::asio::buffer(payload)}; + using auth_method_data_frame = classic_protocol::frame::Frame< + classic_protocol::message::client::AuthMethodData>; + auto decode_result{classic_protocol::decode( + buffer, get_shared_capabilities())}; + if (!decode_result) { + throw boost::system::system_error{decode_result.error()}; + } + + validate_and_update_sequence_number(decode_result.value().second.seq_id()); + + // after the auth method switch the client uses the server's auth method + client_auth_method_ = server_auth_method_; + client_auth_method_data_ = + decode_result.value().second.payload().auth_method_data(); +} + [[nodiscard]] network_buffer_type connection_context::generate_encoded_fast_auth() { std::string result_buffer{}; @@ -429,6 +466,18 @@ connection_context::generate_server_auth_method_data() { return server_auth_method_data_; } +[[nodiscard]] std::string +connection_context::generate_server_auth_method_switch_data() const { + if (get_server_auth_method() == + caching_sha2_password_authenticator::plugin_name) { + // caching_sha2_password follows the historical handshake seed shape: + // 20 bytes of challenge data plus a trailing NUL filler. + return get_server_auth_method_data() + '\0'; + } + + return get_server_auth_method_data(); +} + [[nodiscard]] std::uint8_t connection_context::generate_sequence_number() { return sequence_number_++; } diff --git a/src/minimysql/connection_context.hpp b/src/minimysql/connection_context.hpp index a2c69d7..40d5875 100644 --- a/src/minimysql/connection_context.hpp +++ b/src/minimysql/connection_context.hpp @@ -130,6 +130,9 @@ class connection_context { [[nodiscard]] network_buffer_type generate_encoded_server_greeting(); void parse_client_greeting(const network_buffer_type &payload); + [[nodiscard]] network_buffer_type generate_encoded_auth_method_switch(); + void parse_client_auth_method_data(const network_buffer_type &payload); + [[nodiscard]] network_buffer_type generate_encoded_fast_auth(); [[nodiscard]] network_buffer_type generate_encoded_ok(); [[nodiscard]] network_buffer_type generate_encoded_eof(); @@ -228,6 +231,7 @@ class connection_context { [[nodiscard]] static capability_bitset get_default_server_capabilities() noexcept; [[nodiscard]] const std::string &generate_server_auth_method_data(); + [[nodiscard]] std::string generate_server_auth_method_switch_data() const; [[nodiscard]] std::uint8_t generate_sequence_number(); void validate_and_update_sequence_number(std::uint8_t sequence_number); diff --git a/src/minimysql/network_service.cpp b/src/minimysql/network_service.cpp index 412472f..c977aba 100644 --- a/src/minimysql/network_service.cpp +++ b/src/minimysql/network_service.cpp @@ -291,8 +291,28 @@ void handle_exception(std::string_view context) { << " authentication that does not match the one associated " "with the user account (" << context.get_server_auth_method() << ")\n"; - // TODO: send SwitchAuthentication packet - co_return; + + const auto auth_method_switch{ + context.generate_encoded_auth_method_switch()}; + print_generic(remote_endpoint, context, "auth method switch"); + co_await minimysql::async_write_mysql_frame( + socket, auth_method_switch, + network_service::session_authentication_timeout); + std::cout << "sent server auth method switch (" + << std::size(auth_method_switch) << " bytes to " + << remote_endpoint << ")\n"; + + co_await minimysql::async_read_mysql_frame( + socket, data, network_service::session_authentication_timeout); + std::cout << "received client auth method switch response (" + << std::size(data) << " bytes from " << remote_endpoint + << ")\n"; + context.parse_client_auth_method_data(data); + std::cout << "client auth method after switch: " + << context.get_client_auth_method() << '\n' + << " auth_method_data: " + << std::size(context.get_client_auth_method_data()) + << " byte(s)\n"; } if (!context.check_client_authentication()) { std::cout << "client authentication failed for "