class CommandPacket(Packet): ''' Implementation based on description: https://mariadb.com/kb/en/library/1-connecting-connecting/#initial-handshake-packet ''' def setup(self, length=0, count_header=1, body=''): if length == 0: return # self.salt=self.session.salt self._length = length self._seq = count_header self._body = body self.type = Datum('int<1>') buffer = body buffer = self.type.setFromBuff(buffer) if self.type.value == COMMANDS.COM_QUERY: self.sql = Datum('str<EOF>') self.sql.setFromBuff(buffer) else: self.data = Datum('str<EOF>') self.data.setFromBuff(buffer) def __str__(self): return str({ 'header': { 'length': self.length, 'seq': self.seq }, 'type': getConstName(COMMANDS, self.type.value), 'vars': self.__dict__ })
class PasswordAnswer(Packet): def setup(self, length=0, count_header=1, body=''): length = len(body) if length == 0: return self.password = Datum('string<NUL>') buffer = body buffer = self.password.setFromBuff(buffer)
class SwitchOutResponse(Packet): def setup(self, length=0, count_header=1, body=''): length = len(body) if length == 0: return self.enc_password = Datum('string<EOF>') # 0x04 buffer = body buffer = self.enc_password.setFromBuff(buffer)
def read_params(self, buffer, num_params): if not num_params > 0: return # read null-map null_bytes = math.floor((num_params + 7) / 8) nulls = [] for i in range(null_bytes): b, buffer = self._read_byte(buffer) for i in range(8): nulls.append(((1 << i) & b) != 0) # read send-type byte b, buffer = self._read_byte(buffer) types = [] if b == 1: # read types for i in range(num_params): t, buffer = self._read_byte(buffer) s, buffer = self._read_byte(buffer) types.append(dict(type=t, signed=s)) datumtypes = { TYPES.MYSQL_TYPE_VAR_STRING: 'string<lenenc>', TYPES.MYSQL_TYPE_STRING: 'string<lenenc>', TYPES.MYSQL_TYPE_VARCHAR: 'string<lenenc>', TYPES.MYSQL_TYPE_TINY: 'int<1>', TYPES.MYSQL_TYPE_SHORT: 'int<2>', TYPES.MYSQL_TYPE_LONG: 'int<4>', TYPES.MYSQL_TYPE_LONGLONG: 'int<8>', } for i in range(num_params): if nulls[i]: self.parameters.append(None) continue datum_type = datumtypes.get(types[i]['type']) if datum_type is not None: x = Datum(datum_type) buffer = x.setFromBuff(buffer) value = x.value if isinstance(value, bytes): value = value.decode() self.parameters.append(value) else: # NOTE at this moment all sends as strings and it works raise Exception(f"Unsupported type {types[i]['type']}")
class CommandPacket(Packet): ''' Implementation based on description: https://mariadb.com/kb/en/library/1-connecting-connecting/#initial-handshake-packet ''' def _read_byte(self, buffer): b = buffer[:1] buffer = buffer[1:] b = struct.unpack('<B', b)[0] return b, buffer def read_params(self, buffer, num_params): if not num_params > 0: return # read null-map null_bytes = math.floor((num_params + 7) / 8) nulls = [] for i in range(null_bytes): b, buffer = self._read_byte(buffer) for i in range(8): nulls.append(((1 << i) & b) != 0) # read send-type byte b, buffer = self._read_byte(buffer) types = [] if b == 1: # read types for i in range(num_params): t, buffer = self._read_byte(buffer) s, buffer = self._read_byte(buffer) types.append(dict(type=t, signed=s)) datumtypes = { TYPES.MYSQL_TYPE_VAR_STRING: 'string<lenenc>', TYPES.MYSQL_TYPE_STRING: 'string<lenenc>', TYPES.MYSQL_TYPE_VARCHAR: 'string<lenenc>', TYPES.MYSQL_TYPE_TINY: 'int<1>', TYPES.MYSQL_TYPE_SHORT: 'int<2>', TYPES.MYSQL_TYPE_LONG: 'int<4>', TYPES.MYSQL_TYPE_LONGLONG: 'int<8>', } for i in range(num_params): if nulls[i]: self.parameters.append(None) continue datum_type = datumtypes.get(types[i]['type']) if datum_type is not None: x = Datum(datum_type) buffer = x.setFromBuff(buffer) value = x.value if isinstance(value, bytes): value = value.decode() self.parameters.append(value) else: # NOTE at this moment all sends as strings and it works raise Exception(f"Unsupported type {types[i]['type']}") def setup(self, length=0, count_header=1, body=''): if length == 0: return # self.salt=self.session.salt self._length = length self._seq = count_header self._body = body self.type = Datum('int<1>') buffer = body buffer = self.type.setFromBuff(buffer) if self.type.value in (COMMANDS.COM_QUERY, COMMANDS.COM_STMT_PREPARE): self.sql = Datum('str<EOF>') buffer = self.sql.setFromBuff(buffer) elif self.type.value == COMMANDS.COM_STMT_EXECUTE: # https://mariadb.com/kb/en/com_stmt_execute/ self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) self.flags = Datum('int<1>') buffer = self.flags.setFromBuff(buffer) self.iteration_count = Datum('int<4>') buffer = self.iteration_count.setFromBuff(buffer) self.parameters = [] prepared_stmt = self.session.prepared_stmts[self.stmt_id.value] if prepared_stmt['type'] == 'select': num_params = len(prepared_stmt['statement'].parameters) self.read_params(buffer, num_params) elif prepared_stmt['type'] in ['insert', 'delete']: # if prepared_stmt['type'] == 'insert': # prepared_stmt['statement'].sql # statement = parse_sql(prepared_stmt['statement'].sql) # num_params = 0 # for row in statement.values: # for item in row: # if isinstance(item, Parameter): # num_params = num_params + 1 # elif prepared_stmt['type'] == 'delete': # num_params = prepared_stmt['statement'].sql.count('?') num_params = len(prepared_stmt['statement'].parameters) self.read_params(buffer, num_params) elif self.type.value == COMMANDS.COM_STMT_CLOSE: self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) elif self.type.value == COMMANDS.COM_STMT_FETCH: self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) self.limit = Datum('int<4>') buffer = self.limit.setFromBuff(buffer) elif self.type.value == COMMANDS.COM_INIT_DB: self.database = Datum('str<EOF>') buffer = self.database.setFromBuff(buffer) else: self.data = Datum('str<EOF>') buffer = self.data.setFromBuff(buffer) def __str__(self): return str({ 'header': { 'length': self.length, 'seq': self.seq }, 'type': getConstName(COMMANDS, self.type.value), 'vars': self.__dict__ })
def setup(self, length=0, count_header=1, body=''): length = len(body) if length == 0: return self.salt = self.proxy.salt self._length = length self._seq = count_header self._body = body self.scramble_func = scramble self.capabilities = Datum('int<4>') self.max_packet_size = Datum('int<4>') self.reserved = Datum('string<23>') self.username = Datum('string<NUL>') self.enc_password = Datum('string<NUL>') self.database = Datum('string<NUL>') self.charset = Datum('int<1>') self.client_auth_plugin = Datum('string<NUL>') buffer = body if len(body) == 32 and body[9:] == (b'\x00' * 23): self.type = 'SSLRequest' buffer = self.capabilities.setFromBuff(buffer) buffer = self.max_packet_size.setFromBuff(buffer) buffer = self.charset.setFromBuff(buffer) else: self.type = 'HandshakeResponse' buffer = self.capabilities.setFromBuff(buffer) capabilities = ClentCapabilities(self.capabilities.value) buffer = self.max_packet_size.setFromBuff(buffer) buffer = self.charset.setFromBuff(buffer) buffer = self.reserved.setFromBuff(buffer) buffer = self.username.setFromBuff(buffer) if server_capabilities.has(CAPABILITIES.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) \ and capabilities.PLUGIN_AUTH_LENENC_CLIENT_DATA: self.enc_password = Datum('string<lenenc>') buffer = self.enc_password.setFromBuff(buffer) elif server_capabilities.has(CAPABILITIES.CLIENT_SECURE_CONNECTION) \ and capabilities.SECURE_CONNECTION: self.auth_resp_len = Datum('int<1>') buffer = self.auth_resp_len.setFromBuff(buffer) self.enc_password = Datum( f'string<{self.auth_resp_len.value}>') buffer = self.enc_password.setFromBuff(buffer) else: pass_byte = Datum('int<1>') buffer = pass_byte.setFromBuff(buffer) if capabilities.CONNECT_WITH_DB: buffer = self.database.setFromBuff(buffer) if capabilities.PLUGIN_AUTH: buffer = self.client_auth_plugin.setFromBuff(buffer) # at the end is CLIENT_CONNECT_ATTRS, but we dont use it and dont parse self.session.username = self.username.value
class HandshakeResponsePacket(Packet): ''' Implementation based on description: https://mariadb.com/kb/en/library/1-connecting-connecting/#initial-handshake-packet ''' def setup(self, length=0, count_header=1, body=''): length = len(body) if length == 0: return self.salt = self.proxy.salt self._length = length self._seq = count_header self._body = body self.scramble_func = scramble self.capabilities = Datum('int<4>') self.max_packet_size = Datum('int<4>') self.reserved = Datum('string<23>') self.username = Datum('string<NUL>') self.enc_password = Datum('string<NUL>') self.database = Datum('string<NUL>') self.charset = Datum('int<1>') self.client_auth_plugin = Datum('string<NUL>') buffer = body if len(body) == 32 and body[9:] == (b'\x00' * 23): self.type = 'SSLRequest' buffer = self.capabilities.setFromBuff(buffer) buffer = self.max_packet_size.setFromBuff(buffer) buffer = self.charset.setFromBuff(buffer) else: self.type = 'HandshakeResponse' buffer = self.capabilities.setFromBuff(buffer) capabilities = ClentCapabilities(self.capabilities.value) buffer = self.max_packet_size.setFromBuff(buffer) buffer = self.charset.setFromBuff(buffer) buffer = self.reserved.setFromBuff(buffer) buffer = self.username.setFromBuff(buffer) if server_capabilities.has(CAPABILITIES.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) \ and capabilities.PLUGIN_AUTH_LENENC_CLIENT_DATA: self.enc_password = Datum('string<lenenc>') buffer = self.enc_password.setFromBuff(buffer) elif server_capabilities.has(CAPABILITIES.CLIENT_SECURE_CONNECTION) \ and capabilities.SECURE_CONNECTION: self.auth_resp_len = Datum('int<1>') buffer = self.auth_resp_len.setFromBuff(buffer) self.enc_password = Datum( f'string<{self.auth_resp_len.value}>') buffer = self.enc_password.setFromBuff(buffer) else: pass_byte = Datum('int<1>') buffer = pass_byte.setFromBuff(buffer) if capabilities.CONNECT_WITH_DB: buffer = self.database.setFromBuff(buffer) if capabilities.PLUGIN_AUTH: buffer = self.client_auth_plugin.setFromBuff(buffer) # at the end is CLIENT_CONNECT_ATTRS, but we dont use it and dont parse self.session.username = self.username.value def __str__(self): return str({ 'header': { 'length': self.length, 'seq': self.seq }, 'username': self.username.value, 'password': self.enc_password.value, 'database': self.database.value })
def setup(self, length=0, count_header=1, body=''): if length == 0: return # self.salt=self.session.salt self._length = length self._seq = count_header self._body = body self.type = Datum('int<1>') buffer = body buffer = self.type.setFromBuff(buffer) if self.type.value in (COMMANDS.COM_QUERY, COMMANDS.COM_STMT_PREPARE): self.sql = Datum('str<EOF>') buffer = self.sql.setFromBuff(buffer) elif self.type.value == COMMANDS.COM_STMT_EXECUTE: # https://mariadb.com/kb/en/com_stmt_execute/ self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) self.flags = Datum('int<1>') buffer = self.flags.setFromBuff(buffer) self.iteration_count = Datum('int<4>') buffer = self.iteration_count.setFromBuff(buffer) self.parameters = [] prepared_stmt = self.session.prepared_stmts[self.stmt_id.value] if prepared_stmt['type'] in ['insert', 'delete']: if prepared_stmt['type'] == 'insert': num_params = prepared_stmt['statement'].struct[ 'values'].count(SQL_PARAMETER) elif prepared_stmt['type'] == 'delete': num_params = prepared_stmt['statement'].sql.count('?') if num_params > 0: # read null-map null_bytes = math.floor((num_params + 7) / 8) nulls = [] for i in range(null_bytes): b, buffer = self._read_byte(buffer) for i in range(8): nulls.append(((1 << i) & b) != 0) # read send-type byte b, buffer = self._read_byte(buffer) types = [] if b == 1: # read types for i in range(num_params): t, buffer = self._read_byte(buffer) s, buffer = self._read_byte(buffer) types.append(dict(type=t, signed=s)) self.parameters = [] for i in range(num_params): if nulls[i]: self.parameters.append(None) continue if types[i]['type'] in (TYPES.MYSQL_TYPE_VAR_STRING, TYPES.MYSQL_TYPE_STRING): x = Datum('string<lenenc>') buffer = x.setFromBuff(buffer) self.parameters.append(x.value.decode()) else: # NOTE at this moment all sends as strings and it works raise Exception( f"Unsupported type {types[i]['type']}") elif self.type.value == COMMANDS.COM_STMT_CLOSE: self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) elif self.type.value == COMMANDS.COM_STMT_FETCH: self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) self.limit = Datum('int<4>') buffer = self.limit.setFromBuff(buffer) else: self.data = Datum('str<EOF>') buffer = self.data.setFromBuff(buffer)
class CommandPacket(Packet): ''' Implementation based on description: https://mariadb.com/kb/en/library/1-connecting-connecting/#initial-handshake-packet ''' def _read_byte(self, buffer): b = buffer[:1] buffer = buffer[1:] b = struct.unpack('<B', b)[0] return b, buffer def setup(self, length=0, count_header=1, body=''): if length == 0: return # self.salt=self.session.salt self._length = length self._seq = count_header self._body = body self.type = Datum('int<1>') buffer = body buffer = self.type.setFromBuff(buffer) if self.type.value in (COMMANDS.COM_QUERY, COMMANDS.COM_STMT_PREPARE): self.sql = Datum('str<EOF>') buffer = self.sql.setFromBuff(buffer) elif self.type.value == COMMANDS.COM_STMT_EXECUTE: # https://mariadb.com/kb/en/com_stmt_execute/ self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) self.flags = Datum('int<1>') buffer = self.flags.setFromBuff(buffer) self.iteration_count = Datum('int<4>') buffer = self.iteration_count.setFromBuff(buffer) self.parameters = [] statement = self.session.statements[self.stmt_id.value] if statement['type'] in ['insert', 'delete']: num_params = len(statement['insert'] ) if statement['type'] == 'insert' else 1 if num_params > 0: # read null-map null_bytes = math.floor((num_params + 7) / 8) nulls = [] for i in range(null_bytes): b, buffer = self._read_byte(buffer) for i in range(8): nulls.append(((1 << i) & b) != 0) # read send-type byte b, buffer = self._read_byte(buffer) types = [] if b == 1: # read types for i in range(num_params): t, buffer = self._read_byte(buffer) s, buffer = self._read_byte(buffer) types.append(dict(type=t, signed=s)) self.parameters = [] for i in range(num_params): if nulls[i]: self.parameters.append(None) continue if types[i]['type'] in (TYPES.MYSQL_TYPE_VAR_STRING, TYPES.MYSQL_TYPE_STRING): x = Datum('string<lenenc>') buffer = x.setFromBuff(buffer) self.parameters.append(x.value.decode()) else: # NOTE at this moment all sends as strings and it works raise Exception( f"Unsupported type {types[i]['type']}") elif self.type.value == COMMANDS.COM_STMT_CLOSE: self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) elif self.type.value == COMMANDS.COM_STMT_FETCH: self.stmt_id = Datum('int<4>') buffer = self.stmt_id.setFromBuff(buffer) self.limit = Datum('int<4>') buffer = self.limit.setFromBuff(buffer) else: self.data = Datum('str<EOF>') buffer = self.data.setFromBuff(buffer) def __str__(self): return str({ 'header': { 'length': self.length, 'seq': self.seq }, 'type': getConstName(COMMANDS, self.type.value), 'vars': self.__dict__ })