Ejemplo n.º 1
0
    def _handle_receive(self, message_id, request):
        if not self._check_request_args(request, ["secret", "sent_timestamp", "sent_to", "from", "message"]):
            log.warning("Bad request: %r (args: %r)" % (request, request.args))
            yield self._send_response(message_id, success=self.SMSSYNC_FALSE)
            return
        msginfo = yield self.msginfo_for_request(request)
        supplied_secret = request.args["secret"][0]
        if msginfo is None or (msginfo.smssync_secret and not msginfo.smssync_secret == supplied_secret):
            log.warning("Bad secret or account: %r (args: %r)" % (request, request.args))
            yield self._send_response(message_id, success=self.SMSSYNC_FALSE)
            return

        timestamp = self._parse_timestamp(request)

        normalize = lambda raw: normalize_msisdn(raw, msginfo.country_code)
        message = {
            "message_id": message_id,
            "transport_type": self.transport_type,
            "to_addr": normalize(request.args["sent_to"][0]),
            "from_addr": normalize(request.args["from"][0]),
            "content": request.args["message"][0],
            "timestamp": timestamp,
        }
        self.add_msginfo_metadata(message, msginfo)
        yield self.publish_message(**message)
        self.callLater(
            self._reply_delay, self._respond_with_pending_messages, msginfo, message_id, success=self.SMSSYNC_TRUE
        )
Ejemplo n.º 2
0
    def handle_outbound_message(self, message):
        headers = {
            'Content-Type': 'application/json; charset=utf-8',
        }
        headers.update(self.get_auth_headers())

        params = {
            'to_addr': message['to_addr'],
            'content': message['content'],
            'message_id': message['message_id'],
            'in_reply_to': message['in_reply_to'],
            'session_event': message['session_event']
        }
        if 'helper_metadata' in message:
            params['helper_metadata'] = message['helper_metadata']

        resp = yield http_request_full(self.get_url('messages.json'),
                                       data=json.dumps(params).encode('utf-8'),
                                       headers=headers,
                                       method='PUT')

        if resp.code != http.OK:
            log.warning('Unexpected status code: %s, body: %s' %
                        (resp.code, resp.delivered_body))
            yield self.publish_nack(message['message_id'],
                                    reason='Unexpected status code: %s' %
                                    (resp.code, ))
            return

        remote_message = json.loads(resp.delivered_body)
        yield self.map_message_id(remote_message['message_id'],
                                  message['message_id'])
        yield self.publish_ack(user_message_id=message['message_id'],
                               sent_message_id=remote_message['message_id'])
Ejemplo n.º 3
0
 def _handle_send(self, message_id, request):
     msginfo = yield self.msginfo_for_request(request)
     if msginfo is None:
         log.warning("Bad account: %r (args: %r)" % (request, request.args))
         yield self._send_response(message_id, success=self.SMSSYNC_FALSE)
         return
     yield self._respond_with_pending_messages(msginfo, message_id, task="send", secret=msginfo.smssync_secret)
Ejemplo n.º 4
0
 def send_message_to_client(self, message, conversation, push_url):
     if push_url is None:
         log.warning(
             "push_message_url not configured for conversation: %s" %
             (conversation.key))
         return
     return self.push(push_url, message)
Ejemplo n.º 5
0
    def push(self, url, vumi_message):
        config = self.get_static_config()
        data = vumi_message.to_json().encode('utf-8')
        try:
            auth, url = extract_auth_from_url(url.encode('utf-8'))
            headers = {
                'Content-Type': 'application/json; charset=utf-8',
            }
            if auth is not None:
                username, password = auth

                if username is None:
                    username = ''

                if password is None:
                    password = ''

                headers.update({
                    'Authorization': 'Basic %s' % (
                        base64.b64encode('%s:%s' % (username, password)),)
                })
            resp = yield http_request_full(
                url, data=data, headers=headers, timeout=config.timeout)
            if not (200 <= resp.code < 300):
                # We didn't get a 2xx response.
                log.warning('Got unexpected response code %s from %s' % (
                    resp.code, url))
        except SchemeNotSupported:
            log.warning('Unsupported scheme for URL: %s' % (url,))
        except HttpTimeoutError:
            log.warning("Timeout pushing message to %s" % (url,))
        except DNSLookupError:
            log.warning("DNS lookup error pushing message to %s" % (url,))
        except ConnectionRefusedError:
            log.warning("Connection refused pushing message to %s" % (url,))
Ejemplo n.º 6
0
    def handle_outbound_message(self, message):
        headers = {"Content-Type": "application/json; charset=utf-8"}
        headers.update(self.get_auth_headers())

        params = {
            "to_addr": message["to_addr"],
            "content": message["content"],
            "message_id": message["message_id"],
            "in_reply_to": message["in_reply_to"],
            "session_event": message["session_event"],
        }
        if "helper_metadata" in message:
            params["helper_metadata"] = message["helper_metadata"]

        resp = yield http_request_full(
            self.get_url("messages.json"),
            data=json.dumps(params).encode("utf-8"),
            headers=headers,
            method="PUT",
            agent_class=self.agent_factory,
        )

        if resp.code != http.OK:
            log.warning("Unexpected status code: %s, body: %s" % (resp.code, resp.delivered_body))
            yield self.publish_nack(message["message_id"], reason="Unexpected status code: %s" % (resp.code,))
            return

        remote_message = json.loads(resp.delivered_body)
        yield self.map_message_id(remote_message["message_id"], message["message_id"])
        yield self.publish_ack(user_message_id=message["message_id"], sent_message_id=remote_message["message_id"])
