示例#1
0
class Connection(Thread):
    """Thread to handle a single TCP conection."""
    def __init__(self, conn, mbox, cdr, password):
        """Initialize."""
        # pylint: disable=too-many-arguments
        Thread.__init__(self)

        self.conn = conn
        self.mbox = mbox
        self.cdr = cdr
        self.mboxq = PollableQueue()
        self.password = password
        self.accept_pw = False

    def queue(self):
        """Fetch queue object."""
        return self.mboxq

    def _build_msg_list(self):
        """Send a sanitized list to the client."""
        result = []
        blacklist = ["wav"]
        status = self.mbox.get_mbox_status()
        for _k, ref in status.items():
            result.append(
                {key: val
                 for key, val in ref.items() if key not in blacklist})
        return result

    @staticmethod
    def _build_cdr(entries, start=0, count=-1, sha=None):
        """Extract requested info from cdr list."""
        if sha is not None:
            msg = decode_from_sha(sha)
            start, count = [int(item) for item in msg.split(b',')]
        if not start or start < 0:
            start = 0
        if start >= len(entries) or count == 0:
            return msg

        if count < 0 or count + start > len(entries):
            end = len(entries)
        else:
            end = start + count
        return entries[start:end]

    def _send(self, command, msg):
        """Send message prefixed by length."""
        print('SEND command %s' % (command))
        msglen = len(msg)
        msglen = bytes([
            msglen >> 24, 0xff & (msglen >> 16), 0xff & (msglen >> 8),
            0xff & (msglen >> 0)
        ])
        self.conn.send(bytes([command]) + bytes(msglen) + bytes(msg))

    def _handle_request(self, request):
        print(request)
        if request['cmd'] == cmd.CMD_MESSAGE_PASSWORD:
            self.accept_pw, msg = compare_password(self.password,
                                                   request['sha'])
            if self.accept_pw:
                self._send(cmd.CMD_MESSAGE_VERSION,
                           __version__.encode('utf-8'))
                logging.info("Password accepted")
            else:
                self._send(cmd.CMD_MESSAGE_ERROR, msg.encode('utf-8'))
                logging.warning("Password rejected: %s", msg)
        elif not self.accept_pw:
            logging.warning("Bad Password")
            self._send(cmd.CMD_MESSAGE_ERROR, b'bad password')
        elif request['cmd'] == cmd.CMD_MESSAGE_LIST:
            logging.debug("Requested Message List")
            self._send(cmd.CMD_MESSAGE_LIST,
                       json.dumps(self._build_msg_list()).encode('utf-8'))
        elif request['cmd'] == cmd.CMD_MESSAGE_MP3:
            msg = self.mbox.mp3(request['sha'])
            if msg:
                self._send(cmd.CMD_MESSAGE_MP3, msg)
            else:
                logging.warning("Couldn't find message for %s", request['sha'])
                self._send(cmd.CMD_MESSAGE_ERROR,
                           "Could not find requested message")
        elif request['cmd'] == cmd.CMD_MESSAGE_DELETE:
            msg = self.mbox.delete(request['sha'])
            self._send(cmd.CMD_MESSAGE_LIST,
                       json.dumps(self._build_msg_list()).encode('utf-8'))
        elif request['cmd'] == cmd.CMD_MESSAGE_CDR_AVAILABLE:
            if not self.cdr:
                self._send(cmd.CMD_MESSAGE_ERROR, b'CDR Not enabled')
                return
            self._send(cmd.CMD_MESSAGE_CDR_AVAILABLE,
                       json.dumps({
                           'count': self.cdr.count()
                       }).encode('utf-8'))
        elif request['cmd'] == cmd.CMD_MESSAGE_CDR:
            if not self.cdr:
                self._send(cmd.CMD_MESSAGE_ERROR, b'CDR Not enabled')
                return
            entries = self._build_cdr(self.cdr.entries(), sha=request['sha'])
            try:
                msg = {
                    'keys': self.cdr.keys(),
                    'entries': entries.decode('utf-8')
                }
            except:
                msg = {'keys': self.cdr.keys(), 'entries': entries}
            try:
                for entry in entries:
                    if entry['time'] == '':
                        logging.exception('No timestamp for this: %s' %
                                          (entry))
            except:
                asdf = ''
            self._send(cmd.CMD_MESSAGE_CDR,
                       zlib.compress(json.dumps(msg).encode('utf-8')))

    def run(self):
        """Thread main loop."""
        while True:
            readable, _w, _e = select.select([self.conn, self.mboxq], [], [])
            if self.conn in readable:
                try:
                    request = _parse_request(recv_blocking(self.conn, 66))
                except RuntimeError:
                    logging.warning("Connection closed")
                    break
                logging.debug(request)
                self._handle_request(request)

            if self.mboxq in readable:
                msgtype = self.mboxq.get()
                self.mboxq.task_done()
                if msgtype == 'mbox':
                    self._send(
                        cmd.CMD_MESSAGE_LIST,
                        json.dumps(self._build_msg_list()).encode('utf-8'))
                elif msgtype == 'cdr':
                    self._send(
                        cmd.CMD_MESSAGE_CDR_AVAILABLE,
                        json.dumps({
                            'count': self.cdr.count()
                        }).encode('utf-8'))
