mysql: Implement and test COM_CHANGE_USER

This reworks the parser such that COM_CHANGE_USER switches the
connection back into the CONNECTION_PHASE so that we can remove the
EXPECT_AUTH_SWITCH special case in the COMMAND_PHASE. Adds two pcaps
produced with Python that actually do COM_CHANGE_USER as it seems
not possible from the MySQL CLI.
This commit is contained in:
Arne Welzel 2024-08-13 17:29:36 +02:00
parent a4c79e7304
commit 02f4665e9b
12 changed files with 275 additions and 18 deletions

View file

@ -154,7 +154,6 @@ enum Expected {
EXPECT_EOF_THEN_RESULTSET,
EXPECT_RESULTSET,
EXPECT_REST_OF_PACKET,
EXPECT_AUTH_SWITCH,
};
enum EOFType {
@ -297,7 +296,7 @@ type MySQL_PDU(is_orig: bool) = record {
hdr : Header;
msg : case is_orig of {
false -> server_msg: Server_Message(hdr.seq_id, hdr.len, state);
true -> client_msg: Client_Message(state);
true -> client_msg: Client_Message(hdr.len, state);
} &requires(state);
} &let {
state: int = $context.connection.get_state();
@ -377,7 +376,7 @@ type Server_Connection_Phase_Packets = record {
packet: case pkt_type of {
0x00 -> data_ok: OK_Packet;
0x01 -> auth_more_data: AuthMoreData(false);
0xfe -> auth_switch_request: AuthSwitchRequestPayload;
0xfe -> auth_switch_request: AuthSwitchRequest;
0xff -> data_err: ERR_Packet;
};
};
@ -443,6 +442,7 @@ type Handshake_Response_Packet_v10 = record {
} &let {
deprecate_eof: bool = $context.connection.set_deprecate_eof(cap_flags & CLIENT_DEPRECATE_EOF);
client_query_attrs: bool = $context.connection.set_client_query_attrs(cap_flags & CLIENT_QUERY_ATTRIBUTES);
proc_cap_flags: bool = $context.connection.set_client_capabilities(cap_flags);
};
type Handshake_Response_Packet_v9 = record {
@ -459,9 +459,9 @@ type Handshake_Response_Packet_v9 = record {
# Connection Phase
type Client_Message(state: int) = case state of {
type Client_Message(pkt_len: uint32, state: int) = case state of {
CONNECTION_PHASE -> connection_phase: Connection_Phase_Packets;
COMMAND_PHASE -> command_phase : Command_Request_Packet;
COMMAND_PHASE -> command_phase : Command_Request_Packet(pkt_len);
};
type Connection_Phase_Packets = case $context.connection.get_conn_expectation() of {
@ -514,17 +514,49 @@ type Query_Attributes = record {
# Command Request
type Command_Request_Packet = record {
type Command_Request_Packet(pkt_len: uint32) = record {
command: uint8;
attrs : case ( command == COM_QUERY && $context.connection.get_client_query_attrs() && $context.connection.get_server_query_attrs() ) of {
true -> query_attrs: Query_Attributes;
false -> none: empty;
};
have_change_user: case is_change_user of {
true -> change_user: Change_User_Packet(pkt_len);
false -> none_change_user: empty;
};
arg : bytestring &restofdata;
} &let {
is_change_user = command == COM_CHANGE_USER;
update_expectation: bool = $context.connection.set_next_expected_from_command(command);
};
# Command from the client to switch the user mid-session.
#
# https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_change_user.html
type Change_User_Packet(pkt_len: uint32) = record {
username : NUL_String;
auth_plugin_data_len: uint8;
auth_plugin_data: bytestring &length=auth_plugin_data_len;
database: NUL_String;
charset: uint16;
auth_plugin_name_case: case have_auth_plugin_name of {
true -> auth_plugin_name: NUL_String;
false -> no_more_data1: empty;
};
conn_attrs_case: case have_conn_attrs of {
true -> conn_attrs: Handshake_Connection_Attributes;
false -> no_conn_attrs: empty;
};
} &let {
have_more_data = offsetof(auth_plugin_name_case) < pkt_len;
have_auth_plugin_name = have_more_data && ($context.connection.get_client_capabilities() & CLIENT_PLUGIN_AUTH) == CLIENT_PLUGIN_AUTH;
have_conn_attrs = have_more_data && ($context.connection.get_client_capabilities() & CLIENT_CONNECT_ATTRS) == CLIENT_CONNECT_ATTRS;
} &exportsourcedata;
# Command Response
type Command_Response(pkt_len: uint32) = case $context.connection.get_expectation() of {
@ -534,7 +566,6 @@ type Command_Response(pkt_len: uint32) = case $context.connection.get_expectatio
EXPECT_RESULTSET -> resultset : Resultset(pkt_len);
EXPECT_REST_OF_PACKET -> rest : bytestring &restofdata;
EXPECT_STATUS -> status : Command_Response_Status;
EXPECT_AUTH_SWITCH -> auth_switch : AuthSwitchRequest;
EXPECT_EOF_THEN_RESULTSET -> eof : EOFIfLegacyThenResultset(pkt_len);
default -> unknown : empty;
};
@ -643,11 +674,6 @@ type AuthMoreData(is_orig: bool) = record {
};
type AuthSwitchRequest = record {
status: uint8 &enforce(status==254);
payload: AuthSwitchRequestPayload;
};
type AuthSwitchRequestPayload = record {
name : NUL_String;
data : bytestring &restofdata;
} &let {
@ -708,6 +734,7 @@ refine connection MySQL_Conn += {
bool deprecate_eof_;
bool server_query_attrs_;
bool client_query_attrs_;
uint32 client_capabilities_;
std::string auth_plugin_;
int query_attr_idx_;
%}
@ -797,6 +824,17 @@ refine connection MySQL_Conn += {
return true;
%}
function set_client_capabilities(c: uint32): bool
%{
client_capabilities_ = c;
return true;
%}
function get_client_capabilities(): uint32
%{
return client_capabilities_;
%}
function get_expectation(): Expected
%{
return expected_;
@ -874,8 +912,7 @@ refine connection MySQL_Conn += {
expected_ = EXPECT_STATUS;
break;
case COM_CHANGE_USER:
// XXX: Could we switch into CONNECTION_PHASE instead?
expected_ = EXPECT_AUTH_SWITCH;
update_state(CONNECTION_PHASE);
break;
case COM_BINLOG_DUMP:
expected_ = NO_EXPECTATION;