Ejemplo n.º 7
0
    def handle_PUT_in_reply_to(self, request, payload, in_reply_to):
        user_account = request.getUser()
        conversation = yield self.get_conversation(user_account)

        reply_to = yield self.vumi_api.mdb.get_inbound_message(in_reply_to)
        if reply_to is None:
            self.client_error_response(request, 'Invalid in_reply_to value')
            return

        reply_to_mdh = MessageMetadataHelper(self.vumi_api, reply_to)
        try:
            msg_conversation_key = reply_to_mdh.get_conversation_key()
        except KeyError:
            log.warning('Invalid reply to message %r which has no conversation'
                        ' key' % (reply_to,))
            msg_conversation_key = None
        if msg_conversation_key != conversation.key:
            self.client_error_response(request, 'Invalid in_reply_to value')
            return

        msg_options = ReplyToOptions(payload)
        if not msg_options.is_valid:
            self.client_error_response(request, msg_options.error_msg)
            return

        continue_session = (msg_options.session_event
                            != TransportUserMessage.SESSION_CLOSE)
        helper_metadata = conversation.set_go_helper_metadata()
        helper_metadata.update(self.get_load_balancer_metadata(payload))

        msg = yield self.worker.reply_to(
            reply_to, msg_options.content, continue_session,
            helper_metadata=helper_metadata)

        self.successful_send_response(request, msg)
Ejemplo n.º 8
0
    def _handle_receive(self, message_id, request):
        if not self._check_request_args(request, ['secret', 'sent_timestamp',
                                                  'sent_to', 'from',
                                                  'message']):
            log.warning("Bad request: %r (args: %r)" % (request, request.args))
            yield self._send_response(message_id, success=self.SMSSYNC_FALSE)
            return
        msginfo = yield self.msginfo_for_request(request)
        supplied_secret = request.args['secret'][0]
        if msginfo is None or (msginfo.smssync_secret and
                               not msginfo.smssync_secret == supplied_secret):
            log.warning("Bad secret or account: %r (args: %r)"
                        % (request, request.args))
            yield self._send_response(message_id, success=self.SMSSYNC_FALSE)
            return

        timestamp = self._parse_timestamp(request)

        normalize = lambda raw: normalize_msisdn(raw, msginfo.country_code)
        message = {
            'message_id': message_id,
            'transport_type': self.transport_type,
            'to_addr': normalize(request.args['sent_to'][0]),
            'from_addr': normalize(request.args['from'][0]),
            'content': request.args['message'][0],
            'timestamp': timestamp,
        }
        self.add_msginfo_metadata(message, msginfo)
        yield self.publish_message(**message)
        self.callLater(self._reply_delay, self._respond_with_pending_messages,
                       msginfo, message_id, success=self.SMSSYNC_TRUE)
Ejemplo n.º 9
0
    def process_command_send_message(self, user_account_key, conversation_key,
                                     command_data, **kwargs):
        if kwargs:
            log.info("Received unexpected command args: %s" % (kwargs,))
        conv = yield self.get_conversation(user_account_key, conversation_key)
        if conv is None:
            log.warning("Cannot find conversation '%s' for user '%s'." % (
                conversation_key, user_account_key))
            return

        log.info('Processing send_message: %s' % kwargs)
        to_addr = command_data['to_addr']
        content = command_data['content']
        msg_options = command_data['msg_options']
        in_reply_to = msg_options.pop('in_reply_to', None)
        self.add_conv_to_msg_options(conv, msg_options)
        if in_reply_to:
            msg = yield self.vumi_api.mdb.get_inbound_message(in_reply_to)
            if msg:
                yield self.reply_to(
                    msg, content,
                    helper_metadata=msg_options['helper_metadata'])
            else:
                log.warning('Unable to reply, message %s does not exist.' % (
                    in_reply_to))
        else:
            yield self.send_to(
                to_addr, content, endpoint='default', **msg_options)
Ejemplo n.º 10
0
 def send_message_to_client(self, message, conversation, push_url):
     if push_url is None:
         log.warning(
             "push_message_url not configured for conversation: %s" % (
                 conversation.key))
         return
     return self.push(push_url, message)
Ejemplo n.º 11
0
    def process_command_send_message(self, user_account_key, conversation_key,
                                     command_data, **kwargs):
        if kwargs:
            log.info("Received unexpected command args: %s" % (kwargs, ))
        conv = yield self.get_conversation(user_account_key, conversation_key)
        if conv is None:
            log.warning("Cannot find conversation '%s' for user '%s'." %
                        (conversation_key, user_account_key))
            return

        log.info('Processing send_message: %s' % kwargs)
        to_addr = command_data['to_addr']
        content = command_data['content']
        msg_options = command_data['msg_options']
        in_reply_to = msg_options.pop('in_reply_to', None)
        self.add_conv_to_msg_options(conv, msg_options)
        if in_reply_to:
            msg = yield self.vumi_api.mdb.get_inbound_message(in_reply_to)
            if msg:
                yield self.reply_to(
                    msg,
                    content,
                    helper_metadata=msg_options['helper_metadata'])
            else:
                log.warning('Unable to reply, message %s does not exist.' %
                            (in_reply_to))
        else:
            yield self.send_to(to_addr,
                               content,
                               endpoint='default',
                               **msg_options)
