# Python implementation of the MySQL client-server protocol # http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol try: import hashlib sha_new = lambda *args, **kwargs: hashlib.new("sha1", *args, **kwargs) except ImportError: import sha sha_new = sha.new import socket try: import ssl SSL_ENABLED = True except ImportError: SSL_ENABLED = False import struct import sys import os import ConfigParser try: import cStringIO as StringIO except ImportError: import StringIO from charset import MBLENGTH, charset_by_name, charset_by_id from cursors import Cursor from constants import FIELD_TYPE, FLAG from constants import SERVER_STATUS from constants.CLIENT import * from constants.COMMAND import * from util import join_bytes, byte2int, int2byte from converters import escape_item, encoders, 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 byte2int(data) >= 65 and byte2int(data) <= 122: #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" % byte2int(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 int2byte(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 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(int2byte(int(rand_st.my_rnd() * 31) + 64)) extra = int2byte(int(rand_st.my_rnd() * 31)) out = outbuf.getvalue() outbuf = StringIO.StringIO() for c in out: outbuf.write(int2byte(byte2int(c) ^ byte2int(extra))) return outbuf.getvalue() def _hash_password_323(password): nr = 1345345333L add = 7L nr2 = 0x12345671L for c in [byte2int(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_bytes(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_length_coded_binary(self): """Read a 'Length Coded Binary' 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 = byte2int(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_string(self): """Read a 'Length Coded String' from the data buffer. A 'Length Coded String' 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_length_coded_binary() if length is None: return None return self.read(length) def is_ok_packet(self): return byte2int(self.get_bytes(0)) == 0 def is_eof_packet(self): return byte2int(self.get_bytes(0)) == 254 # 'fe' def is_resultset_packet(self): field_count = byte2int(self.get_bytes(0)) return field_count >= 1 and field_count <= 250 def is_error_packet(self): return byte2int(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_string() self.db = self.read_length_coded_string() self.table_name = self.read_length_coded_string() self.org_table = self.read_length_coded_string() self.name = self.read_length_coded_string() self.org_name = self.read_length_coded_string() self.advance(1) # non-null filler self.charsetnr = struct.unpack(' 2: use_unicode = True if compress or named_pipe: raise NotImplementedError, "compress and named_pipe arguments are not supported" if ssl and (ssl.has_key('capath') or ssl.has_key('cipher')): raise NotImplementedError, 'ssl options capath and cipher are not supported' self.ssl = False if ssl: if not SSL_ENABLED: raise NotImplementedError, "ssl module not found" self.ssl = True client_flag |= SSL for k in ('key', 'cert', 'ca'): v = None if ssl.has_key(k): v = ssl[k] setattr(self, k, v) if read_default_group and not read_default_file: if sys.platform.startswith("win"): read_default_file = "c:\\my.ini" else: read_default_file = "/etc/my.cnf" if read_default_file: if not read_default_group: read_default_group = "client" cfg = ConfigParser.RawConfigParser() cfg.read(os.path.expanduser(read_default_file)) def _config(key, default): try: return cfg.get(read_default_group,key) except: return default user = _config("user",user) passwd = _config("password",passwd) host = _config("host", host) db = _config("db",db) unix_socket = _config("socket",unix_socket) port = _config("port", port) charset = _config("default-character-set", charset) self.host = host self.port = port self.user = user self.password = passwd self.db = db self.unix_socket = unix_socket if charset: self.charset = charset self.use_unicode = True else: self.charset = DEFAULT_CHARSET self.use_unicode = False if use_unicode: self.use_unicode = use_unicode client_flag |= CAPABILITIES client_flag |= MULTI_STATEMENTS if self.db: client_flag |= CONNECT_WITH_DB self.client_flag = client_flag self.cursorclass = cursorclass self.connect_timeout = connect_timeout self._connect() self.messages = [] self.set_charset(charset) self.encoders = encoders self.decoders = conv self._affected_rows = 0 self.host_info = "Not connected" self.autocommit(False) if sql_mode is not None: c = self.cursor() c.execute("SET sql_mode=%s", (sql_mode,)) self.commit() if init_command is not None: c = self.cursor() c.execute(init_command) self.commit() def close(self): ''' Send the quit message and close the socket ''' send_data = struct.pack('= 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_length_coded_binary() self.insert_id = self.first_packet.read_length_coded_binary() self.server_status = struct.unpack('