# Python implementation of the MySQL client-server protocol # http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol import re try: import hashlib sha_new = lambda *args, **kwargs: hashlib.new("sha1", *args, **kwargs) except ImportError: import sha sha_new = sha.new import socket import ssl import struct import sys import os import ConfigParser try: import cStringIO as StringIO except ImportError: import StringIO from charset import MBLENGTH from cursors import Cursor from constants import FIELD_TYPE from constants import SERVER_STATUS from constants.CLIENT import * from constants.COMMAND import * from converters import escape_item, encoders, decoders, field_decoders from err import raise_mysql_exception, Warning, Error, \ InterfaceError, DataError, DatabaseError, OperationalError, \ IntegrityError, InternalError, NotSupportedError, ProgrammingError DEBUG = False NULL_COLUMN = 251 UNSIGNED_CHAR_COLUMN = 251 UNSIGNED_SHORT_COLUMN = 252 UNSIGNED_INT24_COLUMN = 253 UNSIGNED_INT64_COLUMN = 254 UNSIGNED_CHAR_LENGTH = 1 UNSIGNED_SHORT_LENGTH = 2 UNSIGNED_INT24_LENGTH = 3 UNSIGNED_INT64_LENGTH = 8 DEFAULT_CHARSET = 'latin1' MAX_PACKET_LENGTH = 256*256*256-1 def dump_packet(data): def is_ascii(data): if data.isalnum(): return data return '.' print "packet length %d" % len(data) print "method call[1]: %s" % sys._getframe(1).f_code.co_name print "method call[2]: %s" % sys._getframe(2).f_code.co_name print "method call[3]: %s" % sys._getframe(3).f_code.co_name print "method call[4]: %s" % sys._getframe(4).f_code.co_name print "method call[5]: %s" % sys._getframe(5).f_code.co_name print "-" * 88 dump_data = [data[i:i+16] for i in xrange(len(data)) if i%16 == 0] for d in dump_data: print ' '.join(map(lambda x:"%02X" % ord(x), d)) + \ ' ' * (16 - len(d)) + ' ' * 2 + ' '.join(map(lambda x:"%s" % is_ascii(x), d)) print "-" * 88 print "" def _scramble(password, message): if password == None or len(password) == 0: return '\0' if DEBUG: print 'password=' + password stage1 = sha_new(password).digest() stage2 = sha_new(stage1).digest() s = sha_new() s.update(message) s.update(stage2) result = s.digest() return _my_crypt(result, stage1) def _my_crypt(message1, message2): length = len(message1) result = struct.pack('B', length) for i in xrange(length): x = (struct.unpack('B', message1[i:i+1])[0] ^ struct.unpack('B', message2[i:i+1])[0]) result += struct.pack('B', x) return result # old_passwords support ported from libmysql/password.c # note: the following functions do not work! they are very close, though. SCRAMBLE_LENGTH_323 = 8 class RandStruct_323(object): def __init__(self, seed1, seed2): self.max_value = 0x3FFFFFFFL self.seed1 = seed1 % self.max_value self.seed2 = seed2 % self.max_value def my_rnd(self): self.seed1 = (self.seed1 * 3L + self.seed2) % self.max_value self.seed2 = (self.seed1 + self.seed2 + 33L) % self.max_value return float(self.seed1) / float(self.max_value) def _scramble_323(password, message): hash_pass = _hash_password_323(password) hash_message = _hash_password_323(message[:SCRAMBLE_LENGTH_323]) hash_pass_n = struct.unpack(">LL", hash_pass) hash_message_n = struct.unpack(">LL", hash_message) rand_st = RandStruct_323(hash_pass_n[0] ^ hash_message_n[0], hash_pass_n[1] ^ hash_message_n[1]) outbuf = StringIO.StringIO() for _ in xrange(min(SCRAMBLE_LENGTH_323, len(message))): outbuf.write(chr(int(rand_st.my_rnd() * 31) + 64)) extra = chr(int(rand_st.my_rnd() * 31)) out = outbuf.getvalue() outbuf = StringIO.StringIO() for c in out: outbuf.write(chr(ord(c) ^ ord(extra))) return outbuf.getvalue() def _hash_password_323(password): nr = 1345345333L add = 7L nr2 = 0x12345671L for c in [ord(x) for x in password if x not in (' ', '\t')]: nr^= (((nr & 63)+add)*c)+ (nr << 8) & 0xFFFFFFFF nr2= (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF add= (add + c) & 0xFFFFFFFF r1 = nr & ((1L << 31) - 1L) # kill sign bits r2 = nr2 & ((1L << 31) - 1L) # pack return struct.pack(">LL", r1, r2) def pack_int24(n): return struct.pack('BBB', n&0xFF, (n>>8)&0xFF, (n>>16)&0xFF) def unpack_uint16(n): return struct.unpack(' 0: recv_data = socket.recv(bytes_to_read) if len(recv_data) == 0: raise OperationalError(2013, "Lost connection to MySQL server during query") if DEBUG: dump_packet(recv_data) payload_buff.append(recv_data) bytes_to_read -= len(recv_data) self.__data = ''.join(payload_buff) def packet_number(self): return self.__packet_number def get_all_data(self): return self.__data def read(self, size): """Read the first 'size' bytes in packet and advance cursor past them.""" result = self.peek(size) self.advance(size) return result def read_all(self): """Read all remaining data in the packet. (Subsequent read() or peek() will return errors.) """ result = self.__data[self.__position:] self.__position = None # ensure no subsequent read() or peek() return result def advance(self, length): """Advance the cursor in data buffer 'length' bytes.""" new_position = self.__position + length if new_position < 0 or new_position > len(self.__data): raise Exception('Invalid advance amount (%s) for cursor. ' 'Position=%s' % (length, new_position)) self.__position = new_position def rewind(self, position=0): """Set the position of the data buffer cursor to 'position'.""" if position < 0 or position > len(self.__data): raise Exception("Invalid position to rewind cursor to: %s." % position) self.__position = position def peek(self, size): """Look at the first 'size' bytes in packet without moving cursor.""" result = self.__data[self.__position:(self.__position+size)] if len(result) != size: error = ('Result length not requested length:\n' 'Expected=%s. Actual=%s. Position: %s. Data Length: %s' % (size, len(result), self.__position, len(self.__data))) if DEBUG: print error self.dump() raise AssertionError(error) return result def get_bytes(self, position, length=1): """Get 'length' bytes starting at 'position'. Position is start of payload (first four packet header bytes are not included) starting at index '0'. No error checking is done. If requesting outside end of buffer an empty string (or string shorter than 'length') may be returned! """ return self.__data[position:(position+length)] def read_coded_length(self): """Read a 'Length Coded' number from the data buffer. Length coded numbers can be anywhere from 1 to 9 bytes depending on the value of the first byte. """ c = ord(self.read(1)) if c == NULL_COLUMN: return None if c < UNSIGNED_CHAR_COLUMN: return c elif c == UNSIGNED_SHORT_COLUMN: return unpack_uint16(self.read(UNSIGNED_SHORT_LENGTH)) elif c == UNSIGNED_INT24_COLUMN: return unpack_int24(self.read(UNSIGNED_INT24_LENGTH)) elif c == UNSIGNED_INT64_COLUMN: # TODO: what was 'longlong'? confirm it wasn't used? return unpack_int64(self.read(UNSIGNED_INT64_LENGTH)) def read_length_coded_binary(self): """Read a 'Length Coded Binary' from the data buffer. A 'Length Coded Binary' consists first of a length coded (unsigned, positive) integer represented in 1-9 bytes followed by that many bytes of binary data. (For example "cat" would be "3cat".) """ length = self.read_coded_length() if length: return self.read(length) def is_ok_packet(self): return ord(self.get_bytes(0)) == 0 def is_eof_packet(self): return ord(self.get_bytes(0)) == 254 # 'fe' def is_resultset_packet(self): field_count = ord(self.get_bytes(0)) return field_count >= 1 and field_count <= 250 def is_error_packet(self): return ord(self.get_bytes(0)) == 255 def check_error(self): if self.is_error_packet(): self.rewind() self.advance(1) # field_count == error (we already know that) errno = unpack_uint16(self.read(2)) if DEBUG: print "errno = %d" % errno raise_mysql_exception(self.__data) def dump(self): dump_packet(self.__data) class FieldDescriptorPacket(MysqlPacket): """A MysqlPacket that represents a specific column's metadata in the result. Parsing is automatically done and the results are exported via public attributes on the class such as: db, table_name, name, length, type_code. """ def __init__(self, *args): MysqlPacket.__init__(self, *args) self.__parse_field_descriptor() def __parse_field_descriptor(self): """Parse the 'Field Descriptor' (Metadata) packet. This is compatible with MySQL 4.1+ (not compatible with MySQL 4.0). """ self.catalog = self.read_length_coded_binary() self.db = self.read_length_coded_binary() self.table_name = self.read_length_coded_binary() self.org_table = self.read_length_coded_binary() self.name = self.read_length_coded_binary() self.org_name = self.read_length_coded_binary() self.advance(1) # non-null filler self.charsetnr = struct.unpack('= MAX_PACKET_LENGTH: header = struct.pack('= i + 1: i += 1 self.server_capabilities = struct.unpack('= i+12-1: rest_salt = data[i:i+12] self.salt += rest_salt def get_server_info(self): return self.server_version Warning = Warning Error = Error InterfaceError = InterfaceError DatabaseError = DatabaseError DataError = DataError OperationalError = OperationalError IntegrityError = IntegrityError InternalError = InternalError ProgrammingError = ProgrammingError NotSupportedError = NotSupportedError # TODO: move OK and EOF packet parsing/logic into a proper subclass # of MysqlPacket like has been done with FieldDescriptorPacket. class MySQLResult(object): def __init__(self, connection): from weakref import proxy self.connection = proxy(connection) self.affected_rows = None self.insert_id = None self.server_status = 0 self.warning_count = 0 self.message = None self.field_count = 0 self.description = None self.rows = None self.has_next = None def read(self): self.first_packet = self.connection.read_packet() # TODO: use classes for different packet types? if self.first_packet.is_ok_packet(): self._read_ok_packet() else: self._read_result_packet() def _read_ok_packet(self): self.first_packet.advance(1) # field_count (always '0') self.affected_rows = self.first_packet.read_coded_length() self.insert_id = self.first_packet.read_coded_length() self.server_status = struct.unpack('