Ejemplo n.º 12
0
    def handle_outbound_message(self, message):
        headers = {
            'Content-Type': 'application/json; charset=utf-8',
        }
        headers.update(self.get_auth_headers())

        params = {
            'to_addr': message['to_addr'],
            'content': message['content'],
            'message_id': message['message_id'],
            'in_reply_to': message['in_reply_to'],
            'session_event': message['session_event']
        }
        if 'helper_metadata' in message:
            params['helper_metadata'] = message['helper_metadata']

        resp = yield http_request_full(
            self.get_url('messages.json'),
            data=json.dumps(params).encode('utf-8'),
            headers=headers,
            method='PUT')

        if resp.code != http.OK:
            log.warning('Unexpected status code: %s, body: %s' % (
                resp.code, resp.delivered_body))
            yield self.publish_nack(message['message_id'],
                                    reason='Unexpected status code: %s' % (
                                        resp.code,))
            return

        remote_message = json.loads(resp.delivered_body)
        yield self.map_message_id(
            remote_message['message_id'], message['message_id'])
        yield self.publish_ack(user_message_id=message['message_id'],
                               sent_message_id=remote_message['message_id'])
Ejemplo n.º 13
0
    def _handle_delivery_report_esm_class(self, pdu):
        """
        Check if the ``esm_class`` indicates that this is a delivery report.

        If so, handle it and return a deferred ``True``, otherwise return a
        deferred ``False``.

        NOTE: We assume the message content is a string that matches our regex.
              We can't use the usual decoding process here because it lives
              elsewhere and the content should be plain ASCII generated by the
              SMSC anyway.
        """
        if not self.config.delivery_report_use_esm_class:
            # We're not configured to check the ``esm_class``, so do nothing.
            return succeed(False)

        esm_class = pdu["body"]["mandatory_parameters"]["esm_class"]
        if not (esm_class & self.ESM_CLASS_MASK):
            # Delivery report flags in esm_class are not set.
            return succeed(False)

        content = pdu["body"]["mandatory_parameters"]["short_message"]
        match = self.config.delivery_report_regex.search(content or '')
        if not match:
            log.warning(("esm_class %s indicates delivery report, but content"
                         " does not match regex: %r") % (esm_class, content))
            # Even though this doesn't match the regex, the esm_class indicates
            # that it's a DR and we therefore don't want to treat it as a
            # normal message.
            return succeed(True)

        fields = match.groupdict()
        d = self._process_delivery_report_content_fields(fields)
        d.addCallback(lambda _: True)
        return d
Ejemplo n.º 14
0
 def normalise_provider(self, provider):
     if provider not in self.provider_mappings:
         log.warning(
             "No mapping exists for provider '%s', "
             "using '%s' as a fallback" % (provider, provider,))
         return provider
     else:
         return self.provider_mappings[provider]
Ejemplo n.º 15
0
 def _handle_send(self, message_id, request):
     msginfo = yield self.msginfo_for_request(request)
     if msginfo is None:
         log.warning("Bad account: %r (args: %r)" % (request, request.args))
         yield self._send_response(message_id, success=self.SMSSYNC_FALSE)
         return
     yield self._respond_with_pending_messages(
         msginfo, message_id, task='send', secret=msginfo.smssync_secret)
Ejemplo n.º 16
0
    def handle_bind_receiver_resp(self, pdu):
        if not pdu_ok(pdu):
            log.warning('Unable to bind: %r' % (command_status(pdu),))
            self.transport.loseConnection()
            return

        self.state = self.BOUND_STATE_RX
        return self.on_smpp_bind(seq_no(pdu))
Ejemplo n.º 17
0
    def handle_bind_receiver_resp(self, pdu):
        if not pdu_ok(pdu):
            log.warning('Unable to bind: %r' % (command_status(pdu), ))
            self.transport.loseConnection()
            return

        self.state = self.BOUND_STATE_RX
        return self.on_smpp_bind(seq_no(pdu))
Ejemplo n.º 18
0
 def _handle_submit_sm_resp_callback(self, message_id, smpp_message_id,
                                     command_status, cb):
     if message_id is None:
         # We have no message_id, so log a warning instead of calling the
         # callback.
         log.warning("Failed to retrieve message id for deliver_sm_resp."
                     " ack/nack from %s discarded."
                     % self.vumi_transport.transport_name)
     else:
         return cb(message_id, smpp_message_id, command_status)
Ejemplo n.º 19
0
 def consume_user_message(self, message):
     msg_mdh = self.get_metadata_helper(message)
     conversation = yield msg_mdh.get_conversation()
     if conversation is None:
         log.warning("Cannot find conversation for message: %r" % (
             message,))
         return
     ignore = self.get_api_config(conversation, 'ignore_messages', False)
     if not ignore:
         push_url = self.get_api_config(conversation, 'push_message_url')
         yield self.send_message_to_client(message, conversation, push_url)
Ejemplo n.º 20
0
 def consume_user_message(self, message):
     msg_mdh = self.get_metadata_helper(message)
     conversation = yield msg_mdh.get_conversation()
     if conversation is None:
         log.warning("Cannot find conversation for message: %r" %
                     (message, ))
         return
     ignore = self.get_api_config(conversation, 'ignore_messages', False)
     if not ignore:
         push_url = self.get_api_config(conversation, 'push_message_url')
         yield self.send_message_to_client(message, conversation, push_url)
Ejemplo n.º 21
0
 def process_message_in_sandbox(self, msg):
     # TODO remove the delivery class inference and injection into the
     # message once we have message address types
     metadata = msg['helper_metadata']
     metadata['delivery_class'] = self.infer_delivery_class(msg)
     config = yield self.get_config(msg)
     if not config.javascript:
         log.warning("No JS for conversation: %s" %
                     (config.conversation.key, ))
         return
     yield super(JsBoxApplication, self).process_message_in_sandbox(msg)
Ejemplo n.º 22
0
    def on_unsupported_command_id(self, pdu):
        """
        Called when an SMPP PDU is received for which no handler function has
        been defined.

        :param dict pdu:
            The dict result one gets when calling ``smpp.pdu.unpack_pdu()``
            on the received PDU
        """
        log.warning('Received unsupported SMPP command_id: %r' %
                    (command_id(pdu), ))
Ejemplo n.º 23
0
    def on_unsupported_command_id(self, pdu):
        """
        Called when an SMPP PDU is received for which no handler function has
        been defined.

        :param dict pdu:
            The dict result one gets when calling ``smpp.pdu.unpack_pdu()``
            on the received PDU
        """
        log.warning(
            'Received unsupported SMPP command_id: %r' % (command_id(pdu),))
Ejemplo n.º 24
0
 def process_message_in_sandbox(self, msg):
     # TODO remove the delivery class inference and injection into the
     # message once we have message address types
     metadata = msg['helper_metadata']
     metadata['delivery_class'] = self.infer_delivery_class(msg)
     config = yield self.get_config(msg)
     if not config.javascript:
         log.warning("No JS for conversation: %s" % (
             config.conversation.key,))
         return
     yield super(JsBoxApplication, self).process_message_in_sandbox(msg)
Ejemplo n.º 25
0
    def handle_delivery_report(self, receipted_message_id, delivery_status):
        message_id = yield self.message_stash.get_internal_message_id(
            receipted_message_id)
        if message_id is None:
            log.warning("Failed to retrieve message id for delivery report."
                        " Delivery report from %s discarded." %
                        self.transport_name)
            return

        dr = yield self.publish_delivery_report(
            user_message_id=message_id, delivery_status=delivery_status)
        returnValue(dr)
Ejemplo n.º 26
0
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']
        mica_session_identifier = pdu_opts['user_message_reference']
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], mica_session_identifier)

        session_event = self.ussd_service_op_map.get(service_op)

        if session_event == 'new':
            # PSSR request. Let's assume it means a new session.
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(
                vumi_session_identifier, ussd_code=ussd_code)

        elif session_event == 'close':
            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            if session_event != 'continue':
                log.warning(('Received unknown %r ussd_service_op, '
                             'assuming continue.') % (service_op,))
                session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = pdu_params['short_message']

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'ussd_service_op': service_op,
            'session_identifier': mica_session_identifier,
        }

        decoded_msg = self.dcs_decode(content,
                                      pdu_params['data_coding'])

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=decoded_msg,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Ejemplo n.º 27
0
 def get_conversations(self, conv_pointers):
     results = yield gatherResults([
         self.get_conversation_fallback(account_key, conv_key)
         for account_key, conv_key in conv_pointers])
     conversations = []
     for pointer, conv in zip(conv_pointers, results):
         if conv is None:
             log.warning("Conversation %s for account %s not found." % (
                 pointer[1], pointer[0]))
         else:
             conversations.append(conv)
     returnValue(conversations)
Ejemplo n.º 28
0
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']
        mica_session_identifier = pdu_opts['user_message_reference']
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], mica_session_identifier)

        session_event = self.ussd_service_op_map.get(service_op)

        if session_event == 'new':
            # PSSR request. Let's assume it means a new session.
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(vumi_session_identifier,
                                                      ussd_code=ussd_code)

        elif session_event == 'close':
            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            if session_event != 'continue':
                log.warning(('Received unknown %r ussd_service_op, '
                             'assuming continue.') % (service_op, ))
                session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = pdu_params['short_message']

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'ussd_service_op': service_op,
            'session_identifier': mica_session_identifier,
        }

        decoded_msg = self.dcs_decode(content, pdu_params['data_coding'])

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=decoded_msg,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Ejemplo n.º 29
0
 def get_conversations(self, conv_pointers):
     results = yield gatherResults([
         self.get_conversation_fallback(account_key, conv_key)
         for account_key, conv_key in conv_pointers
     ])
     conversations = []
     for pointer, conv in zip(conv_pointers, results):
         if conv is None:
             log.warning("Conversation %s for account %s not found." %
                         (pointer[1], pointer[0]))
         else:
             conversations.append(conv)
     returnValue(conversations)
Ejemplo n.º 30
0
    def handle_delivery_report(self, receipted_message_id, delivery_status):
        message_id = yield self.message_stash.get_internal_message_id(
            receipted_message_id)
        if message_id is None:
            log.warning("Failed to retrieve message id for delivery report."
                        " Delivery report from %s discarded."
                        % self.transport_name)
            return

        dr = yield self.publish_delivery_report(
            user_message_id=message_id,
            delivery_status=delivery_status)
        returnValue(dr)
Ejemplo n.º 31
0
 def connectionMade(self):
     EsmeTransceiverFactory.protocol.connectionMade(self)
     config = self.vumi_transport.get_static_config()
     password = config.password
     # Overly long passwords should be truncated.
     if len(password) > 8:
         password = password[:8]
         log.warning("Password longer than 8 characters, truncating.")
     self.bind(system_id=config.system_id,
               password=password,
               system_type=config.system_type,
               interface_version=config.interface_version,
               address_range=config.address_range)
Ejemplo n.º 32
0
 def find_target(self, config, msg, connector_name):
     endpoint_name = msg.get_routing_endpoint()
     endpoint_routing = config.routing_table.get(connector_name)
     if endpoint_routing is None:
         log.warning("No routing information for connector '%s'" % (
                 connector_name,))
         return None
     target = endpoint_routing.get(endpoint_name)
     if target is None:
         log.warning("No routing information for endpoint '%s' on '%s'" % (
                 endpoint_name, connector_name,))
         return None
     return target
Ejemplo n.º 33
0
 def delivery_report(self, message_id, message_state):
     delivery_status = self.delivery_status(message_state)
     message_id = yield self.r_get_id_for_third_party_id(message_id)
     if message_id is None:
         log.warning("Failed to retrieve message id for delivery report."
                     " Delivery report from %s discarded."
                     % self.transport_name)
         return
     log.msg("PUBLISHING DELIV REPORT: %s %s" % (message_id,
                                                 delivery_status))
     returnValue((yield self.publish_delivery_report(
                 user_message_id=message_id,
                 delivery_status=delivery_status)))
Ejemplo n.º 34
0
    def handle_raw_inbound_message(self, msgid, request):
        request_body = request.content.read()
        log.msg("Inbound message: %r" % (request_body,))
        try:
            body = ET.fromstring(request_body)
        except:
            log.warning("Error parsing request XML: %s" % (request_body,))
            yield self.finish_request(msgid, "", code=400)
            return

        # We always get this.
        session_id = body.find('session_id').text

        status_elem = body.find('status')
        if status_elem is not None:
            # We have a status message. These are all variations on "cancel".
            yield self.handle_status_message(msgid, session_id)
            return

        page_id = body.find('page_id').text

        # They sometimes send us page_id=0 in the middle of a session.
        if page_id == '0' and body.find('mobile_number') is not None:
            # This is a new session.
            session = yield self.save_session(
                session_id,
                from_addr=body.find('mobile_number').text,
                to_addr=body.find('gate').text)  # ???
            session_event = TransportUserMessage.SESSION_NEW
        else:
            # This is an existing session.
            session = yield self.session_manager.load_session(session_id)
            if 'from_addr' not in session:
                # We have a missing or broken session.
                yield self.finish_request(msgid, "", code=400)
                return
            session_event = TransportUserMessage.SESSION_RESUME

        content = body.find('data').text

        transport_metadata = {'session_id': session_id}
        self.publish_message(
                message_id=msgid,
                content=content,
                to_addr=session['to_addr'],
                from_addr=session['from_addr'],
                session_event=session_event,
                transport_name=self.transport_name,
                transport_type=self.config.get('transport_type'),
                transport_metadata=transport_metadata,
                )
Ejemplo n.º 35
0
    def handle_raw_inbound_message(self, msgid, request):
        request_body = request.content.read()
        log.msg("Inbound message: %r" % (request_body, ))
        try:
            body = ET.fromstring(request_body)
        except:
            log.warning("Error parsing request XML: %s" % (request_body, ))
            yield self.finish_request(msgid, "", code=400)
            return

        # We always get this.
        session_id = body.find('session_id').text

        status_elem = body.find('status')
        if status_elem is not None:
            # We have a status message. These are all variations on "cancel".
            yield self.handle_status_message(msgid, session_id)
            return

        page_id = body.find('page_id').text

        # They sometimes send us page_id=0 in the middle of a session.
        if page_id == '0' and body.find('mobile_number') is not None:
            # This is a new session.
            session = yield self.save_session(
                session_id,
                from_addr=body.find('mobile_number').text,
                to_addr=body.find('gate').text)  # ???
            session_event = TransportUserMessage.SESSION_NEW
        else:
            # This is an existing session.
            session = yield self.session_manager.load_session(session_id)
            if 'from_addr' not in session:
                # We have a missing or broken session.
                yield self.finish_request(msgid, "", code=400)
                return
            session_event = TransportUserMessage.SESSION_RESUME

        content = body.find('data').text

        transport_metadata = {'session_id': session_id}
        self.publish_message(
            message_id=msgid,
            content=content,
            to_addr=session['to_addr'],
            from_addr=session['from_addr'],
            session_event=session_event,
            transport_name=self.transport_name,
            transport_type=self.config.get('transport_type'),
            transport_metadata=transport_metadata,
        )
Ejemplo n.º 36
0
 def connectionMade(self):
     EsmeTransceiverFactory.protocol.connectionMade(self)
     config = self.vumi_transport.get_static_config()
     password = config.password
     # Overly long passwords should be truncated.
     if len(password) > 8:
         password = password[:8]
         log.warning("Password longer than 8 characters, truncating.")
     self.bind(
         system_id=config.system_id,
         password=password,
         system_type=config.system_type,
         interface_version=config.interface_version,
         address_range=config.address_range)
