if 'armv7' in platform.platform() and settings.audio.echo_canceller.enabled:
self.output.put("Disable echo canceller on ARM architecture\n")
settings.audio.echo_canceller.enabled = False
settings.save()
for account in account_manager.iter_accounts():
if isinstance(account, Account):
account.sip.register = False
account.presence.enabled = False
account.xcap.enabled = False
account.message_summary.enabled = False
if self.options.account is None:
self.account = account_manager.default_account
else:
possible_accounts = [account for account in account_manager.iter_accounts() if self.options.account in account.id and account.enabled]
if len(possible_accounts) > 1:
self.output.put('More than one account exists which matches %s: %s\n' % (self.options.account, ', '.join(sorted(account.id for account in possible_accounts))))
self.output.stop()
self.stop()
self.end_cancel_thread()
return
elif len(possible_accounts) == 0:
self.output.put('No enabled account that matches %s was found. Available and enabled accounts: %s\n' % (self.options.account, ', '.join(sorted(account.id for account in account_manager.get_accounts() if account.enabled))))
self.output.put("Initiating SIP %s session from '%s' to '%s' via %s...\n" % ('video' if self.enable_video else 'audio', local_identity, remote_identity, session.route))
on_hold_streams = [stream for stream in chain(*(session.streams for session in self.started_sessions)) if stream is not notification.sender and stream.on_hold]
if not on_hold_streams and self.hold_tone.is_active:
description = 'This script can sit idle waiting for an incoming audio session, or initiate an outgoing audio session to a SIP address. The program will close the session and quit when Ctrl+D is pressed.'
parser.add_option('-a', '--account', type='string', dest='account', help='The account name to use for any outgoing traffic. If not supplied, the default account will be used.', metavar='NAME')
parser.add_option('-c', '--config-directory', type='string', dest='config_directory', help='The configuration directory to use. This overrides the default location.')
parser.add_option('-s', '--trace-sip', action='store_true', dest='trace_sip', default=False, help='Dump the raw contents of incoming and outgoing SIP messages.')
parser.add_option('-n', '--trace-notifications', action='store_true', dest='trace_notifications', default=False, help='Print all notifications (disabled by default).')
parser.add_option('-S', '--disable-sound', action='store_true', dest='disable_sound', default=False, help='Disables initializing the sound card.')
parser.set_default('auto_answer_interval', None)
parser.add_option('--auto-answer', action='callback', callback=parse_handle_call_option, callback_args=('auto_answer_interval',), help='Interval after which to answer an incoming session (disabled by default). If the option is specified but the interval is not, it defaults to 0 (accept the session as soon as it starts ringing).', metavar='[INTERVAL]')
parser.add_option('-u', '--auto-answer-uris', type='string', dest='auto_answer_uris', default="", help='Optional list of SIP URIs for which auto-answer is allowed')
parser.add_option('-i', '--external-id', type='string', dest='external_id', help='id used for call control from external application')
parser.add_option('-v', '--spool-dir', type='string', dest='spool_dir', default=None, help='Spool dir for call control from external applications, default is /var/spool/sipclients/sessions')
parser.add_option('-V', '--enable-video', action='store_true', dest='enable_video', default=False, help='Enable video if camera is available')
parser.set_default('auto_hangup_interval', None)
parser.add_option('--auto-hangup', action='callback', callback=parse_handle_call_option, callback_args=('auto_hangup_interval',), help='Interval after which to hang up an established session (disabled by default). If the option is specified but the interval is not, it defaults to 0 (hangup the session as soon as it connects).', metavar='[INTERVAL]')
parser.add_option('-b', '--batch', action='store_true', dest='batch_mode', default=False, help='Run the program in batch mode: reading input from the console is disabled and the option --auto-answer is implied. This is particularly useful when running this script in a non-interactive environment.')
parser.add_option('-d', '--daemonize', action='store_true', dest='daemonize', default=False, help='Enable running this program as a deamon.')
options, args = parser.parse_args()
target = args[0] if args and not options.auto_answer_uris else None
application = SIPAudioApplication()
application.start(target, options)
signal.signal(signal.SIGINT, signal.SIG_DFL)
application.output.join()
sleep(0.1)
sys.exit(0 if application.success else 1)
diff --git a/sip-session3 b/sip-session3
index e9e90c5..d7f6b0c 100755
--- a/sip-session3
+++ b/sip-session3
@@ -1,3076 +1,3082 @@
#!/usr/bin/env python3
import os
import re
import signal
import sys
import urllib.request, urllib.parse, urllib.error
import random
import requests
from collections import defaultdict
from datetime import datetime, timedelta
from dateutil.tz import tzlocal
from itertools import chain
from lxml import html
from optparse import OptionParser
from otr import OTRState, SMPStatus
from threading import Event, Thread
from time import sleep
from application import log
from application.notification import IObserver, NotificationCenter
from application.python.queue import EventQueue
from application.python import Null
from eventlib import api
from twisted.internet import reactor
from zope.interface import implementer
from otr import OTRTransport, OTRState, SMPStatus
from otr.exceptions import IgnoreMessage, UnencryptedMessage, EncryptedMessageError, OTRError
from sipsimple.core import Engine, FromHeader, Message, RouteHeader, SIPCoreError, SIPURI, ToHeader
from sipsimple.account import Account, AccountManager, BonjourAccount
from sipsimple.application import SIPApplication
from sipsimple.audio import WavePlayer
from sipsimple.configuration import ConfigurationError
from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.core import Route
from sipsimple.lookup import DNSLookup
from sipsimple.payloads.iscomposing import IsComposingMessage
from sipsimple.session import IllegalStateError, Session
from sipsimple.streams import MediaStreamRegistry
from sipsimple.streams.msrp.filetransfer import FileSelector
from sipsimple.streams.msrp.chat import CPIMPayload, SimplePayload, CPIMParserError, ChatIdentity, OTREncryption
from sipsimple.storage import FileStorage
from sipsimple.threading.green import run_in_green_thread
from sipsimple.util import ISOTimestamp
from sipclient.configuration import config_directory
from sipclient.configuration.account import AccountExtension
from sipclient.configuration.datatypes import ResourcePath
from sipclient.configuration.settings import SIPSimpleSettingsExtension
show_notice('%s Encrypted message %s to %s failed on %s: %s (%d)' % (datetime.now().replace(microsecond=0), message.id, self.remote_uri, server or client, notification.data.reason.decode(), notification.data.code))
else:
show_notice('%s Message %s to %s failed on %s: %s (%d)' % (datetime.now().replace(microsecond=0), message.id, self.remote_uri, server or client, notification.data.reason.decode() if isinstance(notification.data.reason, bytes) else notification.data.reason, notification.data.code))
self.question = Question("Incoming file transfer for %s from '%s', do you want to accept? (a)ccept/(r)eject" % (os.path.basename(self.filename), identity), 'ari', bold=True)
SIPApplication.start(self, FileStorage(options.config_directory or config_directory))
except ConfigurationError as e:
show_notice("Failed to load sipclient's configuration: %s\n" % str(e), bold=False)
show_notice("If an old configuration file is in place, delete it or move it and recreate the configuration using the sip_settings script.", bold=False)
possible_accounts = [account for account in account_manager.iter_accounts() if self.options.account in account.id and account.enabled]
if len(possible_accounts) > 1:
show_notice('More than one account exists which matches %s: %s' % (self.options.account, ', '.join(sorted(account.id for account in possible_accounts))), bold=False)
self.stop()
return
elif len(possible_accounts) == 0:
show_notice('No enabled account which matches %s was found. Available and enabled accounts: %s' % (self.options.account, ', '.join(sorted(account.id for account in account_manager.get_accounts() if account.enabled))), bold=False)
lines = ['%s Registered contact "%s" of %s at %s:%d;transport=%s for %d seconds' % (now, contact_header.uri, account.id, registrar.address, registrar.port, registrar.transport, expires)]
if len(contact_header_list) > 1:
lines.append('%s Other registered contacts of %s:' % (now, account.id))
lines.extend('%s %s of %s for %s seconds' % (now, str(other_contact_header.uri), account.id, other_contact_header.expires) for other_contact_header in contact_header_list if other_contact_header.uri != notification.data.contact_header.uri)
if account.contact.public_gruu is not None:
lines.append('%s Public GRUU: %s' % (now, account.contact.public_gruu))
show_notice("%s %s wrote a garbled message, probably encryption session must be reset using /otr command" % (datetime.now().replace(microsecond=0), identity))
if data.question == self.smp_verification_question:
try:
audio_stream = next(stream for stream in session.streams if stream.type=='audio' and stream.encryption.type=='ZRTP' and stream.encryption.active and stream.encryption.zrtp.verified)
except StopIteration:
stream.encryption.smp_abort()
else:
show_notice("OTR verification requested by remote and replied automatically using ZRTP SAS", bold=False)
audio_stream = next(stream for stream in session.streams if stream.type=='audio' and stream.encryption.type=='ZRTP' and stream.encryption.active and stream.encryption.zrtp.verified)
show_notice('No camera present, video is disabled')
self.enable_video = False
def _CH_help(self):
self._print_help()
def _CH_quit(self):
self.stop()
def _CH_eof(self):
ui = UI()
if self.active_session is not None:
if self.active_session in self.sessions_with_proposals:
ui.status = 'Cancelling proposal...'
self.active_session.cancel_proposal()
else:
ui.status = 'Ending SIP session...'
self.active_session.end()
elif self.outgoing_session is not None:
ui.status = 'Cancelling SIP session...'
self.outgoing_session.end()
else:
self.stop()
def _CH_hangup(self):
if self.active_session is not None:
show_notice('Ending SIP session...')
self.active_session.end()
elif self.outgoing_session is not None:
show_notice('Cancelling SIP session...')
self.outgoing_session.end()
else:
try:
message_session = next((session for session in self.message_sessions if session.target == self.message_session_to and self.account == session.account))
except StopIteration:
pass
else:
message_session.end()
try:
self.message_sessions.remove(message_session)
except KeyError:
show_notice('Message session not found')
pass
@run_in_green_thread
def _CH_dtmf(self, tones):
if self.active_session is not None:
audio_stream = next((stream for stream in self.active_session.streams if stream.type == 'audio'), None)
show_notice('Accounts available: %s' % ", ".join(account.id for account in account_manager.iter_accounts() if account != self.account and account.enabled))
possible_accounts = [account for account in account_manager.iter_accounts() if new_account in account.id and account.enabled]
if len(possible_accounts) > 1:
show_notice('More than one account exists which matches %s: %s' % (new_account, ', '.join(sorted(account.id for account in possible_accounts))), bold=False)
elif len(possible_accounts) == 0:
show_notice('No enabled account which matches %s was found. Available and enabled accounts: %s' % (self.options.account, ', '.join(sorted(possible_accounts))), bold=False)
parser.add_option('-a', '--account', type='string', dest='account', help='The account name to use for any outgoing traffic. If not supplied, the default account will be used.', metavar='NAME')
parser.add_option('-c', '--config-directory', type='string', dest='config_directory', help='The configuration directory to use. This overrides the default location.')
parser.add_option('-s', '--trace-sip', action='store_true', dest='trace_sip', default=False, help='Dump the raw contents of incoming and outgoing SIP messages.')
parser.add_option('-m', '--trace-msrp', action='store_true', dest='trace_msrp', default=False, help='Dump msrp logging information and the raw contents of incoming and outgoing MSRP messages.')
parser.add_option('-n', '--trace-notifications', action='store_true', dest='trace_notifications', default=False, help='Print all notifications (disabled by default).')
parser.add_option('-S', '--disable-sound', action='store_true', dest='disable_sound', default=False, help='Disables initializing the sound card.')
parser.set_default('auto_answer_interval', None)
parser.add_option('--auto-answer', action='callback', callback=parse_handle_call_option, callback_args=('auto_answer_interval',), help='Interval after which to answer an incoming session (disabled by default). If the option is specified but the interval is not, it defaults to 0 (accept the session as soon as it starts ringing).', metavar='[INTERVAL]')
parser.set_default('auto_hangup_interval', None)
parser.add_option('--auto-hangup', action='callback', callback=parse_handle_call_option, callback_args=('auto_hangup_interval',), help='Interval after which to hang up an established session (disabled by default). If the option is specified but the interval is not, it defaults to 0 (hangup the session as soon as it connects).', metavar='[INTERVAL]')