示例#2
0
class Client:
    """asterisk_mbox client."""
    def __init__(self, ipaddr, port, password, callback=None, **kwargs):
        """constructor."""
        self._ipaddr = ipaddr
        self._port = port
        self._password = encode_password(password).encode('utf-8')
        self._callback = callback
        self._soc = None
        self._thread = None
        self._status = {}

        # Stop thread
        self.signal = PollableQueue()
        # Send data to the server
        self.request_queue = PollableQueue()
        # Receive data from the server
        self.result_queue = PollableQueue()
        if 'autostart' not in kwargs or kwargs['autostart']:
            self.start()

    def start(self):
        """Start thread."""
        if not self._thread:
            logging.info("Starting asterisk mbox thread")
            # Ensure signal queue is empty
            try:
                while True:
                    self.signal.get(False)
            except queue.Empty:
                pass
            self._thread = threading.Thread(target=self._loop)
            self._thread.setDaemon(True)
            self._thread.start()

    def stop(self):
        """Stop thread."""
        if self._thread:
            self.signal.put("Stop")
            self._thread.join()
            if self._soc:
                self._soc.shutdown()
                self._soc.close()
            self._thread = None

    def _connect(self):
        """Connect to server."""
        self._soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._soc.connect((self._ipaddr, self._port))
        self._soc.send(
            _build_request({
                'cmd': cmd.CMD_MESSAGE_PASSWORD,
                'sha': self._password
            }))

    def _recv_msg(self):
        """Read a message from the server."""
        command = ord(recv_blocking(self._soc, 1))
        msglen = recv_blocking(self._soc, 4)
        msglen = ((msglen[0] << 24) + (msglen[1] << 16) + (msglen[2] << 8) +
                  msglen[3])
        msg = recv_blocking(self._soc, msglen)
        return command, msg

    def _handle_msg(self, command, msg, request):
        if command == cmd.CMD_MESSAGE_ERROR:
            logging.warning("Received error: %s", msg.decode('utf-8'))
        elif command == cmd.CMD_MESSAGE_VERSION:
            min_ver = StrictVersion(__min_server_version__)
            server_ver = StrictVersion(msg.decode('utf-8'))
            if server_ver < min_ver:
                raise ServerError("Server version is too low: {} < {}".format(
                    msg.decode('utf-8'), __min_server_version__))
        elif command == cmd.CMD_MESSAGE_LIST:
            self._status = json.loads(msg.decode('utf-8'))
            msg = self._status
        elif command == cmd.CMD_MESSAGE_CDR:
            self._status = json.loads(zlib.decompress(msg).decode('utf-8'))
            msg = self._status

        if self._callback and 'sync' not in request:
            self._callback(command, msg)
        elif request and (command == request.get('cmd')
                          or command == cmd.CMD_MESSAGE_ERROR):
            logging.debug("Got command: %s", cmd.commandstr(command))
            self.result_queue.put([command, msg])
            request.clear()
        else:
            logging.debug("Got unhandled command: %s", cmd.commandstr(command))

    def _clear_request(self, request):
        if not self._callback or 'sync' in request:
            self.result_queue.put(
                [cmd.CMD_MESSAGE_ERROR, "Not connected to server"])
        request.clear()

    def _loop(self):
        """Handle data."""
        request = {}
        connected = False
        while True:
            timeout = None
            sockets = [self.request_queue, self.signal]
            if not connected:
                try:
                    self._clear_request(request)
                    self._connect()
                    self._soc.send(
                        _build_request({'cmd': cmd.CMD_MESSAGE_LIST}))
                    self._soc.send(
                        _build_request({'cmd': cmd.CMD_MESSAGE_CDR_AVAILABLE}))
                    connected = True
                except ConnectionRefusedError:
                    timeout = 5.0
            if connected:
                sockets.append(self._soc)

            readable, _writable, _errored = select.select(
                sockets, [], [], timeout)

            if self.signal in readable:
                break

            if self._soc in readable:
                # We have incoming data
                try:
                    command, msg = self._recv_msg()
                    self._handle_msg(command, msg, request)
                except (RuntimeError, ConnectionResetError):
                    logging.warning("Lost connection")
                    connected = False
                    self._clear_request(request)

            if self.request_queue in readable:
                request = self.request_queue.get()
                self.request_queue.task_done()
                if not connected:
                    self._clear_request(request)
                else:
                    if (request['cmd'] == cmd.CMD_MESSAGE_LIST and self._status
                            and (not self._callback or 'sync' in request)):
                        self.result_queue.put(
                            [cmd.CMD_MESSAGE_LIST, self._status])
                        request = {}
                    else:
                        self._soc.send(_build_request(request))

    def _queue_msg(self, item, **kwargs):
        if not self._thread:
            raise ServerError("Client not running")
        if not self._callback or kwargs.get('sync'):
            item['sync'] = True
            self.request_queue.put(item)
            command, msg = self.result_queue.get()
            if command == cmd.CMD_MESSAGE_ERROR:
                raise ServerError(msg)

            return msg
        else:
            self.request_queue.put(item)

    def messages(self, **kwargs):
        """Get list of messages with metadata."""
        return self._queue_msg({'cmd': cmd.CMD_MESSAGE_LIST}, **kwargs)

    def mp3(self, sha, **kwargs):
        """Get raw MP3 of a message."""
        return self._queue_msg(
            {
                'cmd': cmd.CMD_MESSAGE_MP3,
                'sha': _get_bytes(sha)
            }, **kwargs)

    def delete(self, sha, **kwargs):
        """Delete a message."""
        return self._queue_msg(
            {
                'cmd': cmd.CMD_MESSAGE_DELETE,
                'sha': _get_bytes(sha)
            }, **kwargs)

    def cdr_count(self, **kwargs):
        """Request count of CDR entries"""
        return self._queue_msg({'cmd': cmd.CMD_MESSAGE_CDR_AVAILABLE},
                               **kwargs)

    def get_cdr(self, start=0, count=-1, **kwargs):
        """Request range of CDR messages"""
        sha = encode_to_sha("{:d},{:d}".format(start, count))
        return self._queue_msg({
            'cmd': cmd.CMD_MESSAGE_CDR,
            'sha': sha
        }, **kwargs)