Ejemplo n.º 37
0
    def handle_deliver_sm(self, pdu):
        # These operate before the PDUs ``short_message`` or
        # ``message_payload`` fields have been string decoded.
        # NOTE: order is important!
        pdu_handler_chain = [
            self.dr_processor.handle_delivery_report_pdu,
            self.deliver_sm_processor.handle_multipart_pdu,
            self.deliver_sm_processor.handle_ussd_pdu,
        ]
        for handler in pdu_handler_chain:
            handled = yield handler(pdu)
            if handled:
                self.send_pdu(
                    DeliverSMResp(seq_no(pdu), command_status='ESME_ROK'))
                return

        # At this point we either have a DR in the message payload
        # or have a normal SMS that needs to be decoded and handled.
        content_parts = self.deliver_sm_processor.decode_pdus([pdu])
        if not all([isinstance(part, unicode) for part in content_parts]):
            command_status = self.config.deliver_sm_decoding_error
            log.msg('Not all parts of the PDU were able to be decoded. '
                    'Responding with %s.' % (command_status, ),
                    parts=content_parts)
            self.send_pdu(
                DeliverSMResp(seq_no(pdu), command_status=command_status))
            return

        content = u''.join(content_parts)
        was_cdr = yield self.dr_processor.handle_delivery_report_content(
            content)
        if was_cdr:
            self.send_pdu(DeliverSMResp(seq_no(pdu),
                                        command_status='ESME_ROK'))
            return

        handled = yield self.deliver_sm_processor.handle_short_message_pdu(pdu)
        if handled:
            self.send_pdu(DeliverSMResp(seq_no(pdu),
                                        command_status="ESME_ROK"))
            return

        command_status = self.config.deliver_sm_decoding_error
        log.warning('Unable to process message. '
                    'Responding with %s.' % (command_status, ),
                    content=content,
                    pdu=pdu.get_obj())

        self.send_pdu(DeliverSMResp(seq_no(pdu),
                                    command_status=command_status))
Ejemplo n.º 38
0
    def handle_deliver_sm(self, pdu):
        # These operate before the PDUs ``short_message`` or
        # ``message_payload`` fields have been string decoded.
        # NOTE: order is important!
        pdu_handler_chain = [
            self.dr_processor.handle_delivery_report_pdu,
            self.deliver_sm_processor.handle_multipart_pdu,
            self.deliver_sm_processor.handle_ussd_pdu,
        ]
        for handler in pdu_handler_chain:
            handled = yield handler(pdu)
            if handled:
                self.send_pdu(DeliverSMResp(seq_no(pdu),
                              command_status='ESME_ROK'))
                return

        # At this point we either have a DR in the message payload
        # or have a normal SMS that needs to be decoded and handled.
        content_parts = self.deliver_sm_processor.decode_pdus([pdu])
        if not all([isinstance(part, unicode) for part in content_parts]):
            command_status = self.config.deliver_sm_decoding_error
            log.msg('Not all parts of the PDU were able to be decoded. '
                    'Responding with %s.' % (command_status,),
                    parts=content_parts)
            self.send_pdu(DeliverSMResp(seq_no(pdu),
                          command_status=command_status))
            return

        content = u''.join(content_parts)
        was_cdr = yield self.dr_processor.handle_delivery_report_content(
            content)
        if was_cdr:
            self.send_pdu(DeliverSMResp(seq_no(pdu),
                          command_status='ESME_ROK'))
            return

        handled = yield self.deliver_sm_processor.handle_short_message_pdu(pdu)
        if handled:
            self.send_pdu(DeliverSMResp(seq_no(pdu),
                          command_status="ESME_ROK"))
            return

        command_status = self.config.deliver_sm_decoding_error
        log.warning('Unable to process message. '
                    'Responding with %s.' % (command_status,),
                    content=content, pdu=pdu.get_obj())

        self.send_pdu(DeliverSMResp(seq_no(pdu),
                      command_status=command_status))
Ejemplo n.º 39
0
 def dispatch_outbound_message(self, msg):
     if self.reply_affinity and msg["in_reply_to"]:
         transport_name = self.pop_transport_name(msg)
         if transport_name not in self.transport_name_set:
             log.warning(
                 "LoadBalancer is configured for reply affinity but"
                 " reply for unknown load balancer endpoint %r was"
                 " was received. Using round-robin routing instead." % (transport_name,)
             )
             transport_name = self.transport_name_cycle.next()
     else:
         transport_name = self.transport_name_cycle.next()
     if self.rewrite_transport_names:
         msg["transport_name"] = transport_name
     self.dispatcher.publish_outbound_message(transport_name, msg)
Ejemplo n.º 40
0
 def process_command_stop(self, user_account_key, router_key):
     log.info("Stopping router '%s' for user '%s'." %
              (router_key, user_account_key))
     router = yield self.get_router(user_account_key, router_key)
     if router is None:
         log.warning("Trying to stop missing router '%s' for user '%s'." %
                     (router_key, user_account_key))
         return
     if not router.stopping():
         log.warning(
             "Trying to stop router '%s' for user '%s' with invalid "
             "status: %s" % (router_key, user_account_key, router.status))
         return
     router.set_status_stopped()
     yield router.save()
Ejemplo n.º 41
0
 def find_target(self, config, msg, connector_name):
     endpoint_name = msg.get_routing_endpoint()
     endpoint_routing = config.routing_table.get(connector_name)
     if endpoint_routing is None:
         log.warning("No routing information for connector '%s'" %
                     (connector_name, ))
         return None
     target = endpoint_routing.get(endpoint_name)
     if target is None:
         log.warning("No routing information for endpoint '%s' on '%s'" % (
             endpoint_name,
             connector_name,
         ))
         return None
     return target
Ejemplo n.º 42
0
 def process_command_stop(self, user_account_key, conversation_key):
     conv = yield self.get_conversation(user_account_key, conversation_key)
     if conv is None:
         log.warning(
             "Trying to stop missing conversation '%s' for user '%s'." % (
                 conversation_key, user_account_key))
         return
     if not conv.stopping():
         status = conv.get_status()
         log.warning(
             "Trying to stop conversation '%s' for user '%s' with invalid "
             "status: %s" % (conversation_key, user_account_key, status))
         return
     conv.set_status_stopped()
     yield conv.save()
