Esempio n. 1
0
        def create_reader(bytes):
            b = Buffer(1024)
            for byte in bytes:
                b.write_byte(byte)
            b.flip()

            p = PacketReader(b)
            p.packet.position = b.position
            p.packet.limit = b.limit
            return p
Esempio n. 2
0
 def __init__(self):
     self.state = self.STATE_INIT
     self.buffer = Buffer(1024 * 16)
     self.socket = None
     self.reader = None
     self.writer = None
     self._time_command = False  #whether to keep timing stats on a cmd
     self._command_time = -1
     self._incommand = False
     self.current_resultset = None
Esempio n. 3
0
 def __init__(self):
     self.state = self.STATE_INIT
     self.buffer = Buffer(1024 * 16)
     self.socket = None
     self.reader = None
     self.writer = None
     self._time_command = False #whether to keep timing stats on a cmd
     self._command_time = -1
     self._incommand = False
     self.current_resultset = None
Esempio n. 4
0
        def create_reader(bytes):
            b = Buffer(1024)
            for byte in bytes:
                b.write_byte(byte)
            b.flip()

            p = PacketReader(b)
            p.packet.position = b.position
            p.packet.limit = b.limit
            return p
Esempio n. 5
0
class Connection(object):
    """Represents a single connection to a MySQL Database host."""
    STATE_ERROR = -1
    STATE_INIT = 0
    STATE_CONNECTING = 1
    STATE_CONNECTED = 2
    STATE_CLOSING = 3
    STATE_CLOSED = 4

    def __init__(self):
        self.state = self.STATE_INIT
        self.buffer = Buffer(1024 * 16)
        self.socket = None
        self.reader = None
        self.writer = None
        self._time_command = False #whether to keep timing stats on a cmd
        self._command_time = -1
        self._incommand = False
        self.current_resultset = None

    def _scramble(self, password, seed):
        """taken from java jdbc driver, scrambles the password using the given seed
        according to the mysql login protocol"""
        stage1 = SHA(password).digest()
        stage2 = SHA(stage1).digest()
        md = SHA()
        md.update(seed)
        md.update(stage2)
        #i love python :-):
        return ''.join(map(chr, [x ^ ord(stage1[i]) for i, x in enumerate(map(ord, md.digest()))]))

    def _handshake(self, user, password, database, charset):
        """performs the mysql login handshake"""

        #init buffer for reading (both pos and lim = 0)
        self.buffer.clear()
        self.buffer.flip()

        #read server welcome
        packet = self.reader.read_packet()

        self.protocol_version = packet.read_byte() #normally this would be 10 (0xa)

        if self.protocol_version == 0xff:
            #error on initial greeting, possibly too many connection error
            raise ClientLoginError.from_error_packet(packet, skip = 2)
        elif self.protocol_version == 0xa:
            pass #expected
        else:
            assert False, "Unexpected protocol version %02x" % self.protocol_version

        self.server_version = packet.read_bytes_until(0)

        packet.skip(4) #thread_id
        scramble_buff = packet.read_bytes(8)
        packet.skip(1) #filler
        server_caps = packet.read_short()
        #CAPS.dbg(server_caps)

        if not server_caps & CAPS.PROTOCOL_41:
            assert False, "<4.1 auth not supported"

        server_language = packet.read_byte()
        server_status = packet.read_short()
        packet.skip(13) #filler
        if packet.remaining:
            scramble_buff += packet.read_bytes_until(0)
        else:
            assert False, "<4.1 auth not supported"

        client_caps = server_caps

        #always turn off compression
        client_caps &= ~CAPS.COMPRESS
        client_caps &= ~CAPS.NO_SCHEMA
        #always turn off ssl
        client_caps &= ~CAPS.SSL

        if not server_caps & CAPS.CONNECT_WITH_DB and database:
            assert False, "initial db given but not supported by server"
        if server_caps & CAPS.CONNECT_WITH_DB and not database:
            client_caps &= ~CAPS.CONNECT_WITH_DB

        #build and write our answer to the initial handshake packet
        self.writer.clear()
        self.writer.start()
        self.writer.write_int(client_caps)
        self.writer.write_int(1024 * 1024 * 32) #16mb max packet
        if charset:
            self.writer.write_byte(charset_map[charset.replace("-", "")])
        else:
            self.writer.write_byte(server_language)
        self.writer.write_bytes('\0' * 23) #filler
        self.writer.write_bytes(user + '\0')

        if password:
            self.writer.write_byte(20)
            self.writer.write_bytes(self._scramble(password, scramble_buff))
        else:
            self.writer.write_byte(0)

        if database:
            self.writer.write_bytes(database + '\0')

        self.writer.finish(1)
        self.writer.flush()

        #read final answer from server
        self.buffer.flip()
        packet = self.reader.read_packet()
        result = packet.read_byte()
        if result == 0xff:
            raise ClientLoginError.from_error_packet(packet)
        elif result == 0xfe:
            assert False, "old password handshake not implemented"

    def _close_current_resultset(self, resultset):
        assert resultset == self.current_resultset
        self.current_resultset = None

    def _send_command(self, cmd, cmd_text):
        """sends a command with the given text"""
        #self.log.debug('cmd %s %s', cmd, cmd_text)

        #note: we are not using normal writer.start/finish here, because the cmd
        #could not fit in buffer, causing flushes in write_string, in that case 'finish' would
        #not be able to go back to the header of the packet to write the length in that case
        self.writer.clear()
        self.writer.write_header(len(cmd_text) + 1 + 4, 0) #1 is len of cmd, 4 is len of header, 0 is packet number
        self.writer.write_byte(cmd)
        self.writer.write_bytes(cmd_text)
        self.writer.flush()

    def _close(self):
        #self.log.debug("close mysql client %s", id(self))
        try:
            self.state = self.STATE_CLOSING
            if self.current_resultset:
                self.current_resultset.close(True)
            self.socket.close()
            self.state = self.STATE_CLOSED
        except:
            self.state = self.STATE_ERROR
            raise

    def connect(self, host = "localhost", port = 3306, user = "", password = "", db = "", autocommit = None, charset = None, use_unicode=False):
        """connects to the given host and port with user and password"""
        #self.log.debug("connect mysql client %s %s %s %s %s", id(self), host, port, user, password)
        try:
            #parse addresses of form str <host:port>
            assert type(host) == str, "make sure host is a string"

            if host[0] == '/': #assume unix domain socket
                addr = host
            elif ':' in host:
                host, port = host.split(':')
                port = int(port)
                addr = (host, port)
            else:
                addr = (host, port)

            assert self.state == self.STATE_INIT, "make sure connection is not already connected or closed"

            self.state = self.STATE_CONNECTING
            self.socket = socket.create_connection(addr)          
            
            self.reader = BufferedPacketReader(self.socket, self.buffer)
            self.writer = BufferedPacketWriter(self.socket, self.buffer)
            self._handshake(user, password, db, charset)
            #handshake complete client can now send commands
            self.state = self.STATE_CONNECTED

            if autocommit == False:
                self.set_autocommit(False)
            elif autocommit == True:
                self.set_autocommit(True)
            else:
                pass #whatever is the default of the db (ON in the case of mysql)

            if charset is not None:
                self.set_charset(charset)

            self.set_use_unicode(use_unicode)

            return self
        except gevent.Timeout:
            self.state = self.STATE_INIT
            raise
        except ClientLoginError:
            self.state = self.STATE_INIT
            raise
        except:
            self.state = self.STATE_ERROR
            raise

    def close(self):
        """close this connection"""
        assert self.is_connected(), "make sure connection is connected before closing"
        if self._incommand != False: assert False, "cannot close while still in a command"
        self._close()

    def command(self, cmd, cmd_text):
        """sends a COM_XXX command with the given text and possibly return a resultset (select)"""
        #print 'command', cmd, repr(cmd_text), type(cmd_text)
        assert type(cmd_text) == str #as opposed to unicode
        assert self.is_connected(), "make sure connection is connected before query"
        if self._incommand != False: assert False, "overlapped commands not supported"
        if self.current_resultset: assert False, "overlapped commands not supported, pls read prev resultset and close it"
        try:
            self._incommand = True
            if self._time_command:
                start_time = time.time()
            self._send_command(cmd, cmd_text)
            #read result, expect 1 of OK, ERROR or result set header
            self.buffer.flip()
            packet = self.reader.read_packet()
            result = packet.read_byte()
            #print 'res', result
            if self._time_command:
                end_time = time.time()
                self._command_time = end_time - start_time
            if result == 0x00:
                #OK, return (affected rows, last row id)
                rowcount = self.reader.read_length_coded_binary()
                lastrowid = self.reader.read_length_coded_binary()
                return (rowcount, lastrowid)
            elif result == 0xff:
                raise ClientCommandError.from_error_packet(packet)
            else: #result set
                self.current_resultset = ResultSet(self, result)
                return self.current_resultset

        except socket.error, e:
            (errorcode, errorstring) = e

            if errorcode in [errno.ECONNABORTED, errno.ECONNREFUSED, errno.ECONNRESET, errno.EPIPE]:
                self._incommand = False
                self.close()

            if sys.platform == "win32":
                if errorcode in [errno.WSAECONNABORTED]:
                    self._incommand = False
                    self.close()

            raise
        finally:
