class Logger(object): implements(IObserver) # public methods # def __init__( self, sip_to_stdout=False, msrp_to_stdout=False, pjsip_to_stdout=False, notifications_to_stdout=False, msrp_level=log.level.ERROR, ): self.sip_to_stdout = sip_to_stdout self.msrp_to_stdout = msrp_to_stdout self.pjsip_to_stdout = pjsip_to_stdout self.notifications_to_stdout = notifications_to_stdout self.msrp_level = msrp_level self._siptrace_filename = None self._siptrace_file = None self._siptrace_error = False self._siptrace_start_time = None self._siptrace_packet_count = 0 self._msrptrace_filename = None self._msrptrace_file = None self._msrptrace_error = False self._pjsiptrace_filename = None self._pjsiptrace_file = None self._pjsiptrace_error = False self._notifications_filename = None self._notifications_file = None self._notifications_error = False self._event_queue = EventQueue(handler=self._process_notification, name="Log handling") self._log_directory_error = False def start(self): # try to create the log directory try: self._init_log_directory() except Exception: pass # register to receive log notifications notification_center = NotificationCenter() notification_center.add_observer(self) # start the thread processing the notifications self._event_queue.start() def stop(self): # stop the thread processing the notifications self._event_queue.stop() self._event_queue.join() # close sip trace file if self._siptrace_file is not None: self._siptrace_file.close() self._siptrace_file = None # close msrp trace file if self._msrptrace_file is not None: self._msrptrace_file.close() self._msrptrace_file = None # close pjsip trace file if self._pjsiptrace_file is not None: self._pjsiptrace_file.close() self._pjsiptrace_file = None # close notifications trace file if self._notifications_file is not None: self._notifications_file.close() self._notifications_file = None # unregister from receiving notifications notification_center = NotificationCenter() notification_center.remove_observer(self) def handle_notification(self, notification): self._event_queue.put(notification) def _process_notification(self, notification): settings = SIPSimpleSettings() handler = getattr(self, "_NH_%s" % notification.name, None) if handler is not None: handler(notification) handler = getattr(self, "_LH_%s" % notification.name, None) if handler is not None: handler(notification) if notification.name not in ("SIPEngineLog", "SIPEngineSIPTrace") and ( self.notifications_to_stdout or settings.logs.trace_notifications ): message = "Notification name=%s sender=%s" % (notification.name, notification.sender) if notification.data is not None: message += "\n%s" % pformat(notification.data.__dict__) if self.notifications_to_stdout: print "%s: %s" % (datetime.datetime.now(), message) if settings.logs.trace_notifications: try: self._init_log_file("notifications") except Exception: pass else: self._notifications_file.write( "%s [%s %d]: %s\n" % (datetime.datetime.now(), os.path.basename(sys.argv[0]).rstrip(".py"), os.getpid(), message) ) self._notifications_file.flush() # notification handlers # def _NH_CFGSettingsObjectDidChange(self, notification): settings = SIPSimpleSettings() if notification.sender is settings: if "logs.directory" in notification.data.modified: # sip trace if self._siptrace_file is not None: self._siptrace_file.close() self._siptrace_file = None # pjsip trace if self._pjsiptrace_file is not None: self._pjsiptrace_file.close() self._pjsiptrace_file = None # notifications trace if self._notifications_file is not None: self._notifications_file.close() self._notifications_file = None # try to create the log directory try: self._init_log_directory() except Exception: pass # log handlers # def _LH_SIPEngineSIPTrace(self, notification): settings = SIPSimpleSettings() if not self.sip_to_stdout and not settings.logs.trace_sip: return if self._siptrace_start_time is None: self._siptrace_start_time = notification.data.timestamp self._siptrace_packet_count += 1 if notification.data.received: direction = "RECEIVED" else: direction = "SENDING" buf = [ "%s: Packet %d, +%s" % (direction, self._siptrace_packet_count, (notification.data.timestamp - self._siptrace_start_time)) ] buf.append( "%(source_ip)s:%(source_port)d -(SIP over %(transport)s)-> %(destination_ip)s:%(destination_port)d" % notification.data.__dict__ ) buf.append(notification.data.data) buf.append("--") message = "\n".join(buf) if self.sip_to_stdout: print "%s: %s\n" % (notification.data.timestamp, message) if settings.logs.trace_sip: try: self._init_log_file("siptrace") except Exception: pass else: self._siptrace_file.write( "%s [%s %d]: %s\n" % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip(".py"), os.getpid(), message) ) self._siptrace_file.flush() def _LH_SIPEngineLog(self, notification): settings = SIPSimpleSettings() if not self.pjsip_to_stdout and not settings.logs.trace_pjsip: return message = "(%(level)d) %(sender)14s: %(message)s" % notification.data.__dict__ if self.pjsip_to_stdout: print "%s %s" % (notification.data.timestamp, message) if settings.logs.trace_pjsip: try: self._init_log_file("pjsiptrace") except Exception: pass else: self._pjsiptrace_file.write( "%s [%s %d] %s\n" % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip(".py"), os.getpid(), message) ) self._pjsiptrace_file.flush() def _LH_DNSLookupTrace(self, notification): settings = SIPSimpleSettings() if not self.sip_to_stdout and not settings.logs.trace_sip: return message = "DNS lookup %(query_type)s %(query_name)s" % notification.data.__dict__ if notification.data.error is None: message += " succeeded, ttl=%d: " % notification.data.answer.ttl if notification.data.query_type == "A": message += ", ".join(record.address for record in notification.data.answer) elif notification.data.query_type == "SRV": message += ", ".join( "%d %d %d %s" % (record.priority, record.weight, record.port, record.target) for record in notification.data.answer ) elif notification.data.query_type == "NAPTR": message += ", ".join( '%d %d "%s" "%s" "%s" %s' % (record.order, record.preference, record.flags, record.service, record.regexp, record.replacement) for record in notification.data.answer ) else: import dns.resolver message_map = { dns.resolver.NXDOMAIN: "DNS record does not exist", dns.resolver.NoAnswer: "DNS response contains no answer", dns.resolver.NoNameservers: "no DNS name servers could be reached", dns.resolver.Timeout: "no DNS response received, the query has timed out", } message += " failed: %s" % message_map.get(notification.data.error.__class__, "") if self.sip_to_stdout: print "%s: %s" % (notification.data.timestamp, message) if settings.logs.trace_sip: try: self._init_log_file("siptrace") except Exception: pass else: self._siptrace_file.write( "%s [%s %d]: %s\n" % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip(".py"), os.getpid(), message) ) self._siptrace_file.flush() def _LH_MSRPTransportTrace(self, notification): settings = SIPSimpleSettings() if not self.msrp_to_stdout and not settings.logs.trace_msrp: return arrow = {"incoming": "<--", "outgoing": "-->"}[notification.data.direction] local_address = notification.sender.getHost() local_address = "%s:%d" % (local_address.host, local_address.port) remote_address = notification.sender.getPeer() remote_address = "%s:%d" % (remote_address.host, remote_address.port) message = "%s %s %s\n" % (local_address, arrow, remote_address) + notification.data.data if self.msrp_to_stdout: print "%s: %s" % (notification.data.timestamp, message) if settings.logs.trace_msrp: try: self._init_log_file("msrptrace") except Exception: pass else: self._msrptrace_file.write( "%s [%s %d]: %s\n" % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip(".py"), os.getpid(), message) ) self._msrptrace_file.flush() def _LH_MSRPLibraryLog(self, notification): settings = SIPSimpleSettings() if not self.msrp_to_stdout and not settings.logs.trace_msrp: return if notification.data.level < self.msrp_level: return message = "%s%s" % (notification.data.level.prefix, notification.data.message) if self.msrp_to_stdout: print "%s: %s" % (notification.data.timestamp, message) if settings.logs.trace_msrp: try: self._init_log_file("msrptrace") except Exception: pass else: self._msrptrace_file.write( "%s [%s %d]: %s\n" % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip(".py"), os.getpid(), message) ) self._msrptrace_file.flush() # private methods # def _init_log_directory(self): settings = SIPSimpleSettings() log_directory = settings.logs.directory.normalized try: makedirs(log_directory) except Exception, e: if not self._log_directory_error: print "failed to create logs directory '%s': %s" % (log_directory, e) self._log_directory_error = True self._siptrace_error = True self._pjsiptrace_error = True self._notifications_error = True raise else:
class FileLogger(object): implements(IObserver) # public methods # def __init__(self, msrp_level=log.level.INFO): self.msrp_level = msrp_level self._siptrace_filename = None self._siptrace_file = None self._siptrace_error = False self._siptrace_start_time = None self._siptrace_packet_count = 0 self._msrptrace_filename = None self._msrptrace_file = None self._msrptrace_error = False self._pjsiptrace_filename = None self._pjsiptrace_file = None self._pjsiptrace_error = False self._notifications_filename = None self._notifications_file = None self._notifications_error = False self._event_queue = EventQueue(handler=self._process_notification, name='Log handling') self._log_directory_error = False def start(self): # try to create the log directory try: self._init_log_directory() except Exception: pass # register to receive log notifications notification_center = NotificationCenter() notification_center.add_observer(self) # start the thread processing the notifications self._event_queue.start() def stop(self): # stop the thread processing the notifications self._event_queue.stop() self._event_queue.join() # close sip trace file if self._siptrace_file is not None: self._siptrace_file.close() self._siptrace_file = None # close msrp trace file if self._msrptrace_file is not None: self._msrptrace_file.close() self._msrptrace_file = None # close pjsip trace file if self._pjsiptrace_file is not None: self._pjsiptrace_file.close() self._pjsiptrace_file = None # close notifications trace file if self._notifications_file is not None: self._notifications_file.close() self._notifications_file = None # unregister from receiving notifications notification_center = NotificationCenter() notification_center.remove_observer(self) def handle_notification(self, notification): self._event_queue.put(notification) @allocate_autorelease_pool def _process_notification(self, notification): settings = SIPSimpleSettings() handler = getattr(self, '_NH_%s' % notification.name, None) if handler is not None: handler(notification) handler = getattr(self, '_LH_%s' % notification.name, None) if handler is not None: handler(notification) if notification.name not in ('SIPEngineLog', 'SIPEngineSIPTrace') and settings.logs.trace_notifications: try: self._init_log_file('notifications') except Exception: pass else: message = 'Notification name=%s sender=%s data=%s' % (notification.name, notification.sender, pformat(notification.data)) self._notifications_file.write('%s: %s\n' % (datetime.datetime.now(), message)) self._notifications_file.flush() # notification handlers # def _NH_CFGSettingsObjectDidChange(self, notification): settings = SIPSimpleSettings() if notification.sender is settings: if 'logs.directory' in notification.data.modified: # sip trace if self._siptrace_file is not None: self._siptrace_file.close() self._siptrace_file = None # pjsip trace if self._pjsiptrace_file is not None: self._pjsiptrace_file.close() self._pjsiptrace_file = None # notifications trace if self._notifications_file is not None: self._notifications_file.close() self._notifications_file = None # try to create the log directory try: self._init_log_directory() except Exception: pass # log handlers # def _LH_SIPEngineSIPTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip: return if self._siptrace_start_time is None: self._siptrace_start_time = notification.data.timestamp self._siptrace_packet_count += 1 if notification.data.received: direction = "RECEIVED" else: direction = "SENDING" buf = ["%s: Packet %d, +%s" % (direction, self._siptrace_packet_count, (notification.data.timestamp - self._siptrace_start_time))] buf.append("%(source_ip)s:%(source_port)d -(SIP over %(transport)s)-> %(destination_ip)s:%(destination_port)d" % notification.data.__dict__) buf.append(notification.data.data) buf.append('--') message = '\n'.join(buf) try: self._init_log_file('siptrace') except Exception: pass else: self._siptrace_file.write('%s [%s %d]: %s\n' % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._siptrace_file.flush() def _LH_SIPEngineLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_pjsip: return message = "(%(level)d) %(sender)14s: %(message)s" % notification.data.__dict__ try: self._init_log_file('pjsiptrace') except Exception: pass else: self._pjsiptrace_file.write('%s [%s %d] %s\n' % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._pjsiptrace_file.flush() def _LH_DNSLookupTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip: return message = 'DNS lookup %(query_type)s %(query_name)s' % notification.data.__dict__ if notification.data.error is None: message += ' succeeded, ttl=%d: ' % notification.data.answer.ttl if notification.data.query_type == 'A': message += ", ".join(record.address for record in notification.data.answer) elif notification.data.query_type == 'SRV': message += ", ".join('%d %d %d %s' % (record.priority, record.weight, record.port, record.target) for record in notification.data.answer) elif notification.data.query_type == 'NAPTR': message += ", ".join('%d %d "%s" "%s" "%s" %s' % (record.order, record.preference, record.flags, record.service, record.regexp, record.replacement) for record in notification.data.answer) else: import dns.resolver message_map = {dns.resolver.NXDOMAIN: 'DNS record does not exist', dns.resolver.NoAnswer: 'DNS response contains no answer', dns.resolver.NoNameservers: 'no DNS name servers could be reached', dns.resolver.Timeout: 'no DNS response received, the query has timed out'} message += ' failed: %s' % message_map.get(notification.data.error.__class__, '') try: self._init_log_file('siptrace') except Exception: pass else: self._siptrace_file.write('%s [%s %d]: %s\n' % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._siptrace_file.flush() def _LH_MSRPTransportTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp: return arrow = {'incoming': '<--', 'outgoing': '-->'}[notification.data.direction] local_address = notification.sender.getHost() local_address = '%s:%d' % (local_address.host, local_address.port) remote_address = notification.sender.getPeer() remote_address = '%s:%d' % (remote_address.host, remote_address.port) message = '%s %s %s\n' % (local_address, arrow, remote_address) + notification.data.data try: self._init_log_file('msrptrace') except Exception: pass else: self._msrptrace_file.write('%s [%s %d]: %s\n' % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._msrptrace_file.flush() def _LH_MSRPLibraryLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp: return if notification.data.level < self.msrp_level: return message = '%s%s' % (notification.data.level.prefix, notification.data.message) try: self._init_log_file('msrptrace') except Exception: pass else: self._msrptrace_file.write('%s [%s %d]: %s\n' % (notification.data.timestamp, os.path.basename(sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._msrptrace_file.flush() # private methods # def _init_log_directory(self): settings = SIPSimpleSettings() log_directory = settings.logs.directory.normalized try: makedirs(log_directory) except Exception, e: if not self._log_directory_error: print "failed to create logs directory '%s': %s" % (log_directory, e) self._log_directory_error = True self._siptrace_error = True self._pjsiptrace_error = True self._notifications_error = True raise else:
class LogManager(object): __metaclass__ = Singleton implements(IObserver) def __init__(self): self.name = os.path.basename(sys.argv[0]).rsplit('.py', 1)[0] self.pid = os.getpid() self.msrp_level = log.level.INFO self.siptrace_file = Null self.msrptrace_file = Null self.pjsiptrace_file = Null self.notifications_file = Null self.event_queue = Null self.notification_queue = NotificationQueue() self._siptrace_start_time = None self._siptrace_packet_count = None def start(self): settings = SIPSimpleSettings() notification_center = NotificationCenter() notification_center.add_observer(self) if settings.logs.trace_sip: self.siptrace_file = LogFile( os.path.join(ApplicationData.directory, 'logs', 'sip_trace.txt')) if settings.logs.trace_msrp: self.msrptrace_file = LogFile( os.path.join(ApplicationData.directory, 'logs', 'msrp_trace.txt')) if settings.logs.trace_pjsip: self.pjsiptrace_file = LogFile( os.path.join(ApplicationData.directory, 'logs', 'pjsip_trace.txt')) if settings.logs.trace_notifications: self.notifications_file = LogFile( os.path.join(ApplicationData.directory, 'logs', 'notifications_trace.txt')) self._siptrace_start_time = datetime.now() self._siptrace_packet_count = 0 self.event_queue = EventQueue(handler=self._process_notification, name='Blink LogManager') self.event_queue.start() while settings.logs.trace_notifications and self.notification_queue and self.notification_queue.notifications: notification = self.notification_queue.notifications.popleft() self.handle_notification(notification) self.notification_queue = None def stop(self): notification_center = NotificationCenter() notification_center.remove_observer(self) self.event_queue.stop() self.event_queue.join() self.event_queue = Null self.siptrace_file = Null self.msrptrace_file = Null self.pjsiptrace_file = Null self.notifications_file = Null def handle_notification(self, notification): self.event_queue.put(notification) def _process_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) handler = getattr(self, '_LH_%s' % notification.name, Null) handler(notification) settings = SIPSimpleSettings() if notification.name not in ('SIPEngineLog', 'SIPEngineSIPTrace' ) and settings.logs.trace_notifications: message = 'Notification name=%s sender=%s data=%s' % ( notification.name, notification.sender, pformat(notification.data)) try: self.notifications_file.write( '%s [%s %d]: %s\n' % (datetime.now(), self.name, self.pid, message)) self.notifications_file.flush() except Exception: pass def _NH_CFGSettingsObjectDidChange(self, notification): settings = SIPSimpleSettings() if notification.sender is settings: if 'logs.trace_sip' in notification.data.modified: self.siptrace_file = LogFile( os.path.join( ApplicationData.directory, 'logs', 'sip_trace.txt')) if settings.logs.trace_sip else Null if 'logs.trace_msrp' in notification.data.modified: self.msrptrace_file = LogFile( os.path.join(ApplicationData.directory, 'logs', 'msrp_trace.txt') ) if settings.logs.trace_msrp else Null if 'logs.trace_pjsip' in notification.data.modified: self.pjsiptrace_file = LogFile( os.path.join(ApplicationData.directory, 'logs', 'pjsip_trace.txt') ) if settings.logs.trace_pjsip else Null if 'logs.trace_notifications' in notification.data.modified: self.notifications_file = LogFile( os.path.join(ApplicationData.directory, 'logs', 'notifications_trace.txt') ) if settings.logs.trace_notifications else Null def _LH_SIPEngineSIPTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip: return self._siptrace_packet_count += 1 if notification.data.received: direction = "RECEIVED" else: direction = "SENDING" buf = [ "%s: Packet %d, +%s" % (direction, self._siptrace_packet_count, (notification.datetime - self._siptrace_start_time)), "%(source_ip)s:%(source_port)d -(SIP over %(transport)s)-> %(destination_ip)s:%(destination_port)d" % notification.data.__dict__, notification.data.data, '--' ] message = '\n'.join(buf) try: self.siptrace_file.write( '%s [%s %d]: %s\n' % (notification.datetime, self.name, self.pid, message)) self.siptrace_file.flush() except Exception: pass def _LH_SIPEngineLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_pjsip: return message = "(%(level)d) %(message)s" % notification.data.__dict__ try: self.pjsiptrace_file.write('[%s %d] %s\n' % (self.name, self.pid, message)) self.pjsiptrace_file.flush() except Exception: pass def _LH_DNSLookupTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip: return message = 'DNS lookup %(query_type)s %(query_name)s' % notification.data.__dict__ if notification.data.error is None: message += ' succeeded, ttl=%d: ' % notification.data.answer.ttl if notification.data.query_type == 'A': message += ", ".join(record.address for record in notification.data.answer) elif notification.data.query_type == 'SRV': message += ", ".join('%d %d %d %s' % (record.priority, record.weight, record.port, record.target) for record in notification.data.answer) elif notification.data.query_type == 'NAPTR': message += ", ".join( '%d %d "%s" "%s" "%s" %s' % (record.order, record.preference, record.flags, record.service, record.regexp, record.replacement) for record in notification.data.answer) else: import dns.resolver message_map = { dns.resolver.NXDOMAIN: 'DNS record does not exist', dns.resolver.NoAnswer: 'DNS response contains no answer', dns.resolver.NoNameservers: 'no DNS name servers could be reached', dns.resolver.Timeout: 'no DNS response received, the query has timed out' } message += ' failed: %s' % message_map.get( notification.data.error.__class__, '') try: self.siptrace_file.write( '%s [%s %d]: %s\n' % (notification.datetime, self.name, self.pid, message)) self.siptrace_file.flush() except Exception: pass def _LH_MSRPTransportTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp: return arrow = { 'incoming': '<--', 'outgoing': '-->' }[notification.data.direction] local_address = '%s:%d' % (notification.data.local_address.host, notification.data.local_address.port) remote_address = '%s:%d' % (notification.data.remote_address.host, notification.data.remote_address.port) message = '%s %s %s\n' % (local_address, arrow, remote_address) + notification.data.data prefix = '[Illegal request!] ' if notification.data.illegal else '' try: self.msrptrace_file.write( '%s [%s %d]: %s%s\n' % (notification.datetime, self.name, self.pid, prefix, message)) self.msrptrace_file.flush() except Exception: pass def _LH_MSRPLibraryLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp: return if notification.data.level < self.msrp_level: return message = '%s %s' % (notification.data.level, notification.data.message) try: self.msrptrace_file.write( '%s [%s %d]: %s\n' % (notification.datetime, self.name, self.pid, message)) self.msrptrace_file.flush() except Exception: pass
class DataConnection(object): implements(IObserver) def __init__(self, name): self.name = name self.secret = None self.private_key = DSAPrivateKey.generate() self.otr_session = OTRSession(self.private_key, transport=self) self.peer = None self.send_queue = EventQueue(handler=self._send_handler) self.send_queue.start() self.ake_done = Event() self.smp_done = Event() self.all_done = Event() self.otr_done = Event() self.smp_status = None self.same_secrets = None self.sent_message = None self.received_message = None NotificationCenter().add_observer(self, sender=self.otr_session) def _send_handler(self, message): time.sleep(0.01) self.peer.receive(message) def connect(self, peer): self.peer = peer def disconnect(self): self.send_queue.stop() self.send_queue = None def start_otr(self, secret=None): self.secret = secret self.otr_session.start() def stop_otr(self): self.otr_session.stop() def inject_otr_message(self, message): log.debug("{0.name} sending: {1!r}".format(self, message)) self.send_queue.put(message) def send(self, content, content_type='text/plain'): log.debug("{0.name} encoding: {1!r}".format(self, content)) self.sent_message = content content = self.otr_session.handle_output(content, content_type) log.debug("{0.name} sending: {1!r}".format(self, content)) self.send_queue.put(content) def receive(self, message): # log.debug("{0.name} received: {1!r}".format(self, message)) try: message = self.otr_session.handle_input(message, 'text/plain') except IgnoreMessage: return else: log.debug("{0.name} decoded: {1!r}".format(self, message)) self.received_message = message self.all_done.set() def handle_notification(self, notification): handler = getattr(self, '_NH_{0.name}'.format(notification), Null) handler(notification) def _NH_OTRSessionStateChanged(self, notification): if notification.data.new_state is OTRState.Encrypted: self.ake_done.set() if self.secret is None: self.smp_done.set() elif self.name < self.peer.name: self.otr_session.smp_verify(secret=self.secret) elif notification.data.old_state is OTRState.Encrypted: self.otr_done.set() def _NH_OTRSessionSMPVerificationDidStart(self, notification): if notification.data.originator == 'remote': if self.secret: self.otr_session.smp_answer(secret=self.secret) else: self.otr_session.smp_abort() def _NH_OTRSessionSMPVerificationDidNotStart(self, notification): self.smp_status = notification.data.reason self.smp_done.set() def _NH_OTRSessionSMPVerificationDidEnd(self, notification): self.same_secrets = notification.data.same_secrets self.smp_status = notification.data.status self.smp_done.set()
class FileLogger(object, metaclass=Singleton): # public methods # def __init__(self, msrp_level=log.level.INFO): self.msrp_level = msrp_level self._siptrace_filename = None self._siptrace_file = None self._siptrace_error = False self._siptrace_start_time = None self._siptrace_packet_count = 0 self._msrptrace_filename = None self._msrptrace_file = None self._msrptrace_error = False self._pjsiptrace_filename = None self._pjsiptrace_file = None self._pjsiptrace_error = False self._notifications_filename = None self._notifications_file = None self._notifications_error = False self._log_directory_error = False self._event_queue = None def start(self): # try to create the log directory try: self._init_log_directory() except Exception: pass # register to receive log notifications notification_center = NotificationCenter() notification_center.add_observer(self) # start the thread processing the notifications self._event_queue = EventQueue(handler=self._process_notification, name='Log handling') self._event_queue.start() def stop(self): # stop the thread processing the notifications self._event_queue.stop() self._event_queue.join() # close sip trace file if self._siptrace_file is not None: self._siptrace_file.close() self._siptrace_file = None # close msrp trace file if self._msrptrace_file is not None: self._msrptrace_file.close() self._msrptrace_file = None # close pjsip trace file if self._pjsiptrace_file is not None: self._pjsiptrace_file.close() self._pjsiptrace_file = None # close notifications trace file if self._notifications_file is not None: self._notifications_file.close() self._notifications_file = None # unregister from receiving notifications notification_center = NotificationCenter() notification_center.remove_observer(self) def handle_notification(self, notification): self._event_queue.put(notification) def _process_notification(self, notification): settings = SIPSimpleSettings() handler = getattr(self, '_NH_%s' % notification.name, None) if handler is not None: handler(notification) handler = getattr(self, '_LH_%s' % notification.name, None) if handler is not None: handler(notification) if notification.name not in ( 'SIPEngineLog', 'SIPEngineSIPTrace' ) and settings.logs.trace_notifications and settings.logs.trace_notifications_to_file: try: self._init_log_file('notifications') except Exception: pass else: message = 'Notification name=%s sender=%s data=%s' % ( notification.name, notification.sender, pformat(notification.data)) try: self._notifications_file.write( '%s: %s\n' % (datetime.datetime.now(), message)) self._notifications_file.flush() except Exception as e: print('Write message failed: %s' % message) # notification handlers # def _NH_CFGSettingsObjectDidChange(self, notification): settings = SIPSimpleSettings() if notification.sender is settings: if 'logs.directory' in notification.data.modified: # sip trace if self._siptrace_file is not None: self._siptrace_file.close() self._siptrace_file = None # pjsip trace if self._pjsiptrace_file is not None: self._pjsiptrace_file.close() self._pjsiptrace_file = None # notifications trace if self._notifications_file is not None: self._notifications_file.close() self._notifications_file = None # try to create the log directory try: self._init_log_directory() except Exception: pass # log handlers # def _LH_SIPEngineSIPTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip or not settings.logs.trace_sip_to_file: return if self._siptrace_start_time is None: self._siptrace_start_time = notification.datetime self._siptrace_packet_count += 1 if notification.data.received: direction = "RECEIVED" else: direction = "SENDING" buf = [ "%s: Packet %d, +%s" % (direction, self._siptrace_packet_count, (notification.datetime - self._siptrace_start_time)) ] buf.append( "%(source_ip)s:%(source_port)d -(SIP over %(transport)s)-> %(destination_ip)s:%(destination_port)d" % notification.data.__dict__) buf.append(notification.data.data) buf.append('--') message = '\n'.join(buf) try: self._init_log_file('siptrace') except Exception: pass else: self._siptrace_file.write( '%s [%s %d]: %s\n' % (notification.datetime, os.path.basename( sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._siptrace_file.flush() def _LH_SIPEngineLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_pjsip or not settings.logs.trace_pjsip_to_file: return message = "(%(level)d) %(message)s" % notification.data.__dict__ try: self._init_log_file('pjsiptrace') except Exception: pass else: self._pjsiptrace_file.write('[%s %d] %s\n' % (os.path.basename( sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._pjsiptrace_file.flush() def _LH_DNSLookupTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip or not settings.logs.trace_sip_to_file: return message = 'DNS lookup %(query_type)s %(query_name)s' % notification.data.__dict__ if notification.data.error is None: message += ' succeeded, ttl=%d: ' % notification.data.answer.ttl if notification.data.query_type == 'A': message += ", ".join(record.address for record in notification.data.answer) elif notification.data.query_type == 'SRV': message += ", ".join('%d %d %d %s' % (record.priority, record.weight, record.port, record.target) for record in notification.data.answer) elif notification.data.query_type == 'NAPTR': message += ", ".join( '%d %d "%s" "%s" "%s" %s' % (record.order, record.preference, record.flags, record.service, record.regexp, record.replacement) for record in notification.data.answer) else: import dns.resolver message_map = { dns.resolver.NXDOMAIN: 'DNS record does not exist', dns.resolver.NoAnswer: 'DNS response contains no answer', dns.resolver.NoNameservers: 'no DNS name servers could be reached', dns.resolver.Timeout: 'no DNS response received, the query has timed out' } message += ' failed: %s' % message_map.get( notification.data.error.__class__, '') try: self._init_log_file('siptrace') except Exception: pass else: self._siptrace_file.write( '%s [%s %d]: %s\n' % (notification.datetime, os.path.basename( sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._siptrace_file.flush() def _LH_MSRPTransportTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp or not settings.logs.trace_msrp_to_file: return arrow = { 'incoming': '<--', 'outgoing': '-->' }[notification.data.direction] local_address = notification.sender.getHost() local_address = '%s:%d' % (local_address.host, local_address.port) remote_address = notification.sender.getPeer() remote_address = '%s:%d' % (remote_address.host, remote_address.port) message = '%s %s %s\n' % (local_address, arrow, remote_address) + notification.data.data try: self._init_log_file('msrptrace') except Exception: pass else: self._msrptrace_file.write( '%s [%s %d]: %s\n' % (notification.datetime, os.path.basename( sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._msrptrace_file.flush() def _LH_MSRPLibraryLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp or not settings.logs.trace_msrp_to_file: return if notification.data.level < self.msrp_level: return message = '%s%s' % (notification.data.level.prefix, notification.data.message) try: self._init_log_file('msrptrace') except Exception: pass else: self._msrptrace_file.write( '%s [%s %d]: %s\n' % (notification.datetime, os.path.basename( sys.argv[0]).rstrip('.py'), os.getpid(), message)) self._msrptrace_file.flush() # private methods # def _init_log_directory(self): settings = SIPSimpleSettings() log_directory = settings.logs.directory.normalized try: makedirs(log_directory) except Exception as e: if not self._log_directory_error: print("failed to create logs directory '%s': %s" % (log_directory, e)) self._log_directory_error = True self._siptrace_error = True self._pjsiptrace_error = True self._notifications_error = True raise else: self._log_directory_error = False # sip trace if self._siptrace_filename is None: self._siptrace_filename = os.path.join(log_directory, 'sip_trace.txt') self._siptrace_error = False # msrp trace if self._msrptrace_filename is None: self._msrptrace_filename = os.path.join( log_directory, 'msrp_trace.txt') self._msrptrace_error = False # pjsip trace if self._pjsiptrace_filename is None: self._pjsiptrace_filename = os.path.join( log_directory, 'pjsip_trace.txt') self._pjsiptrace_error = False # notifications trace if self._notifications_filename is None: self._notifications_filename = os.path.join( log_directory, 'notifications_trace.txt') self._notifications_error = False def _init_log_file(self, type): if getattr(self, '_%s_file' % type) is None: self._init_log_directory() filename = getattr(self, '_%s_filename' % type) try: setattr(self, '_%s_file' % type, open(filename, 'a')) except Exception as e: if not getattr(self, '_%s_error' % type): print("failed to create log file '%s': %s" % (filename, e)) setattr(self, '_%s_error' % type, True) raise else: setattr(self, '_%s_error' % type, False)
class RadiusDatabase(object): """Interface with the Radius database""" class RadiusTask(object): def __init__(self, deferred, tasktype, **kwargs): self.deferred = deferred self.tasktype = tasktype self.args = kwargs def __init__(self): self.queue = EventQueue(handler=self._handle_task, name='RadiusQueue') self.queue.start() credentials = RadiusDatabaseConfig.user and ( "%s%s@" % (RadiusDatabaseConfig.user, RadiusDatabaseConfig.password and ":%s" % (RadiusDatabaseConfig.password) or '')) or '' self.conn = sqlobject.connectionForURI( "mysql://%s%s/%s" % (credentials, RadiusDatabaseConfig.host, RadiusDatabaseConfig.database)) def close(self): return threads.deferToThread(self._close) def _close(self): self.queue.stop() self.queue.join() self.conn.close() def getTerminatedCalls(self, calls): """ Retrieve those calls from the ones in progress that were already terminated by caller/called. Returns a Deferred. Callback will be called with list of call ids. """ deferred = defer.Deferred() self.queue.put( RadiusDatabase.RadiusTask(deferred, 'terminated', calls=calls)) return deferred def getTimedoutCalls(self, calls): """ Retrieve those calls from the ones in progress that did timeout and were closed by mediaproxy. Returns a Deferred. Callback will be called with list of call ids. """ deferred = defer.Deferred() self.queue.put( RadiusDatabase.RadiusTask(deferred, 'timedout', calls=calls)) return deferred def query(self, task): def _unknown_task(task): raise RadiusDatabaseError("Got unknown task to handle: %s" % task.tasktype) return getattr(self, '_RD_%s' % task.tasktype, _unknown_task)(task) def _handle_task(self, task): try: reactor.callFromThread(task.deferred.callback, self.query(task)) except Exception, e: reactor.callFromThread(task.deferred.errback, failure.Failure(e))
class TraceManager(object): __metaclass__ = Singleton implements(IObserver) def __init__(self): self.name = os.path.basename(sys.argv[0]).rsplit('.py', 1)[0] self.pid = os.getpid() self.msrp_level = log.level.INFO self.siptrace_file = Null self.msrptrace_file = Null self.pjsiptrace_file = Null self.notifications_file = Null self.event_queue = Null self.notification_queue = NotificationQueue() self._siptrace_start_time = None self._siptrace_packet_count = None def start(self): settings = SIPSimpleSettings() notification_center = NotificationCenter() notification_center.add_observer(self) self.siptrace_file = LogFile(os.path.join(ApplicationData.directory, 'logs', 'sip.log')) self.msrptrace_file = LogFile(os.path.join(ApplicationData.directory, 'logs', 'msrp.log')) self.pjsiptrace_file = LogFile(os.path.join(ApplicationData.directory, 'logs', 'pjsip.log')) self.notifications_file = LogFile(os.path.join(ApplicationData.directory, 'logs', 'notifications.log')) self._siptrace_start_time = datetime.now() self._siptrace_packet_count = 0 self.event_queue = EventQueue(handler=self._process_notification, name='Log handling') self.event_queue.start() while settings.logs.trace_notifications and self.notification_queue and self.notification_queue.notifications: notification = self.notification_queue.notifications.popleft() self.handle_notification(notification) self.notification_queue = None def stop(self): notification_center = NotificationCenter() notification_center.remove_observer(self) self.event_queue.stop() self.event_queue.join() self.event_queue = Null self.siptrace_file = Null self.msrptrace_file = Null self.pjsiptrace_file = Null self.notifications_file = Null def handle_notification(self, notification): self.event_queue.put(notification) def _process_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) handler = getattr(self, '_LH_%s' % notification.name, Null) handler(notification) settings = SIPSimpleSettings() if notification.name not in ('SIPEngineLog', 'SIPEngineSIPTrace') and settings.logs.trace_notifications: message = 'Notification name=%s sender=%s data=%s' % (notification.name, notification.sender, pformat(notification.data)) try: self.notifications_file.write('%s [%s %d]: %s\n' % (datetime.now(), self.name, self.pid, message)) except Exception: pass def _LH_SIPEngineSIPTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip: return self._siptrace_packet_count += 1 if notification.data.received: direction = "RECEIVED" else: direction = "SENDING" buf = ["%s: Packet %d, +%s" % (direction, self._siptrace_packet_count, (notification.datetime - self._siptrace_start_time))] buf.append("%(source_ip)s:%(source_port)d -(SIP over %(transport)s)-> %(destination_ip)s:%(destination_port)d" % notification.data.__dict__) buf.append(notification.data.data) buf.append('--') message = '\n'.join(buf) try: self.siptrace_file.write('%s [%s %d]: %s\n' % (notification.datetime, self.name, self.pid, message)) except Exception: pass def _LH_SIPEngineLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_pjsip: return message = "(%(level)d) %(message)s" % notification.data.__dict__ try: self.pjsiptrace_file.write('[%s %d] %s\n' % (self.name, self.pid, message)) except Exception: pass def _LH_DNSLookupTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_sip: return message = 'DNS lookup %(query_type)s %(query_name)s' % notification.data.__dict__ if notification.data.error is None: message += ' succeeded, ttl=%d: ' % notification.data.answer.ttl if notification.data.query_type == 'A': message += ", ".join(record.address for record in notification.data.answer) elif notification.data.query_type == 'SRV': message += ", ".join('%d %d %d %s' % (record.priority, record.weight, record.port, record.target) for record in notification.data.answer) elif notification.data.query_type == 'NAPTR': message += ", ".join('%d %d "%s" "%s" "%s" %s' % (record.order, record.preference, record.flags, record.service, record.regexp, record.replacement) for record in notification.data.answer) else: import dns.resolver message_map = {dns.resolver.NXDOMAIN: 'DNS record does not exist', dns.resolver.NoAnswer: 'DNS response contains no answer', dns.resolver.NoNameservers: 'no DNS name servers could be reached', dns.resolver.Timeout: 'no DNS response received, the query has timed out'} message += ' failed: %s' % message_map.get(notification.data.error.__class__, '') try: self.siptrace_file.write('%s [%s %d]: %s\n' % (notification.datetime, self.name, self.pid, message)) except Exception: pass def _LH_MSRPTransportTrace(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp: return arrow = {'incoming': '<--', 'outgoing': '-->'}[notification.data.direction] local_address = notification.data.local_address local_address = '%s:%d' % (local_address.host, local_address.port) remote_address = notification.data.remote_address remote_address = '%s:%d' % (remote_address.host, remote_address.port) message = '%s %s %s\n' % (local_address, arrow, remote_address) + notification.data.data try: self.msrptrace_file.write('%s [%s %d]: %s\n' % (notification.datetime, self.name, self.pid, message)) except Exception: pass def _LH_MSRPLibraryLog(self, notification): settings = SIPSimpleSettings() if not settings.logs.trace_msrp: return if notification.data.level < self.msrp_level: return message = '%s%s' % (notification.data.level.prefix, notification.data.message) try: self.msrptrace_file.write('%s [%s %d]: %s\n' % (notification.datetime, self.name, self.pid, message)) except Exception: pass
class SMSViewController(NSObject): chatViewController = objc.IBOutlet() splitView = objc.IBOutlet() smileyButton = objc.IBOutlet() outputContainer = objc.IBOutlet() addContactView = objc.IBOutlet() addContactLabel = objc.IBOutlet() zoom_period_label = '' showHistoryEntries = 50 remoteTypingTimer = None enableIsComposing = False handle_scrolling = True scrollingTimer = None scrolling_back = False message_count_from_history = 0 contact = None account = None target_uri = None routes = None queue = None queued_serial = 0 windowController = None last_route = None def initWithAccount_target_name_(self, account, target, display_name): self = objc.super(SMSViewController, self).init() if self: self.session_id = str(uuid.uuid1()) self.notification_center = NotificationCenter() self.account = account self.target_uri = target self.display_name = display_name self.messages = {} self.encryption = OTREncryption(self) self.message_queue = EventQueue(self._send_message) self.history = ChatHistory() self.local_uri = '%s@%s' % (account.id.username, account.id.domain) self.remote_uri = '%s@%s' % (self.target_uri.user.decode(), self.target_uri.host.decode()) self.contact = NSApp.delegate( ).contactsWindowController.getFirstContactFromAllContactsGroupMatchingURI( self.remote_uri) NSBundle.loadNibNamed_owner_("SMSView", self) self.chatViewController.setContentFile_( NSBundle.mainBundle().pathForResource_ofType_( "ChatView", "html")) self.chatViewController.setAccount_(self.account) self.chatViewController.resetRenderedMessages() self.chatViewController.inputText.unregisterDraggedTypes() self.chatViewController.inputText.setMaxLength_(MAX_MESSAGE_LENGTH) self.splitView.setText_( NSLocalizedString("%i chars left", "Label") % MAX_MESSAGE_LENGTH) self.enableIsComposing = True self.log_info('Using local account %s' % self.local_uri) self.notification_center.add_observer( self, name='ChatStreamOTREncryptionStateChanged') self.started = False return self def dealloc(self): if self.remoteTypingTimer: self.remoteTypingTimer.invalidate() self.chatViewController.close() objc.super(SMSViewController, self).dealloc() def awakeFromNib(self): # setup smiley popup smileys = SmileyManager().get_smiley_list() menu = self.smileyButton.menu() while menu.numberOfItems() > 0: menu.removeItemAtIndex_(0) bigText = NSAttributedString.alloc().initWithString_attributes_( " ", NSDictionary.dictionaryWithObject_forKey_( NSFont.systemFontOfSize_(16), NSFontAttributeName)) for text, file in smileys: image = NSImage.alloc().initWithContentsOfFile_(file) if not image: print("Can't load %s" % file) continue image.setScalesWhenResized_(True) image.setSize_(NSMakeSize(16, 16)) atext = bigText.mutableCopy() atext.appendAttributedString_( NSAttributedString.alloc().initWithString_(text)) item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_( text, "insertSmiley:", "") menu.addItem_(item) item.setTarget_(self) item.setAttributedTitle_(atext) item.setRepresentedObject_( NSAttributedString.alloc().initWithString_(text)) item.setImage_(image) @objc.python_method def revalidateToolbar(self): pass @objc.python_method def isOutputFrameVisible(self): return True @objc.python_method def log_info(self, text): BlinkLogger().log_info("[SMS with %s] %s" % (self.remote_uri, text)) @objc.IBAction def addContactPanelClicked_(self, sender): if sender.tag() == 1: NSApp.delegate().contactsWindowController.addContact( uris=[(self.target_uri, 'sip')]) self.addContactView.removeFromSuperview() frame = self.chatViewController.outputView.frame() frame.origin.y = 0 frame.size = self.outputContainer.frame().size self.chatViewController.outputView.setFrame_(frame) @objc.python_method def insertSmiley_(self, sender): smiley = sender.representedObject() self.chatViewController.appendAttributedString_(smiley) @objc.python_method def matchesTargetAccount(self, target, account): that_contact = NSApp.delegate( ).contactsWindowController.getFirstContactMatchingURI(target) this_contact = NSApp.delegate( ).contactsWindowController.getFirstContactMatchingURI(self.target_uri) return (self.target_uri == target or (this_contact and that_contact and this_contact == that_contact)) and self.account == account @objc.python_method def gotMessage(self, sender, call_id, content, content_type, is_replication_message=False, timestamp=None, window=None): is_html = content_type == 'text/html' encrypted = False try: content = self.encryption.otr_session.handle_input( content, content_type) except IgnoreMessage: return None except UnencryptedMessage: encrypted = False encryption_active = True except EncryptedMessageError as e: self.log_info('OTP encrypted message error: %s' % str(e)) return None except OTRError as e: self.log_info('OTP error: %s' % str(e)) return None else: encrypted = encryption_active = self.encryption.active content = content.decode() if isinstance(content, bytes) else content if content.startswith('?OTR:'): self.log_info('Dropped OTR message that could not be decoded') return None self.enableIsComposing = True icon = NSApp.delegate().contactsWindowController.iconPathForURI( format_identity_to_string(sender)) timestamp = timestamp or ISOTimestamp.now() hash = hashlib.sha1() hash.update((content + str(timestamp) + str(sender)).encode('utf-8')) id = hash.hexdigest() encryption = '' if encrypted: encryption = 'verified' if self.encryption.verified else 'unverified' if not is_replication_message and not window.isKeyWindow(): nc_body = html2txt(content) if is_html else content nc_title = NSLocalizedString("SMS Message Received", "Label") nc_subtitle = format_identity_to_string(sender, format='full') NSApp.delegate().gui_notify(nc_title, nc_body, nc_subtitle) self.log_info("Incoming message %s received" % call_id) self.chatViewController.showMessage(call_id, id, 'incoming', format_identity_to_string(sender), icon, content, timestamp, is_html=is_html, state="delivered", media_type='sms', encryption=encryption) self.notification_center.post_notification( 'ChatViewControllerDidDisplayMessage', sender=self, data=NotificationData( direction='incoming', history_entry=False, remote_party=format_identity_to_string(sender), local_party=format_identity_to_string(self.account) if self.account is not BonjourAccount() else 'bonjour.local', check_contact=True)) # save to history if not is_replication_message or (is_replication_message and self.local_uri == self.account.id): message = MessageInfo(id, call_id=call_id, direction='incoming', sender=sender, recipient=self.account, timestamp=timestamp, content=content, content_type=content_type, status="delivered", encryption=encryption) self.add_to_history(message) def remoteBecameIdle_(self, timer): window = timer.userInfo() if window: window.noteView_isComposing_(self, False) if self.remoteTypingTimer: self.remoteTypingTimer.invalidate() self.remoteTypingTimer = None @objc.python_method def gotIsComposing(self, window, state, refresh, last_active): self.enableIsComposing = True flag = state == "active" if flag: if refresh is None: refresh = 120 if last_active is not None and ( last_active - ISOTimestamp.now() > datetime.timedelta(seconds=refresh)): # message is old, discard it return if self.remoteTypingTimer: # if we don't get any indications in the request refresh, then we assume remote to be idle self.remoteTypingTimer.setFireDate_( NSDate.dateWithTimeIntervalSinceNow_(refresh)) else: self.remoteTypingTimer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( refresh, self, "remoteBecameIdle:", window, False) else: if self.remoteTypingTimer: self.remoteTypingTimer.invalidate() self.remoteTypingTimer = None window.noteView_isComposing_(self, flag) @objc.python_method @run_in_gui_thread def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification.sender, notification.data) @objc.python_method def inject_otr_message(self, data): if not self.encryption.active: self.log_info('Negotiating OTR encryption...') messageObject = OTRInternalMessage(data) self.sendMessage(messageObject) @objc.python_method def _NH_DNSLookupDidFail(self, lookup, data): self.notification_center.remove_observer(self, sender=lookup) message = "DNS lookup of SIP proxies for %s failed: %s" % (str( self.target_uri.host), data.error) self.setRoutesFailed(message) @objc.python_method def _NH_DNSLookupDidSucceed(self, lookup, data): self.notification_center.remove_observer(self, sender=lookup) result_text = ', '.join( ('%s:%s (%s)' % (result.address, result.port, result.transport.upper()) for result in data.result)) self.log_info("DNS lookup for %s succeeded: %s" % (self.target_uri.host.decode(), result_text)) routes = data.result if not routes: self.setRoutesFailed("No routes found to SIP Proxy") else: self.setRoutesResolved(routes) @objc.python_method def _NH_ChatStreamOTREncryptionStateChanged(self, stream, data): if data.new_state is OTRState.Encrypted: local_fingerprint = stream.encryption.key_fingerprint remote_fingerprint = stream.encryption.peer_fingerprint self.log_info("Chat encryption activated using OTR protocol") self.log_info("OTR local fingerprint %s" % local_fingerprint) self.log_info("OTR remote fingerprint %s" % remote_fingerprint) self.chatViewController.showSystemMessage("0", "Encryption enabled", ISOTimestamp.now()) self.showSystemMessage("Encryption enabled", ISOTimestamp.now()) elif data.new_state is OTRState.Finished: self.log_info("Chat encryption deactivated") self.chatViewController.showSystemMessage("0", "Encryption deactivated", ISOTimestamp.now(), is_error=True) elif data.new_state is OTRState.Plaintext: self.log_info("Chat encryption deactivated") self.chatViewController.showSystemMessage("0", "Encryption deactivated", ISOTimestamp.now(), is_error=True) @objc.python_method def _NH_SIPMessageDidSucceed(self, sender, data): try: message = self.messages.pop(str(sender)) except KeyError: pass else: call_id = data.headers['Call-ID'].body self.composeReplicationMessage(message, data.code) if message.content_type != "application/im-iscomposing+xml": self.log_info("Outgoing %s message %s delivered" % (message.content_type, call_id)) if data.code == 202: self.chatViewController.markMessage( message.id, MSG_STATE_DEFERRED) message.status = 'deferred' else: self.chatViewController.markMessage( message.id, MSG_STATE_DELIVERED) message.status = 'delivered' message.call_id = call_id self.add_to_history(message) self.notification_center.remove_observer(self, sender=sender) @objc.python_method def _NH_SIPMessageDidFail(self, sender, data): try: message = self.messages.pop(str(sender)) except KeyError: pass else: if data.code == 408: self.last_route = None call_id = message.call_id self.composeReplicationMessage(message, data.code) if message.content_type != "application/im-iscomposing+xml": self.chatViewController.markMessage(message.id, MSG_STATE_FAILED) message.status = 'failed' self.add_to_history(message) self.log_info("Outgoing message %s delivery failed: %s" % (call_id.decode(), data.reason)) self.notification_center.remove_observer(self, sender=sender) @objc.python_method def add_to_history(self, message): # writes the record to the sql database cpim_to = format_identity_to_string( message.recipient) if message.recipient else '' cpim_from = format_identity_to_string( message.sender) if message.sender else '' cpim_timestamp = str(message.timestamp) content_type = "html" if "html" in message.content_type else "text" self.history.add_message(message.id, 'sms', self.local_uri, self.remote_uri, message.direction, cpim_from, cpim_to, cpim_timestamp, message.content, content_type, "0", message.status, call_id=message.call_id) @objc.python_method 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') @objc.python_method @run_in_green_thread def sendReplicationMessage(self, response_code, content, content_type="message/cpim", timestamp=None): timestamp = timestamp or ISOTimestamp.now() # Lookup routes if self.account.sip.outbound_proxy is not None: uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={ 'transport': self.account.sip.outbound_proxy.transport }) else: uri = SIPURI(host=self.account.id.domain) route = None if self.last_route is None: lookup = DNSLookup() settings = SIPSimpleSettings() try: routes = lookup.lookup_sip_proxy( uri, settings.sip.transport_list).wait() except DNSLookupError: pass else: route = routes[0] else: route = self.last_route if route: extra_headers = [ Header("X-Offline-Storage", "no"), Header("X-Replication-Code", str(response_code)), Header("X-Replication-Timestamp", str(ISOTimestamp.now())) ] message_request = Message(FromHeader(self.account.uri, self.account.display_name), ToHeader(self.account.uri), RouteHeader(route.uri), content_type, content, credentials=self.account.credentials, extra_headers=extra_headers) message_request.send( 15 if content_type != "application/im-iscomposing+xml" else 5) @objc.python_method @run_in_gui_thread def setRoutesResolved(self, routes): self.routes = routes self.last_route = self.routes[0] self.log_info('Proceed using route: %s' % self.last_route) if not self.started: self.message_queue.start() if not self.encryption.active and not self.started: self.encryption.start() self.started = True @objc.python_method @run_in_gui_thread def setRoutesFailed(self, reason): self.message_queue.stop() self.started = False for msgObject in self.message_queue: id = msgObject.id try: message = self.messages.pop(id) except KeyError: pass else: if content_type not in ('application/im-iscomposing+xml', 'message/cpim'): self.chatViewController.markMessage( message.id, MSG_STATE_FAILED) message.status = 'failed' self.add_to_history(message) log_text = NSLocalizedString("Routing failure: %s", "Label") % msg self.chatViewController.showSystemMessage( '0', reason, ISOTimestamp.now(), True) self.log_info(log_text) @objc.python_method def _send_message(self, message): if (not self.last_route): self.log_info('No route found') return message.timestamp = ISOTimestamp.now() if not isinstance(message, OTRInternalMessage): try: content = self.encryption.otr_session.handle_output( message.content, message.content_type) except OTRError as e: if 'has ended the private conversation' in str(e): self.log_info( 'Encryption has been disabled by remote party, please resend the message again' ) self.encryption.stop() else: self.log_info('Failed to encrypt outgoing message: %s' % str(e)) return timeout = 5 if message.content_type != "application/im-iscomposing+xml": self.enableIsComposing = True timeout = 15 message_request = Message(FromHeader(self.account.uri, self.account.display_name), ToHeader(self.target_uri), RouteHeader(self.last_route.uri), message.content_type, message.content, credentials=self.account.credentials) self.notification_center.add_observer(self, sender=message_request) message_request.send(timeout) message.status = 'sent' message.call_id = message_request._request.call_id.decode() if not isinstance(message, OTRInternalMessage): if self.encryption.active: self.log_info( 'Sending encrypted %s message %s to %s' % (message.content_type, message.id, self.last_route.uri)) else: self.log_info( 'Sending %s message %s to %s' % (message.content_type, message.id, self.last_route.uri)) id = str(message_request) self.messages[id] = message @objc.python_method def lookup_destination(self, target_uri): assert isinstance(target_uri, SIPURI) lookup = DNSLookup() self.notification_center.add_observer(self, sender=lookup) settings = SIPSimpleSettings() if isinstance(self.account, Account) and self.account.sip.outbound_proxy is not None: uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={ 'transport': self.account.sip.outbound_proxy.transport }) self.log_info("Starting DNS lookup for %s through proxy %s" % (target_uri.host.decode(), uri)) elif isinstance(self.account, Account) and self.account.sip.always_use_my_proxy: uri = SIPURI(host=self.account.id.domain) self.log_info( "Starting DNS lookup for %s via proxy of account %s" % (target_uri.host.decode(), self.account.id)) else: uri = target_uri self.log_info("Starting DNS lookup for %s" % target_uri.host.decode()) lookup.lookup_sip_proxy(uri, settings.sip.transport_list) @objc.python_method def sendMessage(self, content, content_type="text/plain"): # entry point for sending messages, they will be added to self.message_queue if content_type != "application/im-iscomposing+xml": icon = NSApp.delegate().contactsWindowController.iconPathForSelf() if not isinstance(content, OTRInternalMessage): timestamp = ISOTimestamp.now() hash = hashlib.sha1() content = content.decode() if isinstance(content, bytes) else content hash.update((content + str(timestamp)).encode("utf-8")) id = hash.hexdigest() call_id = '' encryption = '' if self.encryption.active: encryption = 'verified' if self.encryption.verified else 'unverified' self.chatViewController.showMessage(call_id, id, 'outgoing', None, icon, content, timestamp, state="sent", media_type='sms', encryption=encryption) recipient = ChatIdentity(self.target_uri, self.display_name) mInfo = MessageInfo(id, sender=self.account, recipient=recipient, timestamp=timestamp, content_type=content_type, content=content, status="queued", encryption=encryption) self.messages[id] = mInfo self.message_queue.put(mInfo) else: self.message_queue.put(content) # Async DNS lookup if host is None or host.default_ip is None: self.setRoutesFailed( NSLocalizedString("No Internet connection", "Label")) return if self.last_route is None: self.lookup_destination(self.target_uri) else: self.setRoutesResolved([self.last_route]) def textView_doCommandBySelector_(self, textView, selector): if selector == "insertNewline:" and self.chatViewController.inputText == textView: content = str(textView.string()) textView.setString_("") textView.didChangeText() if content: self.sendMessage(content) self.chatViewController.resetTyping() recipient = ChatIdentity(self.target_uri, self.display_name) self.notification_center.post_notification( 'ChatViewControllerDidDisplayMessage', sender=self, data=NotificationData( direction='outgoing', history_entry=False, remote_party=format_identity_to_string(recipient), local_party=format_identity_to_string(self.account) if self.account is not BonjourAccount() else 'bonjour.local', check_contact=True)) return True return False def textDidChange_(self, notif): chars_left = MAX_MESSAGE_LENGTH - self.chatViewController.inputText.textStorage( ).length() self.splitView.setText_( NSLocalizedString("%i chars left", "Label") % chars_left) @objc.python_method def getContentView(self): return self.chatViewController.view def chatView_becameIdle_(self, chatView, last_active): if self.enableIsComposing: content = IsComposingMessage( state=State("idle"), refresh=Refresh(60), last_active=LastActive(last_active or ISOTimestamp.now()), content_type=ContentType('text')).toxml() self.sendMessage(content, IsComposingDocument.content_type) def chatView_becameActive_(self, chatView, last_active): if self.enableIsComposing: content = IsComposingMessage( state=State("active"), refresh=Refresh(60), last_active=LastActive(last_active or ISOTimestamp.now()), content_type=ContentType('text')).toxml() self.sendMessage(content, IsComposingDocument.content_type) def chatViewDidLoad_(self, chatView): self.replay_history() @objc.python_method def scroll_back_in_time(self): self.chatViewController.clear() self.chatViewController.resetRenderedMessages() self.replay_history() @objc.python_method @run_in_green_thread def replay_history(self): blink_contact = NSApp.delegate( ).contactsWindowController.getFirstContactMatchingURI(self.target_uri) if not blink_contact: remote_uris = self.remote_uri else: remote_uris = list( str(uri.uri) for uri in blink_contact.uris if '@' in uri.uri) zoom_factor = self.chatViewController.scrolling_zoom_factor if zoom_factor: period_array = { 1: datetime.datetime.now() - datetime.timedelta(days=2), 2: datetime.datetime.now() - datetime.timedelta(days=7), 3: datetime.datetime.now() - datetime.timedelta(days=31), 4: datetime.datetime.now() - datetime.timedelta(days=90), 5: datetime.datetime.now() - datetime.timedelta(days=180), 6: datetime.datetime.now() - datetime.timedelta(days=365), 7: datetime.datetime.now() - datetime.timedelta(days=3650) } after_date = period_array[zoom_factor].strftime("%Y-%m-%d") if zoom_factor == 1: self.zoom_period_label = NSLocalizedString( "Displaying messages from last day", "Label") elif zoom_factor == 2: self.zoom_period_label = NSLocalizedString( "Displaying messages from last week", "Label") elif zoom_factor == 3: self.zoom_period_label = NSLocalizedString( "Displaying messages from last month", "Label") elif zoom_factor == 4: self.zoom_period_label = NSLocalizedString( "Displaying messages from last three months", "Label") elif zoom_factor == 5: self.zoom_period_label = NSLocalizedString( "Displaying messages from last six months", "Label") elif zoom_factor == 6: self.zoom_period_label = NSLocalizedString( "Displaying messages from last year", "Label") elif zoom_factor == 7: self.zoom_period_label = NSLocalizedString( "Displaying all messages", "Label") self.chatViewController.setHandleScrolling_(False) results = self.history.get_messages( remote_uri=remote_uris, media_type=('chat', 'sms'), after_date=after_date, count=10000, search_text=self.chatViewController.search_text) else: results = self.history.get_messages( remote_uri=remote_uris, media_type=('chat', 'sms'), count=self.showHistoryEntries, search_text=self.chatViewController.search_text) messages = [row for row in reversed(results)] self.render_history_messages(messages) @objc.python_method @run_in_gui_thread def render_history_messages(self, messages): if self.chatViewController.scrolling_zoom_factor: if not self.message_count_from_history: self.message_count_from_history = len(messages) self.chatViewController.lastMessagesLabel.setStringValue_( self.zoom_period_label) else: if self.message_count_from_history == len(messages): self.chatViewController.setHandleScrolling_(False) self.chatViewController.lastMessagesLabel.setStringValue_( NSLocalizedString( "%s. There are no previous messages.", "Label") % self.zoom_period_label) self.chatViewController.setHandleScrolling_(False) else: self.chatViewController.lastMessagesLabel.setStringValue_( self.zoom_period_label) else: self.message_count_from_history = len(messages) if len(messages): self.chatViewController.lastMessagesLabel.setStringValue_( NSLocalizedString("Scroll up for going back in time", "Label")) else: self.chatViewController.setHandleScrolling_(False) self.chatViewController.lastMessagesLabel.setStringValue_( NSLocalizedString("There are no previous messages", "Label")) if len(messages): message = messages[0] delta = datetime.date.today() - message.date if not self.chatViewController.scrolling_zoom_factor: if delta.days <= 2: self.chatViewController.scrolling_zoom_factor = 1 elif delta.days <= 7: self.chatViewController.scrolling_zoom_factor = 2 elif delta.days <= 31: self.chatViewController.scrolling_zoom_factor = 3 elif delta.days <= 90: self.chatViewController.scrolling_zoom_factor = 4 elif delta.days <= 180: self.chatViewController.scrolling_zoom_factor = 5 elif delta.days <= 365: self.chatViewController.scrolling_zoom_factor = 6 elif delta.days <= 3650: self.chatViewController.scrolling_zoom_factor = 7 call_id = None seen_sms = {} last_media_type = 'sms' last_chat_timestamp = None for message in messages: if message.status == 'failed': continue if message.sip_callid != '' and message.media_type == 'sms': try: seen = seen_sms[message.sip_callid] except KeyError: seen_sms[message.sip_callid] = True else: continue if message.direction == 'outgoing': icon = NSApp.delegate( ).contactsWindowController.iconPathForSelf() else: sender_uri = sipuri_components_from_string( message.cpim_from)[0] icon = NSApp.delegate( ).contactsWindowController.iconPathForURI(sender_uri) timestamp = ISOTimestamp(message.cpim_timestamp) is_html = False if message.content_type == 'text' else True #if call_id is not None and call_id != message.sip_callid and message.media_type == 'chat': # self.chatViewController.showSystemMessage(message.sip_callid, 'Chat session established', timestamp, False) #if message.media_type == 'sms' and last_media_type == 'chat': # self.chatViewController.showSystemMessage(message.sip_callid, 'Short messages', timestamp, False) self.chatViewController.showMessage(message.sip_callid, message.id, message.direction, message.cpim_from, icon, message.body, timestamp, recipient=message.cpim_to, state=message.status, is_html=is_html, history_entry=True, media_type=message.media_type, encryption=message.encryption) call_id = message.sip_callid last_media_type = 'chat' if message.media_type == 'chat' else 'sms' if message.media_type == 'chat': last_chat_timestamp = timestamp self.chatViewController.loadingProgressIndicator.stopAnimation_(None) self.chatViewController.loadingTextIndicator.setStringValue_("") def webviewFinishedLoading_(self, notification): self.document = self.outputView.mainFrameDocument() self.finishedLoading = True for script in self.messageQueue: self.outputView.stringByEvaluatingJavaScriptFromString_(script) self.messageQueue = [] if hasattr(self.delegate, "chatViewDidLoad_"): self.delegate.chatViewDidLoad_(self) def webView_decidePolicyForNavigationAction_request_frame_decisionListener_( self, webView, info, request, frame, listener): # intercept link clicks so that they are opened in Safari theURL = info[WebActionOriginalURLKey] if theURL.scheme() == "file": listener.use() else: listener.ignore() NSWorkspace.sharedWorkspace().openURL_(theURL)
class RadiusDatabase(object): """Interface with the Radius database""" class RadiusTask(object): def __init__(self, deferred, tasktype, **kwargs): self.deferred = deferred self.tasktype = tasktype self.args = kwargs def __init__(self): self.queue = EventQueue(handler=self._handle_task, name='RadiusQueue') self.queue.start() credentials = RadiusDatabaseConfig.user and ( "%s%s@" % (RadiusDatabaseConfig.user, RadiusDatabaseConfig.password and ":%s" % (RadiusDatabaseConfig.password) or '') ) or '' self.conn = sqlobject.connectionForURI("mysql://%s%s/%s" % (credentials, RadiusDatabaseConfig.host, RadiusDatabaseConfig.database)) def close(self): return threads.deferToThread(self._close) def _close(self): self.queue.stop() self.queue.join() self.conn.close() def getTerminatedCalls(self, calls): """ Retrieve those calls from the ones in progress that were already terminated by caller/called. Returns a Deferred. Callback will be called with list of call ids. """ deferred = defer.Deferred() self.queue.put(RadiusDatabase.RadiusTask(deferred, 'terminated', calls=calls)) return deferred def getTimedoutCalls(self, calls): """ Retrieve those calls from the ones in progress that did timeout and were closed by mediaproxy. Returns a Deferred. Callback will be called with list of call ids. """ deferred = defer.Deferred() self.queue.put(RadiusDatabase.RadiusTask(deferred, 'timedout', calls=calls)) return deferred def query(self, task): def _unknown_task(task): raise RadiusDatabaseError("Got unknown task to handle: %s" % task.tasktype) return getattr(self, '_RD_%s' % task.tasktype, _unknown_task)(task) def _handle_task(self, task): try: reactor.callFromThread(task.deferred.callback, self.query(task)) except Exception as e: reactor.callFromThread(task.deferred.errback, failure.Failure(e)) def _RD_terminated(self, task): calls = dict([(call.callid, call) for call in list(task.args['calls'].values()) if call.inprogress]) if not calls: return {} ids = "(%s)" % ','.join(["'" + key + "'" for key in list(calls.keys())]) query = """SELECT %(session_id_field)s AS callid, %(duration_field)s AS duration, %(from_tag_field)s AS fromtag, %(to_tag_field)s AS totag FROM %(table)s WHERE %(session_id_field)s IN %(ids)s AND (%(stop_info_field)s IS NOT NULL OR %(stop_time_field)s IS NOT NULL)""" % {'session_id_field': RadiusDatabaseConfig.sessionIdField, 'duration_field': RadiusDatabaseConfig.durationField, 'from_tag_field': RadiusDatabaseConfig.fromTagField, 'to_tag_field': RadiusDatabaseConfig.toTagField, 'stop_info_field': RadiusDatabaseConfig.stopInfoField, 'stop_time_field': RadiusDatabaseConfig.stopTimeField, 'table': RadiusDatabaseConfig.table.normalized, 'ids': ids} try: rows = self.conn.queryAll(query) except Exception as e: log.error("Query failed: %s" % query) raise RadiusDatabaseError("Exception while querying for terminated calls %s." % e) def find(row, calls): try: call = calls[row[0]] except KeyError: return False return call.fromtag==row[2] and call.totag==row[3] return dict([(row[0], {'callid': row[0], 'duration': row[1], 'fromtag': row[2], 'totag': row[3]}) for row in rows if find(row, calls)]) def _RD_timedout(self, task): calls = dict([(call.callid, call) for call in list(task.args['calls'].values()) if call.inprogress]) if not calls: return {} ids = "(%s)" % ','.join(["'" + key + "'" for key in list(calls.keys())]) query = '''SELECT %(session_id_field)s AS callid, %(duration_field)s AS duration, %(from_tag_field)s AS fromtag, %(to_tag_field)s AS totag FROM %(table)s WHERE %(session_id_field)s IN %(ids)s AND %(media_info_field)s = 'timeout' AND %(stop_info_field)s IS NULL''' % {'session_id_field': RadiusDatabaseConfig.sessionIdField, 'duration_field': RadiusDatabaseConfig.durationField, 'from_tag_field': RadiusDatabaseConfig.fromTagField, 'to_tag_field': RadiusDatabaseConfig.toTagField, 'media_info_field': RadiusDatabaseConfig.mediaInfoField, 'stop_info_field': RadiusDatabaseConfig.stopInfoField, 'table': RadiusDatabaseConfig.table.normalized, 'ids': ids} try: rows = self.conn.queryAll(query) except Exception as e: log.error("Query failed: %s" % query) raise RadiusDatabaseError("Exception while querying for timedout calls %s." % e) def find(row, calls): try: call = calls[row[0]] except KeyError: return False return call.fromtag==row[2] and call.totag==row[3] return dict([(row[0], {'callid': row[0], 'duration': row[1], 'fromtag': row[2], 'totag': row[3]}) for row in rows if find(row, calls)])