Ejemplo n.º 43
0
 def process_command_stop(self, user_account_key, conversation_key):
     conv = yield self.get_conversation(user_account_key, conversation_key)
     if conv is None:
         log.warning(
             "Trying to stop missing conversation '%s' for user '%s'." %
             (conversation_key, user_account_key))
         return
     if not conv.stopping():
         status = conv.get_status()
         log.warning(
             "Trying to stop conversation '%s' for user '%s' with invalid "
             "status: %s" % (conversation_key, user_account_key, status))
         return
     conv.set_status_stopped()
     yield conv.save()
Ejemplo n.º 44
0
    def _check_stop_throttling(self):
        """
        Check if we should stop throttling, and stop throttling if we should.

        At a high level, we try each throttled message in our list until all of
        them have been accepted by the SMSC, at which point we stop throttling.

        In more detail:

        We recursively process our list of throttled message_ids until either
        we have none left (at which point we stop throttling) or we find one we
        can successfully look up in our cache.

        When we find a message we can retry, we retry it and return. We remain
        throttled until the SMSC responds. If we're still throttled, the
        message_id gets appended to our list and another check is scheduled for
        later. If we're no longer throttled, this method gets called again
        immediately.

        When there are no more throttled message_ids in our list, we stop
        throttling.
        """
        self._unthrottle_delayedCall = None

        if not self.service.is_bound():
            # We don't have a bound SMPP connection, so try again later.
            log.msg("Can't check throttling while unbound, trying later.")
            self.check_stop_throttling(self.get_static_config().throttle_delay)
            return

        if not self._throttled_message_ids:
            # We have no throttled messages waiting, so stop throttling.
            log.msg("No more throttled messages to retry.")
            self.stop_throttling()
            return

        message_id = self._throttled_message_ids.pop(0)
        message = yield self.message_stash.get_cached_message(message_id)
        if message is None:
            # We can't find this message, so log it and start again.
            log.warning(
                "Could not retrieve throttled message: %s" % (message_id,))
            self.check_stop_throttling(0)
        else:
            # Try handle this message again and leave the rest to our
            # submit_sm_resp handlers.
            log.msg("Retrying throttled message: %s" % (message_id,))
            yield self.handle_outbound_message(message)
Ejemplo n.º 45
0
    def consume_unknown_event(self, event):
        """
        FIXME:  We're forced to do too much hoopla when trying to link events
                back to the conversation the original message was part of.
        """
        outbound_message = yield self.find_outboundmessage_for_event(event)
        if outbound_message is None:
            log.warning('Unable to find message %s for event %s.' % (
                event['user_message_id'], event['event_id']))

        config = yield self.get_message_config(event)
        conversation = config.conversation
        ignore = self.get_api_config(conversation, 'ignore_events', False)
        if not ignore:
            push_url = self.get_api_config(conversation, 'push_event_url')
            yield self.send_event_to_client(event, conversation, push_url)
Ejemplo n.º 46
0
 def process_command_stop(self, user_account_key, router_key):
     log.info("Stopping router '%s' for user '%s'." % (
         router_key, user_account_key))
     router = yield self.get_router(user_account_key, router_key)
     if router is None:
         log.warning(
             "Trying to stop missing router '%s' for user '%s'." % (
                 router_key, user_account_key))
         return
     if not router.stopping():
         log.warning(
             "Trying to stop router '%s' for user '%s' with invalid "
             "status: %s" % (router_key, user_account_key, router.status))
         return
     router.set_status_stopped()
     yield router.save()
Ejemplo n.º 47
0
    def consume_unknown_event(self, event):
        """
        FIXME:  We're forced to do too much hoopla when trying to link events
                back to the conversation the original message was part of.
        """
        outbound_message = yield self.find_outboundmessage_for_event(event)
        if outbound_message is None:
            log.warning('Unable to find message %s for event %s.' %
                        (event['user_message_id'], event['event_id']))

        config = yield self.get_message_config(event)
        conversation = config.conversation
        ignore = self.get_api_config(conversation, 'ignore_events', False)
        if not ignore:
            push_url = self.get_api_config(conversation, 'push_event_url')
            yield self.send_event_to_client(event, conversation, push_url)
Ejemplo n.º 48
0
    def consume_user_message(self, msg):
        # log.msg("Received: %s" % (msg.payload,))
        config = yield self.get_config(msg)
        user_id = msg.user()
        session_event = self._message_session_event(msg)
        session_manager = self.get_session_manager(config)
        session = yield self.load_session(session_manager, user_id)

        if session_event == 'close':
            if ((not config.send_sms_content)
                    or (session and session['state'] != 'more')):
                # Session closed, so clean up and don't reply.
                yield session_manager.clear_session(user_id)
            # We never want to respond to close messages, even if we keep the
            # session alive for the "more" handling.
            return

        if session and ('state' not in session):
            # We've seen at least one session that wasn't empty but didn't have
            # the 'state' field. Let's log this and treat it as an empty
            # session instead.
            log.warning("Bad session, resetting: %s" % (session,))
            session = {}

        if (not session) or (session['state'] == 'more'):
            # If we have no session data, treat this as 'new' even if it isn't.
            # Also, new USSD search overrides old "more content" session.
            session_event = 'new'

        if session_event == 'new':
            session = yield session_manager.create_session(user_id)
            session['state'] = 'new'

        pfunc = getattr(self, 'process_message_%s' % (session['state'],))
        try:
            session = yield pfunc(msg, config, session)
            yield self.handle_session_result(session_manager, user_id, session)
        except Exception as err:
            # Uncomment to raise instead of logging (useful for tests)
            # raise
            if isinstance(err, APIError):
                log.warning("API Error: %s" % (err,))
            else:
                log.err()
            self.fire_metric(config, 'ussd_session_error')
            self.reply_to(msg, config.msg_error, False)
            yield session_manager.clear_session(user_id)