Esempio n. 6
0
class Connection(object):
    """Represents a single connection to a MySQL Database host."""
    STATE_ERROR = -1
    STATE_INIT = 0
    STATE_CONNECTING = 1
    STATE_CONNECTED = 2
    STATE_CLOSING = 3
    STATE_CLOSED = 4

    def __init__(self):
        self.state = self.STATE_INIT
        self.buffer = Buffer(1024 * 16)
        self.socket = None
        self.reader = None
        self.writer = None
        self._time_command = False  #whether to keep timing stats on a cmd
        self._command_time = -1
        self._incommand = False
        self.current_resultset = None

    def _scramble(self, password, seed):
        """taken from java jdbc driver, scrambles the password using the given seed
        according to the mysql login protocol"""
        stage1 = SHA(password).digest()
        stage2 = SHA(stage1).digest()
        md = SHA()
        md.update(seed)
        md.update(stage2)
        #i love python :-):
        return ''.join(
            map(chr, [
                x ^ ord(stage1[i]) for i, x in enumerate(map(ord, md.digest()))
            ]))

    def _handshake(self, user, password, database, charset):
        """performs the mysql login handshake"""

        #init buffer for reading (both pos and lim = 0)
        self.buffer.clear()
        self.buffer.flip()

        #read server welcome
        packet = self.reader.read_packet()

        self.protocol_version = packet.read_byte(
        )  #normally this would be 10 (0xa)

        if self.protocol_version == 0xff:
            #error on initial greeting, possibly too many connection error
            raise ClientLoginError.from_error_packet(packet, skip=2)
        elif self.protocol_version == 0xa:
            pass  #expected
        else:
            assert False, "Unexpected protocol version %02x" % self.protocol_version

        self.server_version = packet.read_bytes_until(0)

        packet.skip(4)  #thread_id
        scramble_buff = packet.read_bytes(8)
        packet.skip(1)  #filler
        server_caps = packet.read_short()
        #CAPS.dbg(server_caps)

        if not server_caps & CAPS.PROTOCOL_41:
            assert False, "<4.1 auth not supported"

        server_language = packet.read_byte()
        server_status = packet.read_short()
        packet.skip(13)  #filler
        if packet.remaining:
            scramble_buff += packet.read_bytes_until(0)
        else:
            assert False, "<4.1 auth not supported"

        client_caps = server_caps

        #always turn off compression
        client_caps &= ~CAPS.COMPRESS
        client_caps &= ~CAPS.NO_SCHEMA
        #always turn off ssl
        client_caps &= ~CAPS.SSL

        if not server_caps & CAPS.CONNECT_WITH_DB and database:
            assert False, "initial db given but not supported by server"
        if server_caps & CAPS.CONNECT_WITH_DB and not database:
            client_caps &= ~CAPS.CONNECT_WITH_DB

        #build and write our answer to the initial handshake packet
        self.writer.clear()
        self.writer.start()
        self.writer.write_int(client_caps)
        self.writer.write_int(1024 * 1024 * 32)  #16mb max packet
        if charset:
            self.writer.write_byte(charset_map[charset.replace("-", "")])
        else:
            self.writer.write_byte(server_language)
        self.writer.write_bytes('\0' * 23)  #filler
        self.writer.write_bytes(user + '\0')

        if password:
            self.writer.write_byte(20)
            self.writer.write_bytes(self._scramble(password, scramble_buff))
        else:
            self.writer.write_byte(0)

        if database:
            self.writer.write_bytes(database + '\0')

        self.writer.finish(1)
        self.writer.flush()

        #read final answer from server
        self.buffer.flip()
        packet = self.reader.read_packet()
        result = packet.read_byte()
        if result == 0xff:
            raise ClientLoginError.from_error_packet(packet)
        elif result == 0xfe:
            assert False, "old password handshake not implemented"

    def _close_current_resultset(self, resultset):
        assert resultset == self.current_resultset
        self.current_resultset = None

    def _send_command(self, cmd, cmd_text):
        """sends a command with the given text"""
        #self.log.debug('cmd %s %s', cmd, cmd_text)

        #note: we are not using normal writer.start/finish here, because the cmd
        #could not fit in buffer, causing flushes in write_string, in that case 'finish' would
        #not be able to go back to the header of the packet to write the length in that case
        self.writer.clear()
        self.writer.write_header(
            len(cmd_text) + 1 + 4,
            0)  #1 is len of cmd, 4 is len of header, 0 is packet number
        self.writer.write_byte(cmd)
        self.writer.write_bytes(cmd_text)
        self.writer.flush()

    def _close(self):
        #self.log.debug("close mysql client %s", id(self))
        try:
            self.state = self.STATE_CLOSING
            if self.current_resultset:
                self.current_resultset.close(True)
            self.socket.close()
            self.state = self.STATE_CLOSED
        except:
            self.state = self.STATE_ERROR
            raise

    def connect(self,
                host="localhost",
                port=3306,
                user="",
                password="",
                db="",
                autocommit=None,
                charset=None,
                use_unicode=False):
        """connects to the given host and port with user and password"""
        #self.log.debug("connect mysql client %s %s %s %s %s", id(self), host, port, user, password)
        try:
            #parse addresses of form str <host:port>
            assert type(host) == str, "make sure host is a string"

            if host[0] == '/':  #assume unix domain socket
                addr = host
            elif ':' in host:
                host, port = host.split(':')
                port = int(port)
                addr = (host, port)
            else:
                addr = (host, port)

            assert self.state == self.STATE_INIT, "make sure connection is not already connected or closed"

            self.state = self.STATE_CONNECTING
            self.socket = socket.create_connection(addr)

            self.reader = BufferedPacketReader(self.socket, self.buffer)
            self.writer = BufferedPacketWriter(self.socket, self.buffer)
            self._handshake(user, password, db, charset)
            #handshake complete client can now send commands
            self.state = self.STATE_CONNECTED

            if autocommit == False:
                self.set_autocommit(False)
            elif autocommit == True:
                self.set_autocommit(True)
            else:
                pass  #whatever is the default of the db (ON in the case of mysql)

            if charset is not None:
                self.set_charset(charset)

            self.set_use_unicode(use_unicode)

            return self
        except gevent.Timeout:
            self.state = self.STATE_INIT
            raise
        except ClientLoginError:
            self.state = self.STATE_INIT
            raise
        except:
            self.state = self.STATE_ERROR
            raise

    def close(self):
        """close this connection"""
        assert self.is_connected(
        ), "make sure connection is connected before closing"
        if self._incommand != False:
            assert False, "cannot close while still in a command"
        self._close()

    def command(self, cmd, cmd_text):
        """sends a COM_XXX command with the given text and possibly return a resultset (select)"""
        #print 'command', cmd, repr(cmd_text), type(cmd_text)
        assert type(cmd_text) == str  #as opposed to unicode
        assert self.is_connected(
        ), "make sure connection is connected before query"
        if self._incommand != False:
            assert False, "overlapped commands not supported"
        if self.current_resultset:
            assert False, "overlapped commands not supported, pls read prev resultset and close it"
        try:
            self._incommand = True
            if self._time_command:
                start_time = time.time()
            self._send_command(cmd, cmd_text)
            #read result, expect 1 of OK, ERROR or result set header
            self.buffer.flip()
            packet = self.reader.read_packet()
            result = packet.read_byte()
            #print 'res', result
            if self._time_command:
                end_time = time.time()
                self._command_time = end_time - start_time
            if result == 0x00:
                #OK, return (affected rows, last row id)
                rowcount = self.reader.read_length_coded_binary()
                lastrowid = self.reader.read_length_coded_binary()
                return (rowcount, lastrowid)
            elif result == 0xff:
                raise ClientCommandError.from_error_packet(packet)
            else:  #result set
                self.current_resultset = ResultSet(self, result)
                return self.current_resultset

        except socket.error, e:
            (errorcode, errorstring) = e

            if errorcode in [
                    errno.ECONNABORTED, errno.ECONNREFUSED, errno.ECONNRESET,
                    errno.EPIPE
            ]:
                self._incommand = False
                self.close()

            if sys.platform == "win32":
                if errorcode in [errno.WSAECONNABORTED]:
                    self._incommand = False
                    self.close()

            raise
        finally: