def send(self): lookup = DNSLookup() settings = SIPSimpleSettings() account = DefaultAccount() if account.sip.outbound_proxy is not None: uri = SIPURI( host=account.sip.outbound_proxy.host, port=account.sip.outbound_proxy.port, parameters={'transport': account.sip.outbound_proxy.transport}) else: uri = self.to_uri try: routes = lookup.lookup_sip_proxy( uri, settings.sip.transport_list).wait() except DNSLookupError: msg = 'DNS lookup error while looking for %s proxy' % uri log.warning(msg) raise SIPMessageError(0, msg) else: route = routes.pop(0) from_header = FromHeader(self.from_uri) to_header = ToHeader(self.to_uri) route_header = RouteHeader(route.uri) notification_center = NotificationCenter() for chunk in chunks(self.body, 1000): if self.use_cpim: additional_headers = [] payload = CPIMPayload( self.body.encode('utf-8'), self.content_type, charset='utf-8', sender=ChatIdentity(self.from_uri, None), recipients=[ChatIdentity(self.to_uri, None)], additional_headers=additional_headers) payload, content_type = payload.encode() else: content_type = self.content_type payload = self.body.encode('utf-8') request = SIPMessageRequest(from_header, to_header, route_header, content_type, payload) notification_center.add_observer(self, sender=request) self._requests.add(request) request.send() error = None count = len(self._requests) while count > 0: notification = self._channel.wait() if notification.name == 'SIPMessageDidFail': error = (notification.data.code, notification.data.reason) count -= 1 self._requests.clear() if error is not None: raise SIPMessageError(*error)
def incoming_message(self, message_request, data): content_type = data.headers.get("Content-Type", Null).content_type from_header = data.headers.get("From", Null) to_header = data.headers.get("To", Null) if Null in (content_type, from_header, to_header): message_request.answer(400) return log.msg("New SIP Message from %s to %s" % (from_header.uri, to_header.uri)) # Check domain if from_header.uri.host not in XMPPGatewayConfig.domains: log.msg("Message rejected: From domain is not a local XMPP domain") message_request.answer(606) return if content_type == "message/cpim": try: cpim_message = CPIMPayload.decode(data.body) except CPIMParserError: log.msg("Message rejected: CPIM parse error") message_request.answer(400) return else: body = cpim_message.content content_type = cpim_message.content_type sender = cpim_message.sender or from_header from_uri = sender.uri else: body = data.body from_uri = from_header.uri to_uri = str(to_header.uri) message_request.answer(200) if from_uri.parameters.get("gr", None) is None: from_uri = SIPURI.new(from_uri) from_uri.parameters["gr"] = generate_sylk_resource() sender = Identity(FrozenURI.parse(from_uri)) recipient = Identity(FrozenURI.parse(to_uri)) if content_type in ("text/plain", "text/html"): if content_type == "text/plain": html_body = None else: html_body = body body = None if XMPPGatewayConfig.use_msrp_for_chat: message = NormalMessage(sender, recipient, body, html_body, use_receipt=False) self.xmpp_manager.send_stanza(message) else: message = ChatMessage(sender, recipient, body, html_body, use_receipt=False) self.xmpp_manager.send_stanza(message) elif content_type == IsComposingDocument.content_type: if not XMPPGatewayConfig.use_msrp_for_chat: try: msg = IsComposingMessage.parse(body) except ParserError: pass else: state = "composing" if msg.state == "active" else "paused" message = ChatComposingIndication(sender, recipient, state, use_receipt=False) self.xmpp_manager.send_stanza(message)
def incoming_message(self, message_request, data): content_type = data.headers.get('Content-Type', Null).content_type from_header = data.headers.get('From', Null) to_header = data.headers.get('To', Null) if Null in (content_type, from_header, to_header): message_request.answer(400) return log.info('New SIP Message from %s to %s' % (from_header.uri, to_header.uri)) # Check domain if from_header.uri.host not in XMPPGatewayConfig.domains: log.info('Message rejected: From domain is not a local XMPP domain') message_request.answer(606) return if content_type == 'message/cpim': try: cpim_message = CPIMPayload.decode(data.body) except CPIMParserError: log.info('Message rejected: CPIM parse error') message_request.answer(400) return else: body = cpim_message.content content_type = cpim_message.content_type sender = cpim_message.sender or from_header from_uri = sender.uri else: body = data.body from_uri = from_header.uri to_uri = str(to_header.uri) message_request.answer(200) if from_uri.parameters.get('gr', None) is None: from_uri = SIPURI.new(from_uri) from_uri.parameters['gr'] = generate_sylk_resource() sender = Identity(FrozenURI.parse(from_uri)) recipient = Identity(FrozenURI.parse(to_uri)) if content_type in ('text/plain', 'text/html'): if content_type == 'text/plain': html_body = None else: html_body = body body = None if XMPPGatewayConfig.use_msrp_for_chat: message = NormalMessage(sender, recipient, body, html_body, use_receipt=False) self.xmpp_manager.send_stanza(message) else: message = ChatMessage(sender, recipient, body, html_body, use_receipt=False) self.xmpp_manager.send_stanza(message) elif content_type == IsComposingDocument.content_type: if not XMPPGatewayConfig.use_msrp_for_chat: try: msg = IsComposingMessage.parse(body) except ParserError: pass else: state = 'composing' if msg.state == 'active' else 'paused' message = ChatComposingIndication(sender, recipient, state, use_receipt=False) self.xmpp_manager.send_stanza(message)
def _send(self): if self.session.routes: from_uri = self.account.uri content = self.content if isinstance( self.content, bytes) else self.content.encode() additional_sip_headers = [] if self.account.sms.use_cpim: ns = CPIMNamespace('urn:ietf:params:imdn', 'imdn') additional_headers = [CPIMHeader('Message-ID', ns, self.id)] if self.account.sms.enable_imdn and self.content_type not in self.__disabled_imdn_content_types__: additional_headers.append( CPIMHeader('Disposition-Notification', ns, 'positive-delivery, display')) payload = CPIMPayload( content, self.content_type, charset='utf-8', sender=ChatIdentity(from_uri, self.account.display_name), recipients=[ChatIdentity(self.sip_uri, None)], timestamp=str(self.timestamp), additional_headers=additional_headers) payload, content_type = payload.encode() else: payload = content content_type = self.content_type route = self.session.routes[0] message_request = Message(FromHeader(from_uri, self.account.display_name), ToHeader(self.sip_uri), RouteHeader(route.uri), content_type, payload, credentials=self.account.credentials, extra_headers=additional_sip_headers) notification_center = NotificationCenter() notification_center.add_observer(self, sender=message_request) message_request.send() else: pass
def composeReplicationMessage(self, sent_message, response_code): if sent_message.content_type == "application/im-iscomposing+xml": return if isinstance(self.account, Account): if not self.account.sms.disable_replication: contact = NSApp.delegate( ).contactsWindowController.getFirstContactMatchingURI( self.target_uri) msg = CPIMPayload( sent_message.content, sent_message.content_type, charset='utf-8', sender=ChatIdentity(self.account.uri, self.account.display_name), recipients=[ ChatIdentity(self.target_uri, contact.name if contact else None) ]) self.sendReplicationMessage(response_code, msg.encode()[0], content_type='message/cpim')
def _NH_SIPEngineGotMessage(self, sender, data): account = AccountManager().find_account(data.request_uri) if not account: BlinkLogger().log_warning( "Could not find local account for incoming SMS to %s, using default" % data.request_uri) account = AccountManager().default_account call_id = data.headers.get('Call-ID', Null).body try: self.received_call_ids.remove(call_id) except KeyError: self.received_call_ids.add(call_id) else: # drop duplicate message received return is_cpim = False cpim_message = None is_replication_message = False if data.content_type == 'message/cpim': try: cpim_message = CPIMPayload.decode(data.body) except CPIMParserError: BlinkLogger().log_warning( "Incoming SMS from %s to %s has invalid CPIM content" % format_identity_to_string(data.from_header), account.id) return else: is_cpim = True content = cpim_message.content content_type = cpim_message.content_type sender_identity = cpim_message.sender or data.from_header if cpim_message.sender and data.from_header.uri == data.to_header.uri and data.from_header.uri == cpim_message.sender.uri: is_replication_message = True window_tab_identity = cpim_message.recipients[ 0] if cpim_message.recipients else data.to_header else: window_tab_identity = data.from_header else: content = data.body.decode('utf-8') content_type = data.content_type sender_identity = data.from_header window_tab_identity = sender_identity is_html = content_type == 'text/html' if content_type in ('text/plain', 'text/html'): pass #BlinkLogger().log_info(u"Incoming SMS %s from %s to %s received" % (call_id, format_identity_to_string(sender_identity), account.id)) elif content_type == 'application/im-iscomposing+xml': # body must not be utf-8 decoded content = cpim_message.content if is_cpim else data.body msg = IsComposingMessage.parse(content) state = msg.state.value refresh = msg.refresh.value if msg.refresh is not None else None content_type = msg.content_type.value if msg.content_type is not None else None last_active = msg.last_active.value if msg.last_active is not None else None viewer = self.openMessageWindow(SIPURI.new( window_tab_identity.uri), window_tab_identity.display_name, account, create_if_needed=False, note_new_message=False) if viewer: viewer.gotIsComposing(self.windowForViewer(viewer), state, refresh, last_active) return else: BlinkLogger().log_warning( "Incoming SMS %s from %s to %s has unknown content-type %s" % (call_id, format_identity_to_string( data.from_header), account.id, data.content_type)) return # display the message note_new_message = False if is_replication_message else True viewer = self.openMessageWindow(SIPURI.new(window_tab_identity.uri), window_tab_identity.display_name, account, note_new_message=note_new_message) self.windowForViewer(viewer).noteNewMessageForSession_(viewer) replication_state = None replication_timestamp = None if is_replication_message: replicated_response_code = data.headers.get( 'X-Replication-Code', Null).body if replicated_response_code == '202': replication_state = 'deferred' elif replicated_response_code == '200': replication_state = 'delivered' else: replication_state = 'failed' replicated_timestamp = data.headers.get('X-Replication-Timestamp', Null).body try: replication_timestamp = ISOTimestamp(replicated_timestamp) except Exception: replication_timestamp = ISOTimestamp.now() window = self.windowForViewer(viewer).window() viewer.gotMessage(sender_identity, call_id, content, is_html, is_replication_message, replication_timestamp, window=window) self.windowForViewer(viewer).noteView_isComposing_(viewer, False)
def _NH_SIPEngineGotMessage(self, notification): account_manager = AccountManager() account = account_manager.find_account(notification.data.request_uri) if account is None: return data = notification.data content_type = data.headers.get('Content-Type', Null).content_type from_header = data.headers.get('From', Null) x_replicated_message = data.headers.get('X-Replicated-Message', Null) to_header = data.headers.get('To', Null) if x_replicated_message is Null: cpim_message = None if content_type == "message/cpim": try: cpim_message = CPIMPayload.decode(data.body) except CPIMParserError: log.warning( 'SIP message from %s to %s rejected: CPIM parse error' % (from_header.uri, '%s@%s' % (to_header.uri.user, to_header.uri.host))) return body = cpim_message.content if isinstance( cpim_message.content, str) else cpim_message.content.decode() content_type = cpim_message.content_type sender = cpim_message.sender or from_header disposition = next( ([item.strip() for item in header.value.split(',')] for header in cpim_message.additional_headers if header.name == 'Disposition-Notification'), None) message_id = next( (header.value for header in cpim_message.additional_headers if header.name == 'Message-ID'), str(uuid.uuid4())) else: payload = SimplePayload.decode(data.body, data.content_type) body = payload.content.decode() content_type = payload.content_type sender = from_header disposition = None message_id = str(uuid.uuid4()) if (content_type.lower().startswith('text/') and '-----BEGIN PGP MESSAGE-----' in body and body.strip().endswith('-----END PGP MESSAGE-----') and content_type != 'text/pgp-private-key'): return if content_type.lower() == 'text/pgp-public-key': return from blink.contacts import URIUtils contact, contact_uri = URIUtils.find_contact(sender.uri) session_manager = SessionManager() notification_center = NotificationCenter() try: blink_session = next( session for session in self.sessions if session.reusable and session.contact.settings is contact.settings) except StopIteration: if content_type.lower() in [ IsComposingDocument.content_type, IMDNDocument.content_type ]: return else: blink_session = session_manager.create_session( contact, contact_uri, [StreamDescription('messages')], account=account, connect=False) if content_type.lower() == IsComposingDocument.content_type: try: document = IsComposingMessage.parse(body) except ParserError as e: log.warning('Failed to parse Is-Composing payload: %s' % str(e)) else: data = NotificationData( state=document.state.value, refresh=document.refresh.value if document.refresh is not None else 120, content_type=document.content_type.value if document.content_type is not None else None, last_active=document.last_active.value if document.last_active is not None else None, sender=sender) notification_center.post_notification( 'BlinkGotComposingIndication', sender=blink_session, data=data) return timestamp = str( cpim_message.timestamp ) if cpim_message is not None and cpim_message.timestamp is not None else str( ISOTimestamp.now()) if account.sms.use_cpim and account.sms.enable_imdn and content_type.lower( ) == IMDNDocument.content_type: # print("-- IMDN received") document = IMDNDocument.parse(body) imdn_message_id = document.message_id.value imdn_status = document.notification.status.__str__() imdn_datetime = document.datetime.__str__() notification_center.post_notification( 'BlinkGotDispositionNotification', sender=blink_session, data=NotificationData(id=imdn_message_id, status=imdn_status)) return message = BlinkMessage(body, content_type, sender, timestamp=timestamp, id=message_id, disposition=disposition) notification_center.post_notification('BlinkMessageIsParsed', sender=blink_session, data=message) if disposition is not None and 'positive-delivery' in disposition: # print("-- Should send delivered imdn") self.send_imdn_message(blink_session, message_id, timestamp, 'delivered') self._add_contact_to_messages_group(blink_session) notification_center.post_notification('BlinkGotMessage', sender=blink_session, data=message) else: # TODO handle replicated messages pass
def _NH_SIPEngineGotMessage(self, sender, data): account = AccountManager().find_account(data.request_uri) if not account: BlinkLogger().log_warning( "Could not find local account for incoming SMS to %s, using default" % data.request_uri) account = AccountManager().default_account call_id = data.headers.get('Call-ID', Null).body try: self.received_call_ids.remove(call_id) except KeyError: self.received_call_ids.add(call_id) else: # drop duplicate message received return is_cpim = False cpim_message = None is_replication_message = False imdn_id = None imdn_timestamp = None if data.content_type == 'message/cpim': try: cpim_message = CPIMPayload.decode(data.body) except CPIMParserError: BlinkLogger().log_warning( "Incoming SMS from %s to %s has invalid CPIM content" % format_identity_to_string(data.from_header), account.id) return else: is_cpim = True content = cpim_message.content content_type = cpim_message.content_type sender_identity = cpim_message.sender or data.from_header cpim_imdn_events = None imdn_timestamp = cpim_message.timestamp for h in cpim_message.additional_headers: if h.name == "Message-ID": imdn_id = h.value if h.name == "Disposition-Notification": cpim_imdn_events = h.value if imdn_id: if cpim_imdn_events and 'positive-delivery' in cpim_imdn_events and imdn_timestamp: BlinkLogger().log_info( "Will send IMDN delivery notification for %s" % imdn_id) else: imdn_id = None if cpim_message.sender and data.from_header.uri == data.to_header.uri and data.from_header.uri == cpim_message.sender.uri: is_replication_message = True window_tab_identity = cpim_message.recipients[ 0] if cpim_message.recipients else data.to_header else: window_tab_identity = data.from_header else: content = data.body content_type = data.content_type sender_identity = data.from_header window_tab_identity = sender_identity if content_type in ('text/plain', 'text/html'): BlinkLogger().log_info( u"Incoming SMS %s from %s to %s received" % (call_id, format_identity_to_string(sender_identity), account.id)) elif content_type in ('text/rsa-public-key'): uri = format_identity_to_string(sender_identity) BlinkLogger().log_info( u"Public key from %s received" % (format_identity_to_string(sender_identity))) if uri == account.id: BlinkLogger().log_info( u"Public key save skipped for own account") return public_key = '' start_public = False for l in content.decode().split("\n"): if l == "-----BEGIN RSA PUBLIC KEY-----": start_public = True if l == "-----END RSA PUBLIC KEY-----": public_key = public_key + l start_public = False break if start_public: public_key = public_key + l + '\n' if public_key: blink_contact = NSApp.delegate( ).contactsWindowController.getFirstContactFromAllContactsGroupMatchingURI( uri) if blink_contact is not None: contact = blink_contact.contact if contact.public_key != public_key: contact.public_key = public_key contact.public_key_checksum = hashlib.sha1( public_key.encode()).hexdigest() contact.save() BlinkLogger().log_info( u"Public key %s from %s saved " % (contact.public_key_checksum, data.from_header.uri)) nc_title = NSLocalizedString( "Public key", "System notification title") nc_subtitle = format_identity_to_string( sender_identity, check_contact=True, format='full') nc_body = NSLocalizedString( "Public key has changed", "System notification title") NSApp.delegate().gui_notify(nc_title, nc_body, nc_subtitle) else: BlinkLogger().log_info( u"Public key from %s has not changed" % data.from_header.uri) else: BlinkLogger().log_info(u"No contact found to save the key") return elif content_type in ('text/rsa-private-key'): BlinkLogger().log_info(u"Private key from %s to %s received" % (data.from_header.uri, account.id)) if account.id == str(data.from_header.uri).split(":")[1]: self.import_key_window = ImportPrivateKeyController( account, content) self.import_key_window.show() return elif content_type in ('message/imdn+xml'): document = IMDNDocument.parse(content) imdn_message_id = document.message_id.value imdn_status = document.notification.status.__str__() BlinkLogger().log_info(u"Received IMDN message %s" % imdn_status) viewer = self.openMessageWindow( SIPURI.new(window_tab_identity.uri), window_tab_identity.display_name, account) if viewer and imdn_status == 'displayed': viewer.chatViewController.markMessage(imdn_message_id, MSG_STATE_DISPLAYED) return elif content_type == 'application/im-iscomposing+xml': content = cpim_message.content if is_cpim else data.body msg = IsComposingMessage.parse(content) state = msg.state.value refresh = msg.refresh.value if msg.refresh is not None else None content_type = msg.content_type.value if msg.content_type is not None else None last_active = msg.last_active.value if msg.last_active is not None else None viewer = self.openMessageWindow(SIPURI.new( window_tab_identity.uri), window_tab_identity.display_name, account, create_if_needed=False, note_new_message=False) if viewer: viewer.gotIsComposing(self.windowForViewer(viewer), state, refresh, last_active) return else: BlinkLogger().log_warning( "Incoming SMS %s from %s to %s has unknown content-type %s" % (call_id, format_identity_to_string( data.from_header), account.id, content_type)) return # display the message note_new_message = False if is_replication_message else True viewer = self.openMessageWindow(SIPURI.new(window_tab_identity.uri), window_tab_identity.display_name, account, note_new_message=note_new_message) self.windowForViewer(viewer).noteNewMessageForSession_(viewer) replication_state = None replication_timestamp = None if is_replication_message: replicated_response_code = data.headers.get( 'X-Replication-Code', Null).body if replicated_response_code == '202': replication_state = 'deferred' elif replicated_response_code == '200': replication_state = 'delivered' else: replication_state = 'failed' replicated_timestamp = data.headers.get('X-Replication-Timestamp', Null).body try: replication_timestamp = ISOTimestamp(replicated_timestamp) except Exception: replication_timestamp = ISOTimestamp.now() window = self.windowForViewer(viewer).window() viewer.gotMessage(sender_identity, call_id, content, content_type, is_replication_message, replication_timestamp, window=window, imdn_id=imdn_id, imdn_timestamp=imdn_timestamp) self.windowForViewer(viewer).noteView_isComposing_(viewer, False)