mirror of https://github.com/sysown/proxysql
Three pieces of dead code that survived the MysqlxWorker retirement
(commit 98aee7db2) and the v2 event-driven rewrite:
* `MysqlxFrontendSession` and `MysqlxBackendSession` (plugins/mysqlx/
include/* + src/*): an alternative session implementation from the
pre-event-driven days. Compiled into the .so but not instantiated
anywhere — `Mysqlx_Thread::accept_new_connection` creates a
`MysqlxSession` directly. The Phase-1 placeholder in
`MysqlxFrontendSession::handler_capabilities_set` (returning
"TLS capability not supported by mysqlx plugin in Phase 1") was
particularly misleading on review since the real, TLS-aware
`MysqlxSession::handler_capabilities_set` in mysqlx_session.cpp:202
has full TLS negotiation wired up.
* `X_FAST_FORWARD` enum value, `handler_fast_forward()` declaration,
dispatch case, and empty body (plugins/mysqlx/src/mysqlx_session.cpp):
state was declared but no code path ever set
`status_ = X_FAST_FORWARD`, so the dispatch case was unreachable and
the handler body was empty.
Drop the four files from `plugins/mysqlx/Makefile`'s SRCS list and
delete them. Plugin .so size drops from ~9.3 MB to ~8.5 MB
(approximately 800 KB of dead code eliminated). Build clean under
PROXYSQLGENAI=1.
ProtocolX
parent
c723ede0cf
commit
79cac4c976
@ -1,35 +0,0 @@
|
||||
#ifndef PROXYSQL_MYSQLX_BACKEND_SESSION_H
|
||||
#define PROXYSQL_MYSQLX_BACKEND_SESSION_H
|
||||
|
||||
#include "mysqlx_config_store.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class MysqlxBackendSession {
|
||||
public:
|
||||
MysqlxBackendSession();
|
||||
~MysqlxBackendSession();
|
||||
|
||||
MysqlxBackendSession(const MysqlxBackendSession&) = delete;
|
||||
MysqlxBackendSession& operator=(const MysqlxBackendSession&) = delete;
|
||||
|
||||
// Connect to backend and authenticate using resolved identity and endpoint.
|
||||
bool connect(const MysqlxResolvedIdentity& identity,
|
||||
const MysqlxBackendEndpoint& endpoint,
|
||||
std::string& err);
|
||||
|
||||
int fd() const { return backend_fd_; }
|
||||
|
||||
// Relay data bidirectionally between frontend_fd and backend_fd.
|
||||
// Runs until one side closes or an error occurs.
|
||||
bool relay(int frontend_fd);
|
||||
|
||||
private:
|
||||
bool authenticate_backend(const std::string& username,
|
||||
const std::string& password,
|
||||
std::string& err);
|
||||
|
||||
int backend_fd_ { -1 };
|
||||
};
|
||||
|
||||
#endif /* PROXYSQL_MYSQLX_BACKEND_SESSION_H */
|
||||
@ -1,43 +0,0 @@
|
||||
#ifndef PROXYSQL_MYSQLX_FRONTEND_SESSION_H
|
||||
#define PROXYSQL_MYSQLX_FRONTEND_SESSION_H
|
||||
|
||||
#include "mysqlx_config_store.h"
|
||||
#include "mysqlx_protocol.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class MysqlxFrontendSession {
|
||||
public:
|
||||
explicit MysqlxFrontendSession(int client_fd);
|
||||
~MysqlxFrontendSession();
|
||||
|
||||
MysqlxFrontendSession(const MysqlxFrontendSession&) = delete;
|
||||
MysqlxFrontendSession& operator=(const MysqlxFrontendSession&) = delete;
|
||||
|
||||
// Run the full X Protocol handshake and authentication.
|
||||
// On success, the session is authenticated and identity is resolved.
|
||||
// On failure, an error has already been sent to the client.
|
||||
bool run_handshake_and_auth(MysqlxConfigStore& config_store);
|
||||
|
||||
// After successful auth, returns the resolved identity.
|
||||
const MysqlxResolvedIdentity& identity() const { return identity_; }
|
||||
|
||||
int client_fd() const { return client_fd_; }
|
||||
|
||||
private:
|
||||
bool handle_capabilities_get();
|
||||
bool handle_capabilities_set(const std::vector<uint8_t>& payload);
|
||||
bool handle_authenticate(MysqlxConfigStore& config_store);
|
||||
|
||||
bool send_capabilities();
|
||||
bool send_auth_continue_challenge();
|
||||
|
||||
int client_fd_;
|
||||
MysqlxResolvedIdentity identity_ {};
|
||||
std::vector<uint8_t> auth_challenge_ {};
|
||||
std::string auth_method_ {};
|
||||
};
|
||||
|
||||
#endif /* PROXYSQL_MYSQLX_FRONTEND_SESSION_H */
|
||||
@ -1,309 +0,0 @@
|
||||
#include "mysqlx_backend_session.h"
|
||||
#include "mysqlx_protocol.h"
|
||||
|
||||
#include "mysqlx.pb.h"
|
||||
#include "mysqlx_connection.pb.h"
|
||||
#include "mysqlx_session.pb.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
MysqlxBackendSession::MysqlxBackendSession() = default;
|
||||
|
||||
MysqlxBackendSession::~MysqlxBackendSession() {
|
||||
if (backend_fd_ >= 0) {
|
||||
close(backend_fd_);
|
||||
backend_fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool MysqlxBackendSession::connect(const MysqlxResolvedIdentity& identity,
|
||||
const MysqlxBackendEndpoint& endpoint,
|
||||
std::string& err) {
|
||||
if (identity.backend_auth_mode == MysqlxBackendAuthMode::pass_through) {
|
||||
err = "pass_through backend auth mode not supported in Phase 1";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (endpoint.hostname.empty() || endpoint.mysqlx_port <= 0) {
|
||||
err = "no valid backend endpoint";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string port_str = std::to_string(endpoint.mysqlx_port);
|
||||
struct addrinfo hints {}, *result = nullptr;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
|
||||
int gai_rc = getaddrinfo(endpoint.hostname.c_str(), port_str.c_str(), &hints, &result);
|
||||
if (gai_rc != 0) {
|
||||
err = "mysqlx backend: cannot resolve '";
|
||||
err += endpoint.hostname;
|
||||
err += "': ";
|
||||
err += gai_strerror(gai_rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
|
||||
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (fd < 0) continue;
|
||||
|
||||
int flag = 1;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
||||
|
||||
struct timeval tv { 10, 0 };
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
|
||||
if (::connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
|
||||
if (fd < 0) {
|
||||
err = "mysqlx backend: connect failed to ";
|
||||
err += endpoint.hostname + ":" + std::to_string(endpoint.mysqlx_port);
|
||||
return false;
|
||||
}
|
||||
backend_fd_ = fd;
|
||||
|
||||
// Authenticate to the backend MySQL X server.
|
||||
std::string backend_user = identity.backend_username;
|
||||
std::string backend_pass = identity.backend_password;
|
||||
|
||||
if (!authenticate_backend(backend_user, backend_pass, err)) {
|
||||
// Auth failure: close the socket we just opened so a later retry
|
||||
// on this MysqlxBackendSession doesn't overwrite backend_fd_ and
|
||||
// leak the previous fd.
|
||||
close(backend_fd_);
|
||||
backend_fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MysqlxBackendSession::authenticate_backend(const std::string& username,
|
||||
const std::string& password,
|
||||
std::string& err) {
|
||||
// Step 1: Send CapabilitiesGet to learn what the backend supports.
|
||||
{
|
||||
Mysqlx::Connection::CapabilitiesGet cap_get;
|
||||
std::string serialized;
|
||||
cap_get.SerializeToString(&serialized);
|
||||
auto frame = mysqlx_build_frame(
|
||||
Mysqlx::ClientMessages_Type_CON_CAPABILITIES_GET,
|
||||
serialized
|
||||
);
|
||||
if (!mysqlx_write_all(backend_fd_, frame.data(), frame.size())) {
|
||||
err = "failed to send CapabilitiesGet to backend";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Read Capabilities response (skip any preceding Notice frames).
|
||||
{
|
||||
MysqlxFrameHeader header {};
|
||||
std::vector<uint8_t> payload {};
|
||||
if (!mysqlx_read_frame(backend_fd_, header, payload)) {
|
||||
err = "failed to read Capabilities from backend";
|
||||
return false;
|
||||
}
|
||||
while (header.message_type == Mysqlx::ServerMessages_Type_NOTICE) {
|
||||
if (!mysqlx_read_frame(backend_fd_, header, payload)) {
|
||||
err = "failed to read frame after notice during capabilities";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (header.message_type != Mysqlx::ServerMessages_Type_CONN_CAPABILITIES) {
|
||||
err = "unexpected response to CapabilitiesGet from backend";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Send AuthenticateStart with MYSQL41.
|
||||
{
|
||||
Mysqlx::Session::AuthenticateStart auth_start;
|
||||
auth_start.set_mech_name("MYSQL41");
|
||||
// auth_data: schema\0username\0
|
||||
std::string auth_data;
|
||||
auth_data += '\0'; // empty schema
|
||||
auth_data += username;
|
||||
auth_data += '\0';
|
||||
auth_start.set_auth_data(auth_data);
|
||||
|
||||
std::string serialized;
|
||||
auth_start.SerializeToString(&serialized);
|
||||
auto frame = mysqlx_build_frame(
|
||||
Mysqlx::ClientMessages_Type_SESS_AUTHENTICATE_START,
|
||||
serialized
|
||||
);
|
||||
if (!mysqlx_write_all(backend_fd_, frame.data(), frame.size())) {
|
||||
err = "failed to send AuthenticateStart to backend";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Read AuthenticateContinue with challenge (skip Notice frames).
|
||||
std::vector<uint8_t> challenge {};
|
||||
{
|
||||
MysqlxFrameHeader header {};
|
||||
std::vector<uint8_t> payload {};
|
||||
if (!mysqlx_read_frame(backend_fd_, header, payload)) {
|
||||
err = "failed to read auth challenge from backend";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip any intervening Notice frames.
|
||||
while (header.message_type == Mysqlx::ServerMessages_Type_NOTICE) {
|
||||
if (!mysqlx_read_frame(backend_fd_, header, payload)) {
|
||||
err = "failed to read frame after notice during auth challenge";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (header.message_type == Mysqlx::ServerMessages_Type_ERROR) {
|
||||
Mysqlx::Error error_msg;
|
||||
error_msg.ParseFromArray(payload.data(), static_cast<int>(payload.size()));
|
||||
err = "backend auth error: " + error_msg.msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.message_type != Mysqlx::ServerMessages_Type_SESS_AUTHENTICATE_CONTINUE) {
|
||||
err = "unexpected message type during backend auth";
|
||||
return false;
|
||||
}
|
||||
|
||||
Mysqlx::Session::AuthenticateContinue auth_continue;
|
||||
if (!auth_continue.ParseFromArray(payload.data(), static_cast<int>(payload.size()))) {
|
||||
err = "failed to parse backend AuthenticateContinue";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& ch = auth_continue.auth_data();
|
||||
challenge.assign(ch.begin(), ch.end());
|
||||
}
|
||||
|
||||
// Step 5: Compute MYSQL41 scramble and send AuthenticateContinue.
|
||||
// Wire format: \0username\0*UPPERCASE_HEX(scramble)
|
||||
{
|
||||
auto scramble = mysqlx_mysql41_scramble(challenge, password);
|
||||
std::string hex_scramble = mysqlx_hex_encode(scramble);
|
||||
|
||||
std::string auth_data;
|
||||
auth_data += '\0'; // empty schema
|
||||
auth_data += username;
|
||||
auth_data += '\0';
|
||||
auth_data += '*';
|
||||
auth_data += hex_scramble;
|
||||
|
||||
Mysqlx::Session::AuthenticateContinue auth_continue;
|
||||
auth_continue.set_auth_data(auth_data);
|
||||
|
||||
std::string serialized;
|
||||
auth_continue.SerializeToString(&serialized);
|
||||
auto frame = mysqlx_build_frame(
|
||||
Mysqlx::ClientMessages_Type_SESS_AUTHENTICATE_CONTINUE,
|
||||
serialized
|
||||
);
|
||||
if (!mysqlx_write_all(backend_fd_, frame.data(), frame.size())) {
|
||||
err = "failed to send auth response to backend";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Read AuthenticateOk (or Error).
|
||||
{
|
||||
MysqlxFrameHeader header {};
|
||||
std::vector<uint8_t> payload {};
|
||||
if (!mysqlx_read_frame(backend_fd_, header, payload)) {
|
||||
err = "failed to read auth result from backend";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.message_type == Mysqlx::ServerMessages_Type_ERROR) {
|
||||
Mysqlx::Error error_msg;
|
||||
error_msg.ParseFromArray(payload.data(), static_cast<int>(payload.size()));
|
||||
err = "backend auth failed: " + error_msg.msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Accept both AuthenticateOk and Notice (some servers send notices first).
|
||||
while (header.message_type == Mysqlx::ServerMessages_Type_NOTICE) {
|
||||
if (!mysqlx_read_frame(backend_fd_, header, payload)) {
|
||||
err = "failed to read post-notice frame from backend";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (header.message_type != Mysqlx::ServerMessages_Type_SESS_AUTHENTICATE_OK) {
|
||||
err = "unexpected message type after backend auth";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MysqlxBackendSession::relay(int frontend_fd) {
|
||||
// Byte-level bidirectional relay between frontend and backend.
|
||||
uint8_t buf[65536];
|
||||
|
||||
struct pollfd pfds[2];
|
||||
pfds[0].fd = frontend_fd;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[1].fd = backend_fd_;
|
||||
pfds[1].events = POLLIN;
|
||||
|
||||
while (true) {
|
||||
pfds[0].revents = 0;
|
||||
pfds[1].revents = 0;
|
||||
|
||||
int ready = poll(pfds, 2, 30000 /*ms*/);
|
||||
if (ready < 0) {
|
||||
return false;
|
||||
}
|
||||
if (ready == 0) {
|
||||
return false; // idle timeout
|
||||
}
|
||||
|
||||
// Check for errors/hangups.
|
||||
if ((pfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) ||
|
||||
(pfds[1].revents & (POLLERR | POLLHUP | POLLNVAL))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Frontend → Backend
|
||||
if (pfds[0].revents & POLLIN) {
|
||||
ssize_t n = read(frontend_fd, buf, sizeof(buf));
|
||||
if (n <= 0) {
|
||||
return n == 0;
|
||||
}
|
||||
if (!mysqlx_write_all(backend_fd_, buf, static_cast<size_t>(n))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Backend → Frontend
|
||||
if (pfds[1].revents & POLLIN) {
|
||||
ssize_t n = read(backend_fd_, buf, sizeof(buf));
|
||||
if (n <= 0) {
|
||||
return n == 0;
|
||||
}
|
||||
if (!mysqlx_write_all(frontend_fd, buf, static_cast<size_t>(n))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,406 +0,0 @@
|
||||
#include "mysqlx_frontend_session.h"
|
||||
|
||||
#include "mysqlx.pb.h"
|
||||
#include "mysqlx_connection.pb.h"
|
||||
#include "mysqlx_session.pb.h"
|
||||
#include "mysqlx_notice.pb.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t CHALLENGE_LENGTH = 20;
|
||||
|
||||
// Parse "schema\0user\0" from auth_data (MYSQL41 initial message).
|
||||
// Returns false if parsing fails.
|
||||
bool parse_mysql41_auth_data(const std::string& auth_data,
|
||||
std::string& schema,
|
||||
std::string& username) {
|
||||
// Format: \0-terminated schema, then \0-terminated username, then scramble.
|
||||
// For AuthenticateStart, auth_data is just schema + \0 + user + \0.
|
||||
size_t first_nul = auth_data.find('\0');
|
||||
if (first_nul == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
schema = auth_data.substr(0, first_nul);
|
||||
|
||||
size_t rest_start = first_nul + 1;
|
||||
size_t second_nul = auth_data.find('\0', rest_start);
|
||||
if (second_nul == std::string::npos) {
|
||||
// No second NUL — everything after first NUL is username.
|
||||
username = auth_data.substr(rest_start);
|
||||
} else {
|
||||
username = auth_data.substr(rest_start, second_nul - rest_start);
|
||||
}
|
||||
|
||||
return !username.empty();
|
||||
}
|
||||
|
||||
// Parse PLAIN auth data: \0username\0password
|
||||
bool parse_plain_auth_data(const std::string& auth_data,
|
||||
std::string& username,
|
||||
std::string& password) {
|
||||
// Format: \0user\0password
|
||||
if (auth_data.empty() || auth_data[0] != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t second_nul = auth_data.find('\0', 1);
|
||||
if (second_nul == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
username = auth_data.substr(1, second_nul - 1);
|
||||
password = auth_data.substr(second_nul + 1);
|
||||
return !username.empty();
|
||||
}
|
||||
|
||||
// Check if the auth method is allowed for this user.
|
||||
// Empty allowed_auth_methods means all supported methods are allowed.
|
||||
bool is_auth_method_allowed(const std::string& method, const std::string& allowed) {
|
||||
if (allowed.empty()) {
|
||||
return true;
|
||||
}
|
||||
// allowed_auth_methods is comma-separated, e.g. "MYSQL41,PLAIN"
|
||||
size_t pos = 0;
|
||||
while (pos < allowed.size()) {
|
||||
size_t comma = allowed.find(',', pos);
|
||||
if (comma == std::string::npos) comma = allowed.size();
|
||||
std::string token = allowed.substr(pos, comma - pos);
|
||||
// Trim whitespace.
|
||||
while (!token.empty() && token.front() == ' ') token.erase(0, 1);
|
||||
while (!token.empty() && token.back() == ' ') token.pop_back();
|
||||
if (token == method) return true;
|
||||
pos = comma + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MysqlxFrontendSession::MysqlxFrontendSession(int client_fd)
|
||||
: client_fd_(client_fd) {
|
||||
// Set socket timeouts to prevent slow-client DoS.
|
||||
struct timeval tv { 30, 0 }; // 30 second timeout
|
||||
setsockopt(client_fd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
setsockopt(client_fd_, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
|
||||
// Generate random challenge for MYSQL41.
|
||||
auth_challenge_.resize(CHALLENGE_LENGTH);
|
||||
RAND_bytes(auth_challenge_.data(), CHALLENGE_LENGTH);
|
||||
}
|
||||
|
||||
MysqlxFrontendSession::~MysqlxFrontendSession() = default;
|
||||
|
||||
bool MysqlxFrontendSession::run_handshake_and_auth(MysqlxConfigStore& config_store) {
|
||||
// X Protocol handshake loop:
|
||||
// 1. Wait for CapabilitiesGet or AuthenticateStart.
|
||||
// 2. If CapabilitiesGet → reply with Capabilities, then wait for next.
|
||||
// 3. If CapabilitiesSet → handle TLS etc, reply Ok, wait for next.
|
||||
// 4. If AuthenticateStart → run auth flow.
|
||||
|
||||
while (true) {
|
||||
MysqlxFrameHeader header {};
|
||||
std::vector<uint8_t> payload {};
|
||||
|
||||
if (!mysqlx_read_frame(client_fd_, header, payload)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (header.message_type) {
|
||||
case Mysqlx::ClientMessages_Type_CON_CAPABILITIES_GET:
|
||||
if (!handle_capabilities_get()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Mysqlx::ClientMessages_Type_CON_CAPABILITIES_SET:
|
||||
if (!handle_capabilities_set(payload)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Mysqlx::ClientMessages_Type_SESS_AUTHENTICATE_START: {
|
||||
// Parse AuthenticateStart.
|
||||
Mysqlx::Session::AuthenticateStart auth_start;
|
||||
if (!auth_start.ParseFromArray(payload.data(), static_cast<int>(payload.size()))) {
|
||||
mysqlx_send_error(client_fd_, 1045, "Invalid AuthenticateStart message");
|
||||
return false;
|
||||
}
|
||||
|
||||
auth_method_ = auth_start.mech_name();
|
||||
|
||||
if (!mysqlx_is_supported_auth_method(auth_method_)) {
|
||||
mysqlx_send_error(client_fd_, 1251,
|
||||
"Authentication method '" + auth_method_ + "' not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auth_method_ == "PLAIN") {
|
||||
// PLAIN: auth_data contains \0user\0password in one shot.
|
||||
std::string username {};
|
||||
std::string password {};
|
||||
if (!parse_plain_auth_data(auth_start.auth_data(), username, password)) {
|
||||
mysqlx_send_error(client_fd_, 1045, "Invalid PLAIN auth data");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto resolved = config_store.resolve_identity(username);
|
||||
if (!resolved.has_value()) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"Access denied for user '" + username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
identity_ = resolved.value();
|
||||
|
||||
if (!identity_.x_enabled) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"X Protocol access not enabled for user '" + username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (identity_.require_tls) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"User '" + username + "' requires TLS for X Protocol access");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_auth_method_allowed("PLAIN", identity_.allowed_auth_methods)) {
|
||||
mysqlx_send_error(client_fd_, 1251,
|
||||
"Authentication method 'PLAIN' not allowed for user '" + username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (identity_.backend_auth_mode == MysqlxBackendAuthMode::pass_through) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"pass_through backend auth mode not supported in Phase 1");
|
||||
return false;
|
||||
}
|
||||
|
||||
// For PLAIN, verify password against backend_password using
|
||||
// constant-time comparison to prevent timing side-channels.
|
||||
bool pwd_match = false;
|
||||
if (!password.empty() && !identity_.backend_password.empty() &&
|
||||
password.size() == identity_.backend_password.size()) {
|
||||
pwd_match = CRYPTO_memcmp(password.data(), identity_.backend_password.data(),
|
||||
password.size()) == 0;
|
||||
}
|
||||
if (!pwd_match) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"Access denied for user '" + username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send AuthenticateOk.
|
||||
Mysqlx::Session::AuthenticateOk auth_ok;
|
||||
std::string serialized;
|
||||
auth_ok.SerializeToString(&serialized);
|
||||
auto frame = mysqlx_build_frame(
|
||||
Mysqlx::ServerMessages_Type_SESS_AUTHENTICATE_OK,
|
||||
serialized
|
||||
);
|
||||
if (!mysqlx_write_all(client_fd_, frame.data(), frame.size())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auth_method_ == "MYSQL41") {
|
||||
// MYSQL41 phase 1: parse schema+user, send challenge.
|
||||
std::string schema {};
|
||||
std::string username {};
|
||||
if (!parse_mysql41_auth_data(auth_start.auth_data(), schema, username)) {
|
||||
mysqlx_send_error(client_fd_, 1045, "Invalid MYSQL41 auth data");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto resolved = config_store.resolve_identity(username);
|
||||
if (!resolved.has_value()) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"Access denied for user '" + username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
identity_ = resolved.value();
|
||||
|
||||
if (!identity_.x_enabled) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"X Protocol access not enabled for user '" + username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (identity_.require_tls) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"User '" + username + "' requires TLS for X Protocol access");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_auth_method_allowed("MYSQL41", identity_.allowed_auth_methods)) {
|
||||
mysqlx_send_error(client_fd_, 1251,
|
||||
"Authentication method 'MYSQL41' not allowed for user '" + username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (identity_.backend_auth_mode == MysqlxBackendAuthMode::pass_through) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"pass_through backend auth mode not supported in Phase 1");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send AuthenticateContinue with challenge.
|
||||
if (!send_auth_continue_challenge()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for AuthenticateContinue from client with scrambled response.
|
||||
return handle_authenticate(config_store);
|
||||
}
|
||||
|
||||
mysqlx_send_error(client_fd_, 1251, "Unsupported auth method");
|
||||
return false;
|
||||
}
|
||||
|
||||
case Mysqlx::ClientMessages_Type_CON_CLOSE:
|
||||
return false;
|
||||
|
||||
default:
|
||||
mysqlx_send_error(client_fd_, 5000,
|
||||
"Unexpected message during handshake");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MysqlxFrontendSession::handle_capabilities_get() {
|
||||
return send_capabilities();
|
||||
}
|
||||
|
||||
bool MysqlxFrontendSession::handle_capabilities_set(const std::vector<uint8_t>& payload) {
|
||||
Mysqlx::Connection::CapabilitiesSet cap_set;
|
||||
if (!cap_set.ParseFromArray(payload.data(), static_cast<int>(payload.size()))) {
|
||||
mysqlx_send_error(client_fd_, 5001, "Invalid CapabilitiesSet message");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 1: reject TLS requests explicitly since we don't implement it yet.
|
||||
if (cap_set.has_capabilities()) {
|
||||
for (const auto& cap : cap_set.capabilities().capabilities()) {
|
||||
if (cap.name() == "tls") {
|
||||
mysqlx_send_error(client_fd_, 5001,
|
||||
"TLS capability not supported by mysqlx plugin in Phase 1");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mysqlx_send_ok(client_fd_);
|
||||
}
|
||||
|
||||
bool MysqlxFrontendSession::send_capabilities() {
|
||||
Mysqlx::Connection::Capabilities caps;
|
||||
|
||||
// Advertise supported auth methods.
|
||||
auto* cap_auth = caps.add_capabilities();
|
||||
cap_auth->set_name("authentication.mechanisms");
|
||||
auto* auth_array = cap_auth->mutable_value()->mutable_array();
|
||||
|
||||
auto* mysql41 = auth_array->add_value();
|
||||
mysql41->set_type(Mysqlx::Datatypes::Any::SCALAR);
|
||||
mysql41->mutable_scalar()->set_type(Mysqlx::Datatypes::Scalar::V_STRING);
|
||||
mysql41->mutable_scalar()->mutable_v_string()->set_value("MYSQL41");
|
||||
|
||||
auto* plain = auth_array->add_value();
|
||||
plain->set_type(Mysqlx::Datatypes::Any::SCALAR);
|
||||
plain->mutable_scalar()->set_type(Mysqlx::Datatypes::Scalar::V_STRING);
|
||||
plain->mutable_scalar()->mutable_v_string()->set_value("PLAIN");
|
||||
|
||||
std::string serialized;
|
||||
caps.SerializeToString(&serialized);
|
||||
|
||||
auto frame = mysqlx_build_frame(
|
||||
Mysqlx::ServerMessages_Type_CONN_CAPABILITIES,
|
||||
serialized
|
||||
);
|
||||
|
||||
return mysqlx_write_all(client_fd_, frame.data(), frame.size());
|
||||
}
|
||||
|
||||
bool MysqlxFrontendSession::send_auth_continue_challenge() {
|
||||
Mysqlx::Session::AuthenticateContinue auth_continue;
|
||||
auth_continue.set_auth_data(
|
||||
std::string(auth_challenge_.begin(), auth_challenge_.end())
|
||||
);
|
||||
|
||||
std::string serialized;
|
||||
auth_continue.SerializeToString(&serialized);
|
||||
|
||||
auto frame = mysqlx_build_frame(
|
||||
Mysqlx::ServerMessages_Type_SESS_AUTHENTICATE_CONTINUE,
|
||||
serialized
|
||||
);
|
||||
|
||||
return mysqlx_write_all(client_fd_, frame.data(), frame.size());
|
||||
}
|
||||
|
||||
bool MysqlxFrontendSession::handle_authenticate(MysqlxConfigStore& /* config_store */) {
|
||||
// Read AuthenticateContinue from client.
|
||||
MysqlxFrameHeader header {};
|
||||
std::vector<uint8_t> payload {};
|
||||
|
||||
if (!mysqlx_read_frame(client_fd_, header, payload)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.message_type != Mysqlx::ClientMessages_Type_SESS_AUTHENTICATE_CONTINUE) {
|
||||
mysqlx_send_error(client_fd_, 1045, "Expected AuthenticateContinue");
|
||||
return false;
|
||||
}
|
||||
|
||||
Mysqlx::Session::AuthenticateContinue auth_continue;
|
||||
if (!auth_continue.ParseFromArray(payload.data(), static_cast<int>(payload.size()))) {
|
||||
mysqlx_send_error(client_fd_, 1045, "Invalid AuthenticateContinue");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Client response is formatted as: schema\0username\0*HEX(scramble)
|
||||
// Parse out the hex scramble after the '*' marker.
|
||||
const std::string& response_str = auth_continue.auth_data();
|
||||
auto star_pos = response_str.find('*');
|
||||
if (star_pos == std::string::npos || star_pos + 1 >= response_str.size()) {
|
||||
mysqlx_send_error(client_fd_, 1045, "Invalid MYSQL41 response format");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string hex_part = response_str.substr(star_pos + 1);
|
||||
std::vector<uint8_t> client_response {};
|
||||
if (!mysqlx_hex_decode(hex_part, client_response)) {
|
||||
mysqlx_send_error(client_fd_, 1045, "Invalid MYSQL41 hex scramble");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify MYSQL41 scramble against the stored password.
|
||||
if (!mysqlx_mysql41_verify(auth_challenge_, client_response, identity_.backend_password)) {
|
||||
mysqlx_send_error(client_fd_, 1045,
|
||||
"Access denied for user '" + identity_.username + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Authentication succeeded — send AuthenticateOk.
|
||||
Mysqlx::Session::AuthenticateOk auth_ok;
|
||||
std::string serialized;
|
||||
auth_ok.SerializeToString(&serialized);
|
||||
|
||||
auto frame = mysqlx_build_frame(
|
||||
Mysqlx::ServerMessages_Type_SESS_AUTHENTICATE_OK,
|
||||
serialized
|
||||
);
|
||||
|
||||
return mysqlx_write_all(client_fd_, frame.data(), frame.size());
|
||||
}
|
||||
Loading…
Reference in new issue