Ejemplo n.º 49
0
    def reconnect_api_clients(self, reason):
        self.disconnect_api_clients()
        if not self.continue_trying:
            log.msg("Not retrying because of explicit request")
            return

        config = self.get_static_config()
        self.retries += 1
        if config.max_retries is not None and (self.retries > config.max_retries):
            log.warning("Abandoning reconnecting after %s attempts." % (self.retries))
            return

        self.delay = min(self.delay * config.factor, config.max_reconnect_delay)
        if config.jitter:
            self.delay = random.normalvariate(self.delay, self.delay * config.jitter)
        log.msg("Will retry in %s seconds" % (self.delay,))
        self.reconnect_call = self.clock.callLater(self.delay, self.connect_api_clients)
Ejemplo n.º 50
0
 def get_ctxt_config(self, ctxt):
     username = getattr(ctxt, 'username', None)
     if username is None:
         raise ValueError("No username provided for retrieving"
                          " RapidSMS conversation.")
     user_account_key, _, conversation_key = username.partition(
         self.AUTH_SEP)
     if not user_account_key or not conversation_key:
         raise ValueError("Invalid username for RapidSMS conversation.")
     conv = yield self.get_conversation(user_account_key, conversation_key)
     if conv is None:
         log.warning("Cannot find conversation '%s' for user '%s'." % (
             conversation_key, user_account_key))
         raise ValueError("No conversation found for retrieiving"
                          " RapidSMS configuration.")
     config = yield self.get_config_for_conversation(conv)
     returnValue(config)
Ejemplo n.º 51
0
    def remove_entry(self, src_conn, src_endpoint):
        src_conn = _to_conn(src_conn)
        src_str = str(src_conn)
        connector_dict = self._routing_table.get(src_str)
        if connector_dict is None or src_endpoint not in connector_dict:
            log.warning(
                "Attempting to remove missing routing entry for (%r, %r)." % (
                    src_str, src_endpoint))
            return None

        old_dest = connector_dict.pop(src_endpoint)

        if not connector_dict:
            # This is the last entry for this connector
            self._routing_table.pop(src_str)

        return old_dest
Ejemplo n.º 52
0
    def consume_content_sms_message(self, msg):
        # log.msg("Received SMS: %s" % (msg.payload,))
        config = yield self.get_config(msg)

        # This is to exclude some spurious messages we might receive.
        if msg['content'] is None:
            self.log_action(msg, 'more-no-content')
            return

        user_id = msg.user()

        session_manager = self.get_session_manager(config)

        session = yield self.load_session(session_manager, user_id)
        self.fire_metric(config, 'sms_more_content_reply')
        if not session:
            self.log_action(msg, 'more-no-session')
            # TODO: Reply with error?
            self.fire_metric(config, 'sms_more_content_reply.no_content')
            return

        if session['state'] != 'more':
            self.log_action(msg, 'more-wrong-session-state')
            return

        # FIXME: This is a stopgap until we can figure out why wy sometimes get
        #        strings instead of integers here.
        raw_more_messages = session.get('more_messages', 0)
        if int(raw_more_messages) != raw_more_messages:
            log.warning("Found non-integer 'more_messages': %r" % (
                raw_more_messages,))
            raw_more_messages = int(raw_more_messages)
        more_messages = raw_more_messages + 1
        session['more_messages'] = more_messages
        if more_messages > 9:
            more_messages = 'extra'
        self.fire_metric(config, 'sms_more_content_reply', more_messages)

        try:
            session = yield self.send_sms_content(msg, config, session)
            yield self.handle_session_result(session_manager, user_id, session)
        except:
            log.err()
            # TODO: Reply with error?
            yield session_manager.clear_session(user_id)
Ejemplo n.º 53
0
    def process_command_initial_action_hack(self, user_account_key,
                                            conversation_key, **kwargs):
        # HACK: This lets us do whatever we used to do when we got a `start'
        # message without having horrible app-specific view logic.
        # TODO: Remove this when we've decoupled the various conversation
        # actions from the lifecycle.

        conv = yield self.get_conversation(user_account_key, conversation_key)
        if conv is None:
            log.warning("Cannot find conversation '%s' for user '%s'." %
                        (user_account_key, conversation_key))
            return

        kwargs.setdefault('content', conv.description)
        kwargs.setdefault('dedupe', False)
        yield self.process_command_bulk_send(user_account_key=user_account_key,
                                             conversation_key=conversation_key,
                                             **kwargs)
Ejemplo n.º 54
0
    def process_command_send_jsbox(self, user_account_key, conversation_key,
                                   batch_id):
        conv = yield self.get_conversation(user_account_key, conversation_key)
        js_config = self.get_jsbox_js_config(conv)
        if js_config is None:
            return
        delivery_class = js_config.get('delivery_class')

        if conv is None:
            log.warning("Cannot find conversation '%s' for user '%s'." %
                        (conversation_key, user_account_key))
            return

        for contacts in (yield
                         conv.get_opted_in_contact_bunches(delivery_class)):
            for contact in (yield contacts):
                to_addr = contact.addr_for(delivery_class)
                yield self.send_inbound_push_trigger(to_addr, conv)