diff --git a/pypjua/clients/__init__.py b/pypjua/clients/__init__.py index f7690689..79a0b01d 100644 --- a/pypjua/clients/__init__.py +++ b/pypjua/clients/__init__.py @@ -1,18 +1,67 @@ import re +from pypjua import SIPURI + _pstn_num_sub_char = "[-() ]" _re_pstn_num_sub = re.compile(_pstn_num_sub_char) _re_pstn_num = re.compile("^\+?([0-9]|%s)+$" % _pstn_num_sub_char) def format_cmdline_uri(uri, default_domain): if "@" not in uri: if _re_pstn_num.match(uri): username = _re_pstn_num_sub.sub("", uri) else: username = uri uri = "%s@%s" % (username, default_domain) if not uri.startswith("sip:") and not uri.startswith("sips:"): uri = "sip:%s" % uri return uri -__all__ = ["format_cmdline_uri"] \ No newline at end of file +_re_host_port = re.compile("^((?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?P[a-zA-Z0-9\-\.]+))(:(?P\d+))?$") +class IPAddressOrHostname(tuple): + def __new__(typ, value): + match = _re_host_port.search(value) + if match is None: + raise ValueError("invalid hostname/port: %r" % value) + if match.group("ip") is None: + host = match.group("host") + is_ip = False + else: + host = match.group("ip") + is_ip = True + if match.group("port") is None: + port = None + else: + port = int(match.group("port")) + if port > 65535: + raise ValueError("port is out of range: %d" % port) + return host, port, is_ip + + +class OutboundProxy(SIPURI): + def __new__(type, value): + if value.lower() == "none": + return None + parameters = {} + host = None + port = None + splitval = value.split(":") + if len(splitval) > 3: + raise ValueError("Could not parse outbound proxy") + elif len(splitval) == 3: + parameters["transport"], host, port = splitval + elif len(splitval) == 2: + if splitval[1].isdigit(): + host, port = splitval + else: + parameters["transport"], host = splitval + else: + host = splitval[0] + if port is not None: + port = int(port) + if port < 0 or port > 65535: + raise ValueError("port is out of range: %d" % port) + return SIPURI(host=host, port=port, parameters=parameters) + + +__all__ = ["format_cmdline_uri", "IPAddressOrHostname", "OutboundProxy"] \ No newline at end of file diff --git a/pypjua/clients/config.py b/pypjua/clients/config.py index 32b484b7..17d1c217 100644 --- a/pypjua/clients/config.py +++ b/pypjua/clients/config.py @@ -1,225 +1,225 @@ import sys import os import glob from optparse import OptionValueError, OptionParser from ConfigParser import NoSectionError from application.configuration import ConfigSection, ConfigFile, datatypes from application.process import process from msrplib.connect import MSRPRelaySettings from pypjua import SIPURI, Route -from pypjua.clients.lookup import lookup_srv -from pypjua.clients.lookup import IPAddressOrHostname +from pypjua.clients.dns_lookup import lookup_srv +from pypjua.clients import IPAddressOrHostname from pypjua.clients.cpim import SIPAddress process._system_config_directory = os.path.expanduser("~/.sipclient") config_ini = os.path.join(process._system_config_directory, 'config.ini') # disable twisted debug messages (enabled by python-application) from twisted.python import log if log.defaultObserver is not None: log.defaultObserver.stop() log.defaultObserver = log.DefaultObserver() log.defaultObserver.start() class GeneralConfig(ConfigSection): _datatypes = {"listen_udp": datatypes.NetworkAddress, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean, "auto_accept_file_transfers": datatypes.Boolean} listen_udp = datatypes.NetworkAddress("any") trace_pjsip = False trace_sip = False file_transfers_directory = os.path.join(process._system_config_directory, 'file_transfers') auto_accept_file_transfers = False class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": IPAddressOrHostname, "msrp_relay": str} sip_address = None password = None display_name = None outbound_proxy = None msrp_relay = "auto" class AudioConfig(ConfigSection): _datatypes = {"disable_sound": datatypes.Boolean} disable_sound = False def get_download_path(name): path = os.path.join(GeneralConfig.file_transfers_directory, name) if os.path.exists(path): all = [int(x[len(path)+1:]) for x in glob.glob(path + '.*')] if not all: return path + '.1' else: return path + '.' + str(max(all)+1) return path def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = IPAddressOrHostname(value) except ValueError, e: raise OptionValueError(e.message) def _parse_msrp_relay(value): if value in ['auto', 'srv', 'none']: return value try: return IPAddressOrHostname(value) except ValueError, e: raise OptionValueError(e.message) def parse_msrp_relay(option, opt_str, value, parser): parser.values.msrp_relay = _parse_msrp_relay(value) def parse_options(usage, description): configuration = ConfigFile(config_ini) configuration.read_settings("Audio", AudioConfig) configuration.read_settings("General", GeneralConfig) parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", help=('The account name from which to read account settings. ' 'Corresponds to section Account_NAME in the configuration file.')) parser.add_option('--show-config', action='store_true', help = ('Show settings from the configuration and exit; ' 'use together with --account-name option')) parser.add_option("--sip-address", type="string", help="SIP login account") parser.add_option("-p", "--password", type="string", help="Password to use to authenticate the local account.") parser.add_option("-n", "--display-name", type="string", help="Display name to use for the local account.") help = ('Use the outbound SIP proxy; ' 'if "auto", discover the SIP proxy through SRV and A ' 'records lookup on the domain part of user SIP URI.') parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help=help, metavar="IP[:PORT]") parser.add_option("-m", "--trace-msrp", action="store_true", default=False, help="Dump the raw contents of incoming and outgoing MSRP messages.") parser.add_option("-s", "--trace-sip", action="store_true", default=GeneralConfig.trace_sip, help="Dump the raw contents of incoming and outgoing SIP messages.") parser.add_option("-j", "--trace-pjsip", action="store_true", default=GeneralConfig.trace_pjsip, help="Print PJSIP logging output.") help=('Use the MSRP relay; ' 'if "srv", do SRV lookup on domain part of the target SIP URI, ' 'use user\'s domain if SRV lookup was not successful; ' 'if "none", the direct connection is performed; ' 'if "auto", use "srv" for incoming connections and "none" for outgoing; ' 'default is "auto".') parser.add_option("-r", "--msrp-relay", type='string', action="callback", callback=parse_msrp_relay, help=help, metavar='IP[:PORT]') parser.add_option("-S", "--disable-sound", action="store_true", default=AudioConfig.disable_sound, help="Do not initialize the soundcard (by default the soundcard is enabled).") #parser.add_option("-y", '--auto-accept-all', action='store_true', default=False, help=SUPPRESS_HELP) parser.add_option('--auto-accept-files', action='store_true', help='Accept all incoming file transfers without bothering user.') parser.add_option('--no-register', action='store_false', dest='register', default=True, help='Bypass registration') parser.add_option('--msrp-tcp', action='store_false', dest='msrp_tls', default=True) options, args = parser.parse_args() if options.account_name is None: account_section = 'Account' else: account_section = 'Account_%s' % options.account_name options.use_bonjour = options.account_name == 'bonjour' if options.account_name not in [None, 'bonjour'] and account_section not in configuration.parser.sections(): msg = "Section [%s] was not found in the configuration file %s" % (account_section, config_ini) raise RuntimeError(msg) configuration.read_settings(account_section, AccountConfig) default_options = dict(outbound_proxy=AccountConfig.outbound_proxy, msrp_relay=_parse_msrp_relay(AccountConfig.msrp_relay), sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, local_ip=GeneralConfig.listen_udp[0], local_port=GeneralConfig.listen_udp[1], auto_accept_files=GeneralConfig.auto_accept_file_transfers) if options.show_config: print 'Configuration file: %s' % config_ini for config_section in [account_section, 'General', 'Audio']: try: options = configuration.parser.options(config_section) except NoSectionError: pass else: print '[%s]' % config_section for option in options: if option in default_options.keys(): print '%s=%s' % (option, default_options[option]) accounts = [] for section in configuration.parser.sections(): if section.startswith('Account') and account_section != section: if section == 'Account': accounts.append('(default)') else: accounts.append(section[8:]) accounts.sort() print "Other accounts: %s" % ', '.join(accounts) sys.exit() options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) if not options.use_bonjour: if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") options.sip_address = SIPAddress.parse(options.sip_address) options.uri = SIPURI(user=options.sip_address.username, host=options.sip_address.domain, display=options.display_name) if args: options.target_address = SIPAddress.parse(args[0], default_domain = options.sip_address.domain) options.target_uri = SIPURI(user=options.target_address.username, host=options.target_address.domain) del args[0] else: options.target_address = None options.target_uri = None options.args = args if options.msrp_relay == 'auto': if options.target_uri is None: options.msrp_relay = 'srv' else: options.msrp_relay = 'none' if options.msrp_relay == 'srv': options.relay = MSRPRelaySettings(domain=options.sip_address.domain, username=options.sip_address.username, password=options.password) elif options.msrp_relay == 'none': options.relay = None else: host, port, is_ip = options.msrp_relay if is_ip or port is not None: options.relay = MSRPRelaySettings(domain=options.sip_address.domain, host=host, port=port, username=options.sip_address.username, password=options.password) else: options.relay = MSRPRelaySettings(domain=options.sip_address.domain, username=options.sip_address.username, password=options.password) if options.use_bonjour: options.route = None else: if options.outbound_proxy is None: proxy_host, proxy_port, proxy_is_ip = options.sip_address.domain, None, False else: proxy_host, proxy_port, proxy_is_ip = options.outbound_proxy options.route = Route(*lookup_srv(proxy_host, proxy_port, proxy_is_ip, 5060)) return options diff --git a/pypjua/clients/lookup.py b/pypjua/clients/dns_lookup.py similarity index 82% rename from pypjua/clients/lookup.py rename to pypjua/clients/dns_lookup.py index e0bd6882..92b8c37b 100644 --- a/pypjua/clients/lookup.py +++ b/pypjua/clients/dns_lookup.py @@ -1,217 +1,170 @@ import re import random import dns.resolver -from pypjua import Route, SIPURI - -_re_host_port = re.compile("^((?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?P[a-zA-Z0-9\-\.]+))(:(?P\d+))?$") -class IPAddressOrHostname(tuple): - def __new__(typ, value): - match = _re_host_port.search(value) - if match is None: - raise ValueError("invalid hostname/port: %r" % value) - if match.group("ip") is None: - host = match.group("host") - is_ip = False - else: - host = match.group("ip") - is_ip = True - if match.group("port") is None: - port = None - else: - port = int(match.group("port")) - if port > 65535: - raise ValueError("port is out of range: %d" % port) - return host, port, is_ip - - -class OutboundProxy(SIPURI): - def __new__(type, value): - if value.lower() == "none": - return None - parameters = {} - host = None - port = None - splitval = value.split(":") - if len(splitval) > 3: - raise ValueError("Could not parse outbound proxy") - elif len(splitval) == 3: - parameters["transport"], host, port = splitval - elif len(splitval) == 2: - if splitval[1].isdigit(): - host, port = splitval - else: - parameters["transport"], host = splitval - else: - host = splitval[0] - if port is not None: - port = int(port) - if port < 0 or port > 65535: - raise ValueError("port is out of range: %d" % port) - return SIPURI(host=host, port=port, parameters=parameters) - +from pypjua import Route def lookup_srv(host, port, is_ip, default_port, service='_sip._udp'): if is_ip: return host, port or default_port else: if port is None: try: srv_answers = dns.resolver.query("%s.%s" % (service, host), "SRV") a_host = str(srv_answers[0].target).rstrip(".") port = srv_answers[0].port print 'Resolved DNS SRV record "%s.%s" --> %s:%d' % (service, host, a_host, port) except: print "Domain %s has no DNS SRV record, attempting DNS A record lookup" % host a_host = host port = default_port else: a_host = host try: a_answers = dns.resolver.query(a_host, "A") print 'Resolved DNS A record "%s" --> %s' % (a_host, ", ".join(a.address for a in a_answers)) except: raise RuntimeError('Could not resolve "%s"' % a_host) return random.choice(a_answers).address, port _service_srv_record_map = {"stun": ("_stun._udp", 3478, False), "msrprelay": ("_msrps._tcp", 2855, True)} def lookup_service_for_sip_uri(uri, service): try: service_prefix, service_port, service_fallback = _service_srv_record_map[service] except KeyError: raise RuntimeError("Unknown service: %s" % service) a_candidates = [] servers = [] try: srv_answers = dns.resolver.query("%s.%s" % (service_prefix, uri.host), "SRV") except: if service_fallback: a_candidates.append((uri.host, service_port)) else: srv_answers = sorted(srv_answers, key=lambda x: x.priority) srv_answers.sort(key=lambda x: x.weight, reverse=True) a_candidates = [(srv_answer.target, srv_answer.port) for srv_answer in srv_answers] for a_host, a_port in a_candidates: try: a_answers = dns.resolver.query(a_host, "A") except: pass else: for a_answer in a_answers: servers.append((a_answer.address, a_port)) return servers _naptr_service_transport_map = {"sips+d2t": "tls", "sip+d2t": "tcp", "sip+d2u": "udp"} _transport_srv_service_map = {"udp": "_sip._udp", "tcp": "_sip._tcp", "tls": "_sips._tls"} def lookup_routes_for_sip_uri(uri, supported_transports): """This function preforms RFC 3263 compliant lookup of transport/ip/port combinations for a particular SIP URI. As arguments it takes a SIPURI object and a list of supported transports, in order of preference of the application. It returns a list of Route objects that can be used in order of preference.""" if len(supported_transports) == 0: raise RuntimeError("No transports are supported") for supported_transport in supported_transports: if supported_transport not in _transport_srv_service_map: raise RuntimeError("Unsupported transport: %s" % supported_transport) supported_transports = [transport.lower() for transport in supported_transports] # If the URI is a SIPS URI, only a TLS transport can be returned. if uri.secure: if "tls" not in supported_transports: raise RuntimeError("Requested lookup for SIPS URI, but TLS transport is not supported") supported_transports = ["tls"] transport = None port = None ip = None srv_candidates = [] a_candidates = [] routes = [] # Check if the transport was already set as a parameter on the SIP URI. if uri.parameters and "transport" in uri.parameters: transport = uri.parameters["transport"] # Check if the port was already set, we can skip NAPTR/SRV lookup later if it is. if uri.port: port = uri.port # Check if the host part of the URI is a IP address, we can skip NAPTR/SRV lookup later if it is. if re.match("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", uri.host): ip = uri.host # Otherwise we can try NAPTR/SRV lookup. if port is None and ip is None: # Only do the NAPTR part if the transport was not specified as a URI parameter. if transport is None: try: naptr_answers = dns.resolver.query(uri.host, "NAPTR") except: # If NAPTR lookup fails for some reason, try SRV lookup for all supported transports. # Only the transports of those lookups that succeed are supported by the server. srv_candidates = [(transport, "%s.%s" % (_transport_srv_service_map[transport], uri.host)) for transport in supported_transports] else: # If NAPTR lookup succeeds, order those entries that are applicable for SIP based on server prefernce. naptr_answers = [answer for answer in naptr_answers if answer.flags.lower() == "s" and answer.service.lower() in _naptr_service_transport_map and _naptr_service_transport_map[answer.service.lower()] in supported_transports] naptr_answers.sort(key=lambda x: x.preference) naptr_answers.sort(key=lambda x: x.order) if len(naptr_answers) == 0: raise RuntimeError("Could find a suitable transport in NAPTR record of domain") srv_candidates = [(_naptr_service_transport_map[answer.service.lower()], answer.replacement) for answer in naptr_answers] else: # Directly try the SRV record of the requested transport. srv_candidates = [(transport, "%s.%s" % (_transport_srv_service_map[transport], uri.host))] for srv_transport, srv_qname in srv_candidates: try: srv_answers = dns.resolver.query(srv_qname, "SRV") except: # If SRV lookup fails, try A record directly for a transport that was requested, # otherwise UDP for a SIP URI, TLS for a SIPS URI. if transport is None: if (uri.secure and srv_transport == "tls") or (not uri.secure and srv_transport == "udp"): a_candidates.append((srv_transport, uri.host, 5061 if srv_transport == "tls" else 5060)) else: if transport == srv_transport: a_candidates.append((transport, uri.host, 5061 if transport == "tls" else 5060)) else: # If SRV lookup succeeds, sort the resulting hosts based on server preference. srv_answers = sorted(srv_answers, key=lambda x: x.priority) srv_answers.sort(key=lambda x: x.weight, reverse=True) for answer in srv_answers: a_candidates.append((srv_transport, answer.target, answer.port)) else: # If NAPT/SRV was skipped, fill in defaults for the other variables. if transport is None: if uri.secure: transport = "tls" else: if "udp" not in supported_transports: raise RuntimeError("UDP transport is not suported") transport = "udp" if port is None: port = 5061 if uri.secure else 5060 # For an IP address, return this immedeately, otherwise do a lookup for the requested hostname. if ip is None: a_candidates.append((transport, uri.host, port)) else: return [Route(ip, port=port, transport=transport)] # Keep results in a dictionary so we don't do double A record lookups a_cache = {} for a_transport, a_qname, a_port in a_candidates: try: if a_qname in a_cache: a_answers = a_cache[a_qname] else: a_answers = dns.resolver.query(a_qname, "A") a_cache[a_qname] = a_answers except: # If lookup fails then don't return this value pass else: for answer in a_answers: routes.append(Route(answer.address, port=a_port, transport=a_transport)) return routes -__all__ = ["IPAddressOrHostname", "OutboundProxy", "lookup_srv", "lookup_service_for_sip_uri", "lookup_routes_for_sip_uri"] +__all__ = ["lookup_srv", "lookup_service_for_sip_uri", "lookup_routes_for_sip_uri"] diff --git a/scripts/sip_audio_session.py b/scripts/sip_audio_session.py index 875c616e..5ff00f0d 100644 --- a/scripts/sip_audio_session.py +++ b/scripts/sip_audio_session.py @@ -1,590 +1,590 @@ #!/usr/bin/env python import sys import traceback import string import socket import os import atexit import select import termios import signal import datetime import random from thread import start_new_thread, allocate_lock from threading import Thread, Timer from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep, time from application.process import process from application.configuration import * from pypjua import * from pypjua.clients import enrollment from pypjua.clients.log import Logger -from pypjua.clients.lookup import * +from pypjua.clients.dns_lookup import * from pypjua.clients.clientconfig import get_path -from pypjua.clients import format_cmdline_uri +from pypjua.clients import * class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False history_directory = '~/.sipclient/history' log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy, "use_ice": datatypes.Boolean, "use_stun_for_ice": datatypes.Boolean, "stun_servers": datatypes.StringList} sip_address = None password = None display_name = None outbound_proxy = None use_ice = False use_stun_for_ice = False stun_servers = [] class SRTPOptions(dict): def __new__(typ, value): value_lower = value.lower() if value_lower == "disabled": return dict(use_srtp=False, srtp_forced=False) elif value_lower == "optional": return dict(use_srtp=True, srtp_forced=False) elif value_lower == "mandatory": return dict(use_srtp=True, srtp_forced=True) else: raise ValueError('Unknown SRTP option: "%s"' % value) class AudioConfig(ConfigSection): _datatypes = {"sample_rate": int, "echo_cancellation_tail_length": int,"codec_list": datatypes.StringList, "disable_sound": datatypes.Boolean, "encryption": SRTPOptions} sample_rate = 32 echo_cancellation_tail_length = 50 codec_list = ["speex", "g711", "ilbc", "gsm", "g722"] disable_sound = False encryption = dict(use_srtp=True, srtp_forced=False) process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("Audio", AudioConfig) configuration.read_settings("General", GeneralConfig) queue = Queue() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() logger = None return_code = 1 def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(10) finally: termios_restore() else: return os.read(fd, 10) def event_handler(event_name, **kwargs): global start_time, packet_count, queue, do_trace_pjsip, logger if event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) class RingingThread(Thread): def __init__(self, inbound): self.inbound = inbound self.stopping = False Thread.__init__(self) self.setDaemon(True) self.start() def stop(self): self.stopping = True def run(self): global queue while True: if self.stopping: return if self.inbound: queue.put(("play_wav", "ring_inbound.wav")) else: queue.put(("play_wav", "ring_outbound.wav")) sleep(5) def print_control_keys(): print "Available control keys:" print " h: hang-up the active session" print " r: toggle audio recording" print " <> : adjust echo cancellation" print " SPACE: hold/on-hold" print " Ctrl-d: quit the program" def read_queue(e, username, domain, password, display_name, route, target_uri, trace_sip, ec_tail_length, sample_rate, codecs, do_trace_pjsip, use_bonjour, stun_servers, transport, auto_hangup): global user_quit, lock, queue, return_code lock.acquire() inv = None audio = None ringer = None printed = False rec_file = None want_quit = target_uri is not None other_user_agent = None on_hold = False session_start_time = None try: if not use_bonjour: sip_uri = SIPURI(user=username, host=domain, display=display_name) credentials = Credentials(sip_uri, password) if len(stun_servers) > 0: e.detect_nat_type(*stun_servers[0]) if target_uri is None: if use_bonjour: print "Using bonjour" print "Listening on local interface %s:%d" % (e.local_ip, e.local_udp_port) print_control_keys() print 'Waiting for incoming SIP session requests...' else: reg = Registration(credentials, route=route) print 'Registering "%s" at %s:%d' % (credentials.uri, route.host, route.port) reg.register() else: inv = Invitation(credentials, target_uri, route=route) print "Call from %s to %s through proxy %s:%s:%d" % (inv.caller_uri, inv.callee_uri, route.transport, route.host, route.port) audio = AudioTransport(transport) inv.set_offered_local_sdp(SDPSession(audio.transport.local_rtp_address, connection=SDPConnection(audio.transport.local_rtp_address), media=[audio.get_local_media(True)])) inv.send_invite() print_control_keys() while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if event_name == "Registration_state": if args["state"] == "registered": if not printed: print "REGISTER was successful" print "Contact: %s (expires in %d seconds)" % (args["contact_uri"], args["expires"]) print_control_keys() print "Waiting for incoming session..." printed = True elif args["state"] == "unregistered": if "code" in args and args["code"] / 100 != 2: print "Unregistered: %(code)d %(reason)s" % args elif inv is None: return_code = 0 user_quit = False command = "quit" elif event_name == "Invitation_sdp": if args["obj"] is inv: if args["succeeded"]: if not audio.is_started: if ringer is not None: ringer.stop() ringer = None session_start_time = time() audio.start(args["local_sdp"], args["remote_sdp"], 0) e.connect_audio_transport(audio) print 'Media negotiation done, using "%s" codec at %dHz' % (audio.codec, audio.sample_rate) print "Audio RTP endpoints %s:%d <-> %s:%d" % (audio.transport.local_rtp_address, audio.transport.local_rtp_port, audio.transport.remote_rtp_address_sdp, audio.transport.remote_rtp_port_sdp) return_code = 0 if auto_hangup is not None: Timer(auto_hangup, lambda: queue.put(("eof", None))).start() if audio.transport.srtp_active: print "RTP audio stream is encrypted" else: audio.update_direction(inv.get_active_local_sdp().media[0].get_direction()) else: print "SDP negotation failed: %s" % args["error"] elif event_name == "Invitation_state": if args["state"] == args["prev_state"] and args["state"] != "EARLY": print "SAME STATE" print args data, args = None, None continue if args["state"] == "EARLY": if "code" in args and args["code"] == 180: if ringer is None: print "Ringing..." ringer = RingingThread(target_uri is None) elif args["state"] == "CONNECTING": if "headers" in args and "User-Agent" in args["headers"]: other_user_agent = args["headers"].get("User-Agent") elif args["state"] == "INCOMING": print "Incoming session..." if inv is None: remote_sdp = args["obj"].get_offered_remote_sdp() if remote_sdp is not None and len(remote_sdp.media) == 1 and remote_sdp.media[0].media == "audio": inv = args["obj"] other_user_agent = args["headers"].get("User-Agent") if ringer is None: ringer = RingingThread(True) inv.respond_to_invite_provisionally() print 'Incoming audio session from "%s", do you want to accept? (y/n)' % str(inv.caller_uri) else: print "Not an audio call, rejecting." args["obj"].disconnect() else: print "Rejecting." args["obj"].disconnect() elif args["prev_state"] == "CONNECTING" and args["state"] == "CONFIRMED": if other_user_agent is not None: print 'Remote SIP User Agent is "%s"' % other_user_agent elif args["state"] == "REINVITED": # Just assume the call got placed on hold for now... prev_remote_direction = inv.get_active_remote_sdp().media[0].get_direction() remote_direction = inv.get_offered_remote_sdp().media[0].get_direction() if "recv" in prev_remote_direction and "recv" not in remote_direction: print "Remote party is placing us on hold" elif "recv" not in prev_remote_direction and "recv" in remote_direction: print "Remote party is taking us out of hold" local_sdp = inv.get_active_local_sdp() local_sdp.version += 1 local_sdp.media[0] = audio.get_local_media(False) inv.set_offered_local_sdp(local_sdp) inv.respond_to_reinvite() elif args["state"] == "DISCONNECTED": if args["obj"] is inv: if rec_file is not None: rec_file.stop() print 'Stopped recording audio to "%s"' % rec_file.file_name rec_file = None if ringer is not None: ringer.stop() ringer = None if args["prev_state"] == "DISCONNECTING": disc_msg = "Session ended by local user" elif args["prev_state"] in ["CALLING", "EARLY"]: if "headers" in args: if "Server" in args["headers"]: print 'Remote SIP server is "%s"' % args["headers"]["Server"] elif "User-Agent" in args["headers"]: print 'Remote SIP User Agent is "%s"' % args["headers"]["User-Agent"] disc_msg = "Session could not be established" else: disc_msg = 'Session ended by "%s"' % inv.remote_uri if "code" in args and args["code"] / 100 != 2: print "%s: %d %s" % (disc_msg, args["code"], args["reason"]) if args["code"] == 408 and args["prev_state"] == "CONNECTING": print "Session failed because ACK was never received" if args["code"] in [301, 302]: print 'Received redirect request to "%s"' % args["headers"]["Contact"] return_code = 0 else: print disc_msg if session_start_time is not None: duration = time() - session_start_time print "Session duration was %d minutes, %d seconds" % (duration / 60, duration % 60) session_start_time = None if want_quit: command = "unregister" else: if audio is not None: audio.stop() audio = None inv = None elif event_name == "detect_nat_type": if args["succeeded"]: print "Detected NAT type: %s" % args["nat_type"] if command == "user_input": if inv is not None: data = data[0] if data.lower() == "h": command = "end" want_quit = target_uri is not None elif data in "0123456789*#ABCD" and audio is not None and audio.is_active: audio.send_dtmf(data) elif data.lower() == "r": if rec_file is None: src = '%s@%s' % (inv.caller_uri.user, inv.caller_uri.host) dst = '%s@%s' % (inv.callee_uri.user, inv.callee_uri.host) dir = os.path.join(os.path.expanduser(GeneralConfig.history_directory), '%s@%s' % (username, domain)) try: file_name = os.path.join(dir, '%s-%s-%s.wav' % (datetime.datetime.now().strftime("%Y%m%d-%H%M%S"), src, dst)) rec_file = e.rec_wav_file(file_name) print 'Recording audio to "%s"' % rec_file.file_name except OSError, e: print "Error while trying to record file: %s" else: rec_file.stop() print 'Stopped recording audio to "%s"' % rec_file.file_name rec_file = None elif data == " ": if inv.state == "CONFIRMED": if not on_hold: on_hold = True print "Placing call on hold" if "send" in audio.direction: new_direction = "sendonly" else: new_direction = "inactive" e.disconnect_audio_transport(audio) else: on_hold = False print "Taking call out of hold" if "send" in audio.direction: new_direction = "sendrecv" else: new_direction = "recvonly" e.connect_audio_transport(audio) local_sdp = inv.get_active_local_sdp() local_sdp.version += 1 local_sdp.media[0] = audio.get_local_media(True, new_direction) inv.set_offered_local_sdp(local_sdp) inv.send_reinvite() elif inv.state in ["INCOMING", "EARLY"] and target_uri is None: if data.lower() == "n": command = "end" want_quit = False elif data.lower() == "y": remote_sdp = inv.get_offered_remote_sdp() audio = AudioTransport(transport, remote_sdp, 0) inv.set_offered_local_sdp(SDPSession(audio.transport.local_rtp_address, connection=SDPConnection(audio.transport.local_rtp_address), media=[audio.get_local_media(False)], start_time=remote_sdp.start_time, stop_time=remote_sdp.stop_time)) inv.accept_invite() if data in ",<": if ec_tail_length > 0: ec_tail_length = max(0, ec_tail_length - 10) e.auto_set_sound_devices(ec_tail_length) print "Set echo cancellation tail length to %d ms" % ec_tail_length elif data in ".>": if ec_tail_length < 500: ec_tail_length = min(500, ec_tail_length + 10) e.auto_set_sound_devices(ec_tail_length) print "Set echo cancellation tail length to %d ms" % ec_tail_length if command == "play_wav": e.play_wav_file(get_path(data)) if command == "eof": command = "end" want_quit = True if command == "end": try: inv.disconnect() except: command = "unregister" if command == "unregister": if target_uri is None and not use_bonjour: reg.unregister() else: user_quit = False command = "quit" if command == "quit": break data, args = None, None except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_invite(**kwargs): global user_quit, lock, queue, do_trace_pjsip, logger ctrl_d_pressed = False do_trace_pjsip = kwargs["do_trace_pjsip"] outbound_proxy = kwargs.pop("outbound_proxy") kwargs["stun_servers"] = lookup_service_for_sip_uri(SIPURI(host=kwargs["domain"]), "stun") if kwargs["use_bonjour"]: kwargs["route"] = None else: if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs['trace_sip']) if kwargs['trace_sip']: print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=True, codecs=kwargs["codecs"], ec_tail_length=kwargs["ec_tail_length"], sample_rate=kwargs["sample_rate"], local_ip=kwargs["local_ip"], local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port")) e.start(not kwargs.pop("disable_sound")) if kwargs["target_uri"] is not None: kwargs["target_uri"] = e.parse_sip_uri(kwargs["target_uri"]) transport_kwargs = AudioConfig.encryption.copy() transport_kwargs["use_ice"] = AccountConfig.use_ice wait_for_stun = False if AccountConfig.use_stun_for_ice: if len(AccountConfig.stun_servers) > 0: wait_for_stun = True try: random_stun = random.choice(AccountConfig.stun_servers) transport_kwargs["ice_stun_address"], ice_stun_port = random_stun.split(":") except: transport_kwargs["ice_stun_address"] = random_stun transport_kwargs["ice_stun_port"] = 3478 else: transport_kwargs["ice_stun_port"] = int(ice_stun_port) else: if len(kwargs["stun_servers"]) > 0: wait_for_stun = True transport_kwargs["ice_stun_address"], transport_kwargs["ice_stun_port"] = random.choice(kwargs["stun_servers"]) kwargs["transport"] = RTPTransport(kwargs.pop("local_ip"), **transport_kwargs) if wait_for_stun: print "Waiting for STUN response for ICE from %s:%d" % (transport_kwargs["ice_stun_address"], transport_kwargs["ice_stun_port"]) while True: command, data = queue.get() if command == "print": print data elif command == "pypjua_event": event_name, args = data if event_name == "RTPTransport_init": if args["succeeded"]: break else: raise RuntimeError("STUN request failed") start_new_thread(read_queue, (e,), kwargs) atexit.register(termios_restore) try: while True: char = getchar() if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_auto_hangup(option, opt_str, value, parser): try: value = parser.rargs[0] except IndexError: value = 0 else: if value == "" or value[0] == '-': value = 0 else: try: value = int(value) except ValueError: value = 0 else: del parser.rargs[0] parser.values.auto_hangup = value def split_codec_list(option, opt_str, value, parser): parser.values.codecs = value.split(",") def parse_options(): retval = {} description = "This script can sit idle waiting for an incoming audio call, or perform an outgoing audio call to the target SIP account. The program will close the session and quit when Ctrl+D is pressed." usage = "%prog [options] [target-user@target-domain.com]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-n", "--display-name", type="string", dest="display_name", help="Display name to use for the local account. This overrides the setting from the config file.") parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-t", "--ec-tail-length", type="int", dest="ec_tail_length", help='Echo cancellation tail length in ms, setting this to 0 will disable echo cancellation. Default is 50 ms.') parser.add_option("-r", "--sample-rate", type="int", dest="sample_rate", help='Sample rate in kHz, should be one of 8, 16 or 32kHz. Default is 32kHz.') parser.add_option("-c", "--codecs", type="string", action="callback", callback=split_codec_list, help='Comma separated list of codecs to be used. Default is "speex,g711,ilbc,gsm,g722".') parser.add_option("-S", "--disable-sound", action="store_true", dest="disable_sound", help="Do not initialize the soundcard (by default the soundcard is enabled).") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") parser.add_option("--auto-hangup", action="callback", callback=parse_auto_hangup, help="Interval after which to hangup an on-going call (applies only to outgoing calls, disabled by default). If the option is specified but the interval is not, it defaults to 0 (hangup the call as soon as it connects).", metavar="[INTERVAL]") options, args = parser.parse_args() retval["use_bonjour"] = options.account_name == "bonjour" if not retval["use_bonjour"]: if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) default_options = dict(outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, trace_sip=GeneralConfig.trace_sip, ec_tail_length=AudioConfig.echo_cancellation_tail_length, sample_rate=AudioConfig.sample_rate, codecs=AudioConfig.codec_list, disable_sound=AudioConfig.disable_sound, do_trace_pjsip=GeneralConfig.trace_pjsip, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports, auto_hangup=None) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not retval["use_bonjour"]: if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: if retval["use_bonjour"]: retval["username"], retval["domain"] = None, None else: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] if args: retval["target_uri"] = format_cmdline_uri(args[0], retval["domain"]) else: retval["target_uri"] = None accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: if not retval["use_bonjour"]: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_invite(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) sys.exit(return_code) diff --git a/scripts/sip_auto_publish_presence.py b/scripts/sip_auto_publish_presence.py index e8b64cab..8dcaec27 100644 --- a/scripts/sip_auto_publish_presence.py +++ b/scripts/sip_auto_publish_presence.py @@ -1,446 +1,447 @@ #!/usr/bin/env python import sys import traceback import string import random import socket import os import atexit import select import termios import signal import re import subprocess import datetime from thread import start_new_thread, allocate_lock from threading import Thread, Event from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep from collections import deque from application.process import process from application.configuration import * from pypjua import * from pypjua.clients import enrollment from pypjua.clients.log import Logger from pypjua.applications import BuilderError from pypjua.applications.pidf import * from pypjua.applications.presdm import * from pypjua.applications.rpid import * from pypjua.clients.clientconfig import get_path -from pypjua.clients.lookup import * +from pypjua.clients.dns_lookup import * +from pypjua.clients import * class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy, "use_presence_agent": datatypes.Boolean} sip_address = None password = None display_name = None outbound_proxy = None use_presence_agent = True process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("General", GeneralConfig) queue = Queue() getstr_event = Event() packet_count = 0 start_time = None old = None user_quit = True want_quit = False lock = allocate_lock() pub = None sip_uri = None string = None logger = None return_code = 1 pidf = None user_agent = None def publish_pidf(): try: pub.publish("application", "pidf+xml", pidf.toxml()) except BuilderError, e: print "PIDF as currently defined is invalid: %s" % str(e) except: traceback.print_exc() def random_note(): try: fortune = subprocess.Popen('fortune', stdout=subprocess.PIPE) fortune.wait() return ' '.join(s for s in re.split(r'\n|\t', fortune.stdout.read()) if s != '') except: return 'Fortune is not installed' def auto_publish(interval): # initialize top level elements tuple = Tuple(''.join(chr(random.randint(97, 122)) for i in xrange(8)), status=Status(basic=Basic('open'))) tuple.contact = Contact("sip:%s@%s" % (sip_uri.user, sip_uri.host)) tuple.contact.priority = "0" tuple.relationship = Relationship('self') tuple.timestamp = Timestamp() pidf.append(tuple) # add email service email_tuple = Tuple(''.join(chr(random.randint(97, 122)) for i in xrange(8)), status=Status(basic=Basic('open'))) email_tuple.contact = Contact("mailto:%s@%s" % (sip_uri.user, sip_uri.host)) email_tuple.contact.priority = "0.5" email_tuple.relationship = Relationship('self') email_tuple.timestamp = Timestamp() pidf.append(email_tuple) person = Person(''.join(chr(random.randint(97, 122)) for i in xrange(8))) person.privacy = Privacy() person.time_offset = TimeOffset() person.timestamp = DMTimestamp() pidf.append(person) device = Device(''.join(chr(random.randint(97, 122)) for i in xrange(8))) device.notes.append(DMNote('Powered by %s' % user_agent, lang='en')) device.timestamp = DMTimestamp() device.user_input = UserInput() pidf.append(device) while True: # 50% chance that basic status will change if random.randint(0, 1) == 1: if tuple.status.basic == 'open': tuple.status.basic = Basic('closed') else: tuple.status.basic = Basic('open') tuple.timestamp = Timestamp() # set sphere (9-18 at work (except on weekends), else at home) now = datetime.datetime.now() if (now.hour >= 9 and now.hour < 18) and now.isoweekday() not in (6, 7): person.sphere = Sphere(Work()) person.sphere.since = datetime.datetime(now.year, now.month, now.day, 9, 0) person.sphere.until = datetime.datetime(now.year, now.month, now.day, 18, 0) else: person.sphere = Sphere(Home()) # set privacy person.privacy.audio = random.choice((True, False)) person.privacy.text = random.choice((True, False)) person.privacy.video = random.choice((True, False)) # set status icon person.status_icon = StatusIcon("http://sipsimpleclient.com/chrome/site/StatusIcons/%s.png" % random.choice(('available', 'busy'))) # change person note if len(person.notes) > 0: del person.notes['en'] person.notes.append(DMNote(random_note(), lang='en')) # change person activity if person.activities is None: person.activities = Activities() else: person.activities.clear() values = list(Activities._xml_value_maps.get(value, value) for value in Activities._xml_values if value != 'unknown') for i in xrange(random.randrange(3)): person.activities.add(random.choice(values)) # change person mood if person.mood is None: person.mood = Mood() else: person.mood.clear() values = list(Mood._xml_value_maps.get(value, value) for value in Mood._xml_values if value != 'unknown') for i in xrange(random.randrange(3)): person.mood.add(random.choice(values)) # change place is if person.place_is is None: person.place_is = PlaceIs() # 50% chance that place is will change if random.randint(0, 1) == 1: person.place_is.audio = Audio(random.choice(list(Audio._xml_values))) if random.randint(0, 1) == 1: person.place_is.video = Video(random.choice(list(Video._xml_values))) if random.randint(0, 1) == 1: person.place_is.text = Text(random.choice(list(Text._xml_values))) person.timestamp = DMTimestamp() # set user-input if device.user_input.value == 'idle': # 50 % chance to change to active: if random.randint(0, 1) == 1: device.user_input.value = 'active' device.user_input.last_input = None else: # 50 % chance to change to idle: if random.randint(0, 1) == 1: device.user_input.value = 'idle' device.user_input.last_input = now - datetime.timedelta(seconds=30) # publish new pidf publish_pidf() sleep(interval) def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) atexit.register(termios_restore) def getstr(prompt='selection'): global string, getstr_event string = '' sys.stdout.write("%s> " % prompt) sys.stdout.flush() getstr_event.wait() getstr_event.clear() sys.stdout.write("\n") ret = string string = None return ret def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(4192) except select.error, e: if e[0] != 4: raise return '' finally: termios_restore() else: return os.read(fd, 4192) def event_handler(event_name, **kwargs): global packet_count, start_time, queue, do_trace_pjsip, logger, want_quit, pub, return_code if event_name == "Publication_state": if kwargs["state"] == "unpublished": queue.put(("print", "Unpublished: %(code)d %(reason)s" % kwargs)) if want_quit or kwargs['code'] in (401, 403, 407): if kwargs['code'] / 100 == 2: return_code = 0 queue.put(("quit", None)) else: pub = Publication(pub.credentials, pub.event, route=pub.route, expires=pub.expires) publish_pidf() elif kwargs["state"] == "published": queue.put(("print", "PUBLISH was successful")) pass elif event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) def read_queue(e, username, domain, password, display_name, route, expires, do_trace_pjsip, interval): global user_quit, lock, queue, pub, sip_uri, pidf, user_agent lock.acquire() try: sip_uri = SIPURI(user=username, host=domain, display=display_name) pub = Publication(Credentials(sip_uri, password), "presence", route=route, expires=expires) # initialize PIDF pidf = PIDF(entity='%s@%s' % (username, domain)) user_agent = e.user_agent #initialize auto publisher start_new_thread(auto_publish, (interval,)) while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if command == "user_input": key = data if command == "eof": command = "end" user_quit = True if command == "end": try: pub.unpublish() except: pass if command == "quit": user_quit = False break except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def sig_handler(signum, frame): global queue, want_quit want_quit = True queue.put(("end", None)) def do_publish(**kwargs): global user_quit, want_quit, lock, queue, do_trace_pjsip, string, getstr_event, old, logger ctrl_d_pressed = False do_trace_pjsip = kwargs["do_trace_pjsip"] outbound_proxy = kwargs.pop("outbound_proxy") if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs['trace_sip']) if kwargs.pop('trace_sip'): print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=True, local_ip=kwargs.pop("local_ip"), local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port")) e.start(False) start_new_thread(read_queue, (e,), kwargs) atexit.register(termios_restore) # unsubscribe on signal.signal(signal.SIGUSR1, sig_handler) try: while True: for char in getchar(): if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True want_quit = True break else: if string is not None: if char == "\x7f": if len(string) > 0: char = "\x08" sys.stdout.write("\x08 \x08") sys.stdout.flush() string = string[:-1] else: if old is not None: sys.stdout.write(char) sys.stdout.flush() if char == "\x0A": getstr_event.set() break else: string += char else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" want_quit = True queue.put(("quit", True)) return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_options(): retval = {} description = "This script will publish rich presence state of the specified SIP account to a SIP Presence Agent, the presence information can be changed using a menu-driven interface." usage = "%prog [options]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-e", "--expires", type="int", dest="expires", help='"Expires" value to set in PUBLISH. Default is 300 seconds.') parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-i", "--interval", type="int", dest="interval", help='Time between state changes. Default is 60 seconds.') parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) if not AccountConfig.use_presence_agent: raise RuntimeError("Presence is not enabled for this account. Please set use_presence_agent=True in the config file") default_options = dict(expires=300, outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, trace_sip=GeneralConfig.trace_sip, do_trace_pjsip=GeneralConfig.trace_pjsip, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports, interval=60) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_publish(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) sys.exit(return_code) diff --git a/scripts/sip_message.py b/scripts/sip_message.py index 376055f5..7329ac1d 100644 --- a/scripts/sip_message.py +++ b/scripts/sip_message.py @@ -1,240 +1,240 @@ #!/usr/bin/env python import sys import traceback import os import signal import random from thread import start_new_thread, allocate_lock from Queue import Queue from optparse import OptionParser, OptionValueError from application.configuration import * from application.process import process from pypjua import * from pypjua.clients import enrollment -from pypjua.clients.lookup import * -from pypjua.clients import format_cmdline_uri +from pypjua.clients.dns_lookup import * +from pypjua.clients import * from pypjua.clients.log import Logger class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy} sip_address = None password = None display_name = None outbound_proxy = None process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("General", GeneralConfig) queue = Queue() packet_count = 0 start_time = None user_quit = True lock = allocate_lock() logger = None def event_handler(event_name, **kwargs): global start_time, packet_count, queue, do_trace_pjsip, logger if event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) def read_queue(e, username, domain, password, display_name, route, target_uri, message): global user_quit, lock, queue lock.acquire() printed = False sent = False msg_buf = [] try: credentials = Credentials(SIPURI(user=username, host=domain, display=display_name), password) if target_uri is None: reg = Registration(credentials, route=route) print 'Registering "%s" at %s:%s:%d' % (credentials.uri, route.transport, route.host, route.port) reg.register() else: if message is None: print "Press Ctrl+D on an empty line to end input and send the MESSAGE request." else: msg_buf.append(message) queue.put(("eof", None)) while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if event_name == "Registration_state": if args["state"] == "registered": if not printed: print "REGISTER was successful" print "Contact: %s (expires in %d seconds)" % (args["contact_uri"], args["expires"]) if len(args["contact_uri_list"]) > 1: print "Other registered contacts:\n%s" % "\n".join(["%s (expires in %d seconds)" % contact_tup for contact_tup in args["contact_uri_list"] if contact_tup[0] != args["contact_uri"]]) print "Press Ctrl+D to stop the program." printed = True elif args["state"] == "unregistered": if "code" in args and args["code"] / 100 != 2: print "Unregistered: %(code)d %(reason)s" % args user_quit = False command = "quit" elif event_name == "Invitation_state": if args["state"] == "INCOMING": args["obj"].end() elif event_name == "message": print 'Received MESSAGE from "%(from_uri)s", Content-Type: %(content_type)s/%(content_subtype)s' % args print args["body"] elif event_name == "message_response": if args["code"] / 100 != 2: print "Could not deliver MESSAGE: %(code)d %(reason)s" % args else: print "MESSAGE was accepted by remote party." user_quit = False command = "quit" if command == "user_input": if not sent: msg_buf.append(data) if command == "eof": if target_uri is None: reg.unregister() elif not sent: sent = True print 'Sending MESSAGE from "%s" to "%s" using proxy %s:%s:%d' % (credentials.uri, target_uri, route.transport, route.host, route.port) send_message(credentials, target_uri, "text", "plain", "\n".join(msg_buf), route) if command == "quit": break except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_message(**kwargs): global user_quit, lock, queue, do_trace_pjsip, logger do_trace_pjsip = kwargs.pop("do_trace_pjsip") outbound_proxy = kwargs.pop("outbound_proxy") ctrl_d_pressed = False if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs['trace_sip']) if kwargs['trace_sip']: print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=kwargs.pop("trace_sip"), local_ip=kwargs.pop("local_ip"), local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port")) e.start(False) if kwargs["target_uri"] is not None: kwargs["target_uri"] = e.parse_sip_uri(kwargs["target_uri"]) start_new_thread(read_queue, (e,), kwargs) try: while True: try: msg = raw_input() queue.put(("user_input", msg)) except EOFError: if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_options(): retval = {} description = "This will either sit idle waiting for an incoming MESSAGE request, or send a MESSAGE request to the specified SIP target. In outgoing mode the program will read the contents of the messages to be sent from standard input, Ctrl+D signalling EOF as usual. In listen mode the program will quit when Ctrl+D is pressed." usage = "%prog [options] [target-user@target-domain.com]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file.") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP login account") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-n", "--display-name", type="string", dest="display_name", help="Display name to use for the local account. This overrides the setting from the config file.") parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-m", "--message", type="string", dest="message", help="Contents of the message to send. This disables reading the message from standard input.") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) default_options = dict(outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, trace_sip=GeneralConfig.trace_sip, message=None, do_trace_pjsip=GeneralConfig.trace_pjsip, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] if args: retval["target_uri"] = format_cmdline_uri(args[0], retval["domain"]) else: retval["target_uri"] = None accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_message(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) diff --git a/scripts/sip_publish_presence.py b/scripts/sip_publish_presence.py index 94f72cf0..705a5662 100644 --- a/scripts/sip_publish_presence.py +++ b/scripts/sip_publish_presence.py @@ -1,703 +1,704 @@ #!/usr/bin/env python import sys import traceback import string import random import socket import os import atexit import select import termios import signal from thread import start_new_thread, allocate_lock from threading import Thread, Event from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep from collections import deque from application.process import process from application.configuration import * from pypjua import * from pypjua.clients import enrollment from pypjua.clients.log import Logger from pypjua.applications import BuilderError from pypjua.applications.pidf import * from pypjua.applications.presdm import * from pypjua.applications.rpid import * from pypjua.clients.clientconfig import get_path -from pypjua.clients.lookup import * +from pypjua.clients.dns_lookup import * +from pypjua.clients import * class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy, "use_presence_agent": datatypes.Boolean} sip_address = None password = None display_name = None outbound_proxy = None use_presence_agent = True process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("General", GeneralConfig) queue = Queue() getstr_event = Event() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() pub = None sip_uri = None string = None logger = None return_code = 1 pidf = None person = None tuple = None menu_stack = deque() def publish_pidf(): try: pub.publish("application", "pidf+xml", pidf.toxml()) except BuilderError, e: print "PIDF as currently defined is invalid: %s" % str(e) except: traceback.print_exc() def exit_program(): print 'Exiting...' queue.put(("eof", None)) class Menu(object): def __init__(self, interface): interface['x'] = {"description": "exit to upper level menu", "handler": Menu.exitMenu} interface['q'] = {"description": "quit program", "handler": exit_program} self.interface = interface def print_prompt(self): print buf = ["Commands:"] for key, desc in self.interface.items(): buf.append(" %s: %s" % (key, desc['description'])) print "\n".join(buf) print def process_input(self, key): desc = self.interface.get(key) if desc is not None: desc["handler"]() else: queue.put(("print", "Illegal key")) def add_action(self, key, description): self.interface[key] = description def del_action(self, key): try: del self.interface[key] except KeyError: pass @staticmethod def gotoMenu(menu): func = (lambda: menu_stack.append(menu)) func.menu = menu return func @staticmethod def exitMenu(): menu_stack.pop() @staticmethod def exitTopLevel(): main = menu_stack.popleft() menu_stack.clear() menu_stack.append(main) class NotesMenu(Menu): def __init__(self, note_type, obj=None, timestamp_type=None): Menu.__init__(self, {'s': {"description": "show current notes", "handler": self._show_notes}, 'a': {"description": "add a note", "handler": self._add_note}, 'd': {"description": "delete a note", "handler": self._del_note}, 'c': {"description": "clear all note data", "handler": self._clear_notes}}) self.list = NoteList() self.note_type = note_type self.obj = obj self.timestamp_type = timestamp_type def _show_notes(self): buf = ["Notes:"] for note in self.list: buf.append(" %s'%s'" % ((note.lang is None) and ' ' or (' (%s) ' % note.lang), note.value)) print '\n'.join(buf) def _add_note(self): lang = getstr("Language") if lang == '': lang = None value = getstr("Note") self.list.append(self.note_type(value, lang)) if self.obj: self.obj.timestamp = self.timestamp_type() print "Note added" self.exitTopLevel() def _del_note(self): buf = ["Current notes:"] for note in self.list: buf.append(" %s'%s'" % ((note.lang is None) and ' ' or (' (%s) ' % note.lang), note.value)) print '\n'.join(buf) print lang = getstr("Language of note to delete") if lang == '': lang = None try: del self.list[lang] except KeyError: print "No note in language `%s'" % lang else: if self.obj: self.obj.timestamp = self.timestamp_type() print "Note deleted" self.exitTopLevel() def _clear_notes(self): notes = list(self.list) for note in notes: del self.list[note.lang] if self.obj: self.obj.timestamp = self.timestamp_type() print "Notes deleted" self.exitTopLevel() # Mood manipulation pidf class MoodMenu(Menu): def __init__(self): Menu.__init__(self, {'s': {"description": "show current moods", "handler": self._show_moods}, 'a': {"description": "add a mood", "handler": self._add_mood}, 'd': {"description": "delete a mood", "handler": self._del_mood}, 'c': {"description": "clear all mood data", "handler": self._clear_moods}, 'n': {"description": "set mood note", "handler": self._set_note}, 'r': {"description": "set random mood", "handler": self._set_random}}) self.auto_random = False def _show_moods(self): buf = ["Moods:"] if person.mood is not None: for m in person.mood.values: buf.append(" %s" % str(m)) print '\n'.join(buf) def _add_mood(self): buf = ["Possible moods:"] values = list(Mood._xml_value_maps.get(value, value) for value in Mood._xml_values) values.sort() max_len = max(len(s) for s in values)+2 format = " %%02d) %%-%ds" % max_len num_line = 72/(max_len+5) i = 0 text = '' for val in values: text += format % (i+1, val) i += 1 if i % num_line == 0: buf.append(text) text = '' print '\n'.join(buf) print m = getstr("Select mood to add (any non-number will string will return") try: m = int(m) if m not in xrange(len(values)): raise ValueError except ValueError: print "Invalid input" else: if person.mood is None: person.mood = Mood() person.mood.add(values[m-1]) person.timestamp = DMTimestamp() publish_pidf() print "Mood added" self.exitTopLevel() def _del_mood(self): if person.mood is None: print "There is no current mood set" return buf = ["Current moods:"] values = person.mood.values values.sort() max_len = max(len(s) for s in values)+2 format = " %%02d) %%-%ds" % max_len num_line = 72/(max_len+5) i = 0 text = '' for val in values: text += format % (i+1, val) i += 1 if i % num_line == 0: buf.append(text) text = '' buf.append(text) print '\n'.join(buf) print m = getstr("Select mood to delete") try: m = int(m) except ValueError: print "Invalid input" else: person.mood.remove(values[m-1]) person.timestamp = DMTimestamp() publish_pidf() print "Mood deleted" self.exitTopLevel() def _clear_moods(self): if person.mood is None: print "There is no current mood set" return person.mood = None person.timestamp = DMTimestamp() publish_pidf() print "Mood information cleared" self.exitTopLevel() def _set_note(self): if person.mood is not None and len(person.mood.notes) > 0: print 'Current note: %s' % person.mood.notes['en'] note = getstr("Set note") if note == '': if person.mood is not None and len(person.mood.notes) > 0: del person.mood.notes['en'] else: if person.mood is None: person.mood = Mood() person.mood.notes.append(RPIDNote(note, lang='en')) person.timestamp = DMTimestamp() publish_pidf() print 'Note set' self.exitTopLevel() def _set_random(self): values = list(Mood._xml_value_maps.get(value, value) for value in Mood._xml_values if value != 'unknown') random.shuffle(values) if person.mood is None: person.mood = Mood() else: person.mood.clear() values = values[:3] for mood in values: person.mood.add(mood) person.timestamp = DMTimestamp() publish_pidf() print "You are now " + ", ".join(values) self.exitTopLevel() def _set_auto_random(self): if self.auto_random: pass # Activities manipulation pidf class ActivitiesMenu(Menu): def __init__(self): Menu.__init__(self, {'s': {"description": "show current activity", "handler": self._show_activity}, 'a': {"description": "set activity", "handler": self._set_activity}, 'd': {"description": "delete activity", "handler": self._del_activity}, 'c': {"description": "clear all activity data", "handler": self._clear_activity}, 'n': {"description": "set activity note", "handler": self._set_note}, 'r': {"description": "set random activity", "handler": self._set_random}}) def _show_activity(self): buf = ["Activity:"] if person.activities is not None: for a in person.activities.values: buf.append(" %s" % str(a)) print '\n'.join(buf) def _set_activity(self): buf = ["Possible activities:"] values = list(Activities._xml_value_maps.get(value, value) for value in Activities._xml_values) values.sort() max_len = max(len(s) for s in values)+2 format = " %%02d) %%-%ds" % max_len num_line = 72/(max_len+5) i = 0 text = '' for val in values: text += format % (i+1, val) i += 1 if i % num_line == 0: buf.append(text) text = '' print '\n'.join(buf) print a = getstr("Select activity to add") try: a = int(a) if a not in xrange(len(values)): raise ValueError except ValueError: print "Invalid input" else: if person.activities is None: person.activities = Activities() else: person.activities.clear() person.activities.add(values[a-1]) person.timestamp = DMTimestamp() publish_pidf() print "Activity set" self.exitTopLevel() def _del_activity(self): if person.activities is None or len(person.activities.values) == 0: print "There is no current activity set" return person.activities.clear() person.timestamp = DMTimestamp() publish_pidf() print "Activity deleted" self.exitTopLevel() def _clear_activity(self): if person.activities is None: print "There is no current activity set" return person.activities = None person.timestamp = DMTimestamp() publish_pidf() print "Activities information cleared" self.exitTopLevel() def _set_note(self): if person.activities is not None and len(person.activities.notes) > 0: print 'Current note: %s' % person.activities.notes['en'] note = getstr("Set note") if note == '': if person.activities is not None and len(person.activities.notes) > 0: del person.activities.notes['en'] else: if person.activities is None: person.activities = Activities() person.activities.notes.append(RPIDNote(note, lang='en')) person.timestamp = DMTimestamp() publish_pidf() print 'Note set' self.exitTopLevel() def _set_random(self): values = list(Activities._xml_value_maps.get(value, value) for value in Activities._xml_values if value != 'unknown') activity = random.choice(values) if person.activities is None: person.activities = Activities() else: person.activities.clear() person.activities.add(activity) person.timestamp = DMTimestamp() publish_pidf() print "You are now %s" % activity self.exitTopLevel() def set_person_note(): if len(person.notes) > 0: print 'Current note: %s' % person.notes['en'] note = getstr("Set note") if note == '': if len(person.notes) > 0: del person.notes['en'] else: person.notes.append(DMNote(note, lang='en')) person.timestamp = DMTimestamp() publish_pidf() print 'Note added' def toggle_basic(): if tuple.status.basic == 'open': tuple.status.basic = Basic('closed') tuple.timestamp = Timestamp() publish_pidf() print "Your basic status is now 'closed'" else: tuple.status.basic = Basic('open') tuple.timestamp = Timestamp() publish_pidf() print "Your basic status is now 'open'" def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) atexit.register(termios_restore) def getstr(prompt='selection'): global string, getstr_event string = '' sys.stdout.write("%s> " % prompt) sys.stdout.flush() getstr_event.wait() getstr_event.clear() sys.stdout.write("\n") ret = string string = None return ret def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(4192) finally: termios_restore() else: return os.read(fd, 4192) def event_handler(event_name, **kwargs): global packet_count, start_time, queue, do_trace_pjsip, logger, return_code if event_name == "Publication_state": if kwargs["state"] == "unpublished": queue.put(("print", "Unpublished: %(code)d %(reason)s" % kwargs)) if kwargs["code"] / 100 == 2: return_code = 0 queue.put(("quit", None)) elif kwargs["state"] == "published": #queue.put(("print", "PUBLISH was successful")) pass elif event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) def read_queue(e, username, domain, password, display_name, route, expires, do_trace_pjsip): global user_quit, lock, queue, pub, sip_uri, pidf, person, tuple lock.acquire() try: sip_uri = SIPURI(user=username, host=domain, display=display_name) pub = Publication(Credentials(sip_uri, password), "presence", route=route, expires=expires) # initialize PIDF pidf = PIDF(entity='%s@%s' % (username, domain)) tuple = Tuple(''.join(chr(random.randint(97, 122)) for i in xrange(8)), status=Status(basic=Basic('open'))) tuple.timestamp = Timestamp() pidf.append(tuple) person = Person(''.join(chr(random.randint(97, 122)) for i in xrange(8))) person.time_offset = TimeOffset() person.timestamp = DMTimestamp() pidf.append(person) # initialize menus top_level = Menu({'s': {"description": "show PIDF", "handler": lambda: sys.stdout.write(pidf.toxml(pretty_print=True))}}) top_level.del_action('x') menu_stack.append(top_level) top_level.add_action('m', {"description": "set mood information", "handler": Menu.gotoMenu(MoodMenu())}) top_level.add_action('a', {"description": "set activities information", "handler": Menu.gotoMenu(ActivitiesMenu())}) top_level.add_action('b', {"description": "toggle basic status", "handler": toggle_basic}) person_notes_menu = NotesMenu(DMNote, person, DMTimestamp) top_level.add_action('n', {"description": "set note", "handler": set_person_note}) # publish initial pidf publish_pidf() # stuff that depends on menus person.notes = person_notes_menu.list menu_stack[-1].print_prompt() while True: command, data = queue.get() if command == "print": print data menu_stack[-1].print_prompt() if command == "pypjua_event": event_name, args = data if command == "user_input": key = data if command == "eof": command = "end" want_quit = True if command == "end": try: pub.unpublish() except: pass if command == "quit": user_quit = False break if command == "user_input": menu_stack[-1].process_input(data) menu_stack[-1].print_prompt() except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_publish(**kwargs): global user_quit, lock, queue, do_trace_pjsip, string, getstr_event, old, logger ctrl_d_pressed = False do_trace_pjsip = kwargs["do_trace_pjsip"] outbound_proxy = kwargs.pop("outbound_proxy") if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs['trace_sip']) if kwargs.pop('trace_sip'): print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=True, local_ip=kwargs.pop("local_ip"), local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port")) e.start(False) start_new_thread(read_queue, (e,), kwargs) atexit.register(termios_restore) try: while True: for char in getchar(): if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True break else: if string is not None: if char == "\x7f": if len(string) > 0: char = "\x08" sys.stdout.write("\x08 \x08") sys.stdout.flush() string = string[:-1] else: if old is not None: sys.stdout.write(char) sys.stdout.flush() if char == "\x0A": getstr_event.set() break else: string += char else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_options(): retval = {} description = "This script will publish rich presence state of the specified SIP account to a SIP Presence Agent, the presence information can be changed using a menu-driven interface." usage = "%prog [options]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-e", "--expires", type="int", dest="expires", help='"Expires" value to set in PUBLISH. Default is 300 seconds.') parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) if not AccountConfig.use_presence_agent: raise RuntimeError("Presence is not enabled for this account. Please set use_presence_agent=True in the config file") default_options = dict(expires=300, outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, trace_sip=GeneralConfig.trace_sip, do_trace_pjsip=GeneralConfig.trace_pjsip, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_publish(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) sys.exit(return_code) diff --git a/scripts/sip_register.py b/scripts/sip_register.py index 8bde9603..f6464f79 100644 --- a/scripts/sip_register.py +++ b/scripts/sip_register.py @@ -1,250 +1,251 @@ #!/usr/bin/env python import sys import traceback import os import signal import termios import select from thread import start_new_thread, allocate_lock from Queue import Queue from optparse import OptionParser, OptionValueError from application.configuration import * from application.process import process from pypjua import * from pypjua.clients import enrollment from pypjua.clients.log import Logger -from pypjua.clients.lookup import * +from pypjua.clients.dns_lookup import * +from pypjua.clients import * class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy} sip_address = None password = None display_name = None outbound_proxy = None process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("General", GeneralConfig) queue = Queue() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() logger = None return_code = 1 def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(10) finally: termios_restore() else: return os.read(fd, 10) def event_handler(event_name, **kwargs): global start_time, packet_count, queue, do_trace_pjsip if event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) def read_queue(e, username, domain, password, display_name, route, expires, max_registers): global user_quit, lock, queue, do_trace_pjsip, logger, return_code lock.acquire() printed = False max_registers = max_registers or None try: credentials = Credentials(SIPURI(user=username, host=domain, display=display_name), password) reg = Registration(credentials, route=route, expires=expires) print 'Registering "%s" at %s:%s:%d' % (credentials.uri, route.transport, route.host, route.port) reg.register() while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if event_name == "Registration_state": if args["state"] == "registered": return_code = 0 if not printed: print "REGISTER was successful" print "Contact: %s (expires in %d seconds)" % (args["contact_uri"], args["expires"]) if len(args["contact_uri_list"]) > 1: print "Other registered contacts:\n%s" % "\n".join(["%s (expires in %d seconds)" % contact_tup for contact_tup in args["contact_uri_list"] if contact_tup[0] != args["contact_uri"]]) print "Press Ctrl+D to stop the program." printed = True if max_registers is not None: max_registers -= 1 if max_registers <= 0: command = "eof" elif args["state"] == "unregistered": if "code" in args and args["code"] / 100 != 2: print "Unregistered: %(code)d %(reason)s" % args user_quit = False command = "quit" elif event_name == "Invitation_state": if args["state"] == "INCOMING": args["obj"].end() if command == "user_input": key = data if key == 's': logger.trace_sip = not logger.trace_sip print "SIP tracing is now %s" % ("activated" if logger.trace_sip else "deactivated") if key == 'l': do_trace_pjsip = not do_trace_pjsip print "PJSIP logging is now %s" % ("activated" if do_trace_pjsip else "deactivated") if command == "eof": reg.unregister() if command == "quit": break except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_register(**kwargs): global user_quit, lock, queue, do_trace_pjsip, logger do_trace_pjsip = kwargs.pop("do_trace_pjsip") ctrl_d_pressed = False outbound_proxy = kwargs.pop("outbound_proxy") if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs.pop('trace_sip')) if logger.trace_sip: print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=True, local_ip=kwargs.pop("local_ip"), local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port")) e.start(False) start_new_thread(read_queue, (e,), kwargs) try: while True: char = getchar() if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_options(): retval = {} description = "This script will register a SIP account to a SIP registrar and refresh it while the program is running. When Ctrl+D is pressed it will unregister." usage = "%prog [options]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file.") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP login account") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-n", "--display-name", type="string", dest="display_name", help="Display name to use for the local account. This overrides the setting from the config file.") parser.add_option("-e", "--expires", type="int", dest="expires", help='"Expires" value to set in REGISTER. Default is 300 seconds.') parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") parser.add_option("-r", "--max-registers", type="int", dest="max_registers", help="Max number of REGISTERs sent (default 1, set to 0 for infinite).") options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) default_options = dict(expires=300, outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, trace_sip=GeneralConfig.trace_sip, do_trace_pjsip=GeneralConfig.trace_pjsip, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports, max_registers=1) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_register(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) sys.exit(return_code) diff --git a/scripts/sip_subscribe_presence.py b/scripts/sip_subscribe_presence.py index 79b385c7..8e6d0f68 100644 --- a/scripts/sip_subscribe_presence.py +++ b/scripts/sip_subscribe_presence.py @@ -1,502 +1,502 @@ #!/usr/bin/env python import sys import traceback import string import socket import os import atexit import select import termios import signal import datetime from thread import start_new_thread, allocate_lock from threading import Thread from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep from application.process import process from application.configuration import * from pypjua import * from pypjua.clients import enrollment from pypjua.clients.log import Logger from pypjua.applications import ParserError from pypjua.applications.pidf import * from pypjua.applications.presdm import * from pypjua.applications.rpid import * from pypjua.clients.clientconfig import get_path -from pypjua.clients.lookup import * -from pypjua.clients import format_cmdline_uri +from pypjua.clients.dns_lookup import * +from pypjua.clients import * class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy} sip_address = None password = None display_name = None outbound_proxy = None process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("General", GeneralConfig) queue = Queue() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() logger = None return_code = 1 def format_note(note): text = "Note" if note.lang is not None: text += "(%s)" % note.lang if note.since is not None or note.until is not None: text += " valid" if note.since is not None: text += " from %s" % note.since if note.until is not None: text += " until %s" % note.until text += ": %s" % note return text def display_person(person, pidf, buf): # display class if person.rpid_class is not None: buf.append(" Class: %s" % person.rpid_class) # display timestamp if person.timestamp is not None: buf.append(" Timestamp: %s" % person.timestamp) # display notes if len(person.notes) > 0: for note in person.notes: buf.append(" %s" % format_note(note)) elif len(pidf.notes) > 0: for note in pidf.notes: buf.append(" %s" % format_note(note)) # display activities if person.activities is not None: activities = person.activities.values if len(activities) > 0: text = " Activities" if person.activities.since is not None or person.activities.until is not None: text += " valid" if person.activities.since is not None: text += " from %s" % person.activities.since if person.activities.until is not None: text += " until %s" % person.activities.until text += ": %s" % ', '.join(str(activity) for activity in activities) buf.append(text) if len(person.activities.notes) > 0: for note in person.activities.notes: buf.append(" %s" % format_note(note)) elif len(person.activities.notes) > 0: buf.append(" Activities") for note in person.activities.notes: buf.append(" %s" % format_note(note)) # display mood if person.mood is not None: moods = person.mood.values if len(moods) > 0: text = " Mood" if person.mood.since is not None or person.mood.until is not None: text += " valid" if person.mood.since is not None: text += " from %s" % person.mood.since if person.mood.until is not None: text += " until %s" % person.mood.until text += ": %s" % ', '.join(str(mood) for mood in moods) buf.append(text) if len(person.mood.notes) > 0: for note in person.mood.notes: buf.append(" %s" % format_note(note)) # display place is if person.place_is is not None: place_keys = (key for key in ('audio', 'video', 'text') if getattr(person.place_is, key) is not None) place_info = ', '.join('%s %s' % (key.capitalize(), getattr(person.place_is, key).value) for key in place_keys) if place_info != '': buf.append(" Place information: " + place_info) # display privacy if person.privacy is not None: text = " Private conversation possible with: " private = [] if person.privacy.audio: private.append("Audio") if person.privacy.video: private.append("Video") if person.privacy.text: private.append("Text") if len(private) > 0: text += ", ".join(private) else: text += "None" buf.append(text) # display sphere if person.sphere is not None: timeinfo = [] if person.sphere.since is not None: timeinfo.append('from %s' % str(person.sphere.since)) if person.sphere.until is not None: timeinfo.append('until %s' % str(person.sphere.until)) if len(timeinfo) != 0: timeinfo = ' (' + ', '.join(timeinfo) + ')' else: timeinfo = '' buf.append(" Current sphere%s: %s" % (timeinfo, person.sphere.value)) # display status icon if person.status_icon is not None: buf.append(" Status icon: %s" % person.status_icon) # display time and time offset if person.time_offset is not None: ctime = datetime.datetime.utcnow() + datetime.timedelta(minutes=int(person.time_offset)) time_offset = int(person.time_offset)/60.0 if time_offset == int(time_offset): offset_info = '(UTC+%d%s)' % (time_offset, (person.time_offset.description is not None and (' (%s)' % person.time_offset.description) or '')) else: offset_info = '(UTC+%.1f%s)' % (time_offset, (person.time_offset.description is not None and (' (%s)' % person.time_offset.description) or '')) buf.append(" Current user time: %s %s" % (ctime.strftime("%H:%M"), offset_info)) # display user input if person.user_input is not None: buf.append(" User is %s" % person.user_input) if person.user_input.last_input: buf.append(" Last input at: %s" % person.user_input.last_input) if person.user_input.idle_threshold: buf.append(" Idle threshold: %s seconds" % person.user_input.idle_threshold) def display_service(service, pidf, buf): # display class if service.rpid_class is not None: buf.append(" Class: %s" % person.rpid_class) # display timestamp if service.timestamp is not None: buf.append(" Timestamp: %s" % service.timestamp) # display notes for note in service.notes: buf.append(" %s" % format_note(note)) # display status if service.status is not None and service.status.basic is not None: buf.append(" Status: %s" % service.status.basic) # display contact if service.contact is not None: buf.append(" Contact%s: %s" % ((service.contact.priority is not None) and (' priority %s' % service.contact.priority) or '', service.contact)) # display device ID if service.device_id is not None: buf.append(" Service offered by device id: %s" % service.device_id) # display relationship if service.relationship is not None: buf.append(" Relationship: %s" % service.relationship.value) # display service-class if service.service_class is not None: buf.append(" Service class: %s" % service.service_class.value) # display status icon if service.status_icon is not None: buf.append(" Status icon: %s" % service.status_icon) # display user input if service.user_input is not None: buf.append(" Service is %s" % service.user_input) if service.user_input.last_input: buf.append(" Last input at: %s" % service.user_input.last_input) if service.user_input.idle_threshold: buf.append(" Idle threshold: %s seconds" % service.user_input.idle_threshold) def display_device(device, pidf, buf): # display device ID if device.device_id is not None: buf.append(" Device id: %s" % device.device_id) # display class if device.rpid_class is not None: buf.append(" Class: %s" % person.rpid_class) # display timestamp if device.timestamp is not None: buf.append(" Timestamp: %s" % device.timestamp) # display notes for note in device.notes: buf.append(" %s" % format_note(note)) # display user input if device.user_input is not None: buf.append(" Device is %s" % device.user_input) if device.user_input.last_input: buf.append(" Last input at: %s" % device.user_input.last_input) if device.user_input.idle_threshold: buf.append(" Idle threshold: %s seconds" % device.user_input.idle_threshold) def handle_pidf(pidf): buf = ["-"*16] buf.append("Presence for %s:" % pidf.entity) persons = {} devices = {} services = {} printed_sep = True for child in pidf: if isinstance(child, Person): persons[child.id] = child elif isinstance(child, Device): devices[child.id] = child elif isinstance(child, Tuple): services[child.id] = child # handle person information if len(persons) == 0: if len(pidf.notes) > 0: buf.append(" Person information:") for note in pidf.notes: buf.append(" %s" % format_note(note)) printed_sep = False else: for person in persons.values(): buf.append(" Person id: %s" % person.id) display_person(person, pidf, buf) printed_sep = False # handle services informaation if len(services) > 0: if not printed_sep: buf.append(" " + "-"*3) for service in services.values(): buf.append(" Service id: %s" % service.id) display_service(service, pidf, buf) # handle devices informaation if len(devices) > 0: if not printed_sep: buf.append(" " + "-"*3) for device in devices.values(): buf.append(" Device id: %s" % device.id) display_device(device, pidf, buf) buf.append("-"*16) # push the data text = '\n'.join(buf) queue.put(("print", text)) def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(10) finally: termios_restore() else: return os.read(fd, 10) def event_handler(event_name, **kwargs): global start_time, packet_count, queue, do_trace_pjsip, logger, return_code if event_name == "Subscription_state": if kwargs["state"] == "ACTIVE": #queue.put(("print", "SUBSCRIBE was successful")) return_code = 0 elif kwargs["state"] == "TERMINATED": if kwargs.has_key("code"): if kwargs['code'] / 100 == 2: return_code = 0 queue.put(("print", "Unsubscribed: %(code)d %(reason)s" % kwargs)) else: queue.put(("print", "Unsubscribed")) queue.put(("quit", None)) elif kwargs["state"] == "PENDING": queue.put(("print", "Subscription is pending")) elif event_name == "Subscription_notify": return_code = 0 if ('%s/%s' % (kwargs['content_type'], kwargs['content_subtype'])) in PIDF.accept_types: queue.put(("print", "Received NOTIFY:")) try: pidf = PIDF.parse(kwargs['body']) except ParserError, e: queue.put(("print", "Got illegal pidf document: %s\n%s" % (str(e), kwargs['body']))) else: handle_pidf(pidf) elif event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) def read_queue(e, username, domain, password, display_name, presentity_uri, route, expires, content_type, do_trace_pjsip): global user_quit, lock, queue, logger lock.acquire() try: credentials = Credentials(SIPURI(user=username, host=domain, display=display_name), password) sub = Subscription(credentials, presentity_uri, 'presence', route=route, expires=expires) print 'Subscribing to "%s" for the presence event, at %s:%s:%d' % (presentity_uri, route.transport, route.host, route.port) sub.subscribe() while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if command == "user_input": key = data if command == "eof": command = "end" want_quit = True if command == "end": try: sub.unsubscribe() except: pass if command == "quit": user_quit = False break except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_subscribe(**kwargs): global user_quit, lock, queue, do_trace_pjsip, logger ctrl_d_pressed = False do_trace_pjsip = kwargs["do_trace_pjsip"] outbound_proxy = kwargs.pop("outbound_proxy") if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") events = Engine.init_options_defaults["events"] if kwargs['content_type'] is not None: events['presence'] = [kwargs['content_type']] logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs['trace_sip']) if kwargs['trace_sip']: print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=kwargs.pop('trace_sip'), events=events, local_ip=kwargs.pop("local_ip"), local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port")) e.start(False) kwargs["presentity_uri"] = e.parse_sip_uri(kwargs["presentity_uri"]) start_new_thread(read_queue, (e,), kwargs) atexit.register(termios_restore) try: while True: char = getchar() if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_options(): retval = {} description = "This script will SUBSCRIBE to the presence event published by the specified SIP target. If a SIP target is not specified, it will subscribe to its own address. It will then interprete PIDF bodies contained in NOTIFYs and display their meaning. The program will un-SUBSCRIBE and quit when CTRL+D is pressed." usage = "%prog [options] [target-user@target-domain.com]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-n", "--display-name", type="string", dest="display_name", help="Display name to use for the local account. This overrides the setting from the config file.") parser.add_option("-e", "--expires", type="int", dest="expires", help='"Expires" value to set in SUBSCRIBE. Default is 300 seconds.') parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-c", "--content-type", type="string", dest="content_type", help = '"Content-Type" the UA expects to receving in a NOTIFY for this subscription. For the known events this does not need to be specified, but may be overridden".') parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) default_options = dict(expires=300, outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, content_type=None, trace_sip=GeneralConfig.trace_sip, do_trace_pjsip=GeneralConfig.trace_pjsip, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] if args: retval["presentity_uri"] = format_cmdline_uri(args[0], retval["domain"]) else: retval["presentity_uri"] = format_cmdline_uri(retval["username"], retval["domain"]) accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_subscribe(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) sys.exit(return_code) diff --git a/scripts/sip_subscribe_rls.py b/scripts/sip_subscribe_rls.py index 3f8bf04e..d8a82b59 100644 --- a/scripts/sip_subscribe_rls.py +++ b/scripts/sip_subscribe_rls.py @@ -1,485 +1,485 @@ #!/usr/bin/env python import sys import traceback import string import socket import os import atexit import select import termios import signal from thread import start_new_thread, allocate_lock from threading import Thread from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep from application.process import process from application.configuration import * from pypjua import * from pypjua.clients import enrollment from pypjua.clients.log import Logger from pypjua.applications import ParserError from pypjua.applications.pidf import * from pypjua.applications.presdm import * from pypjua.applications.rpid import * from pypjua.clients.clientconfig import get_path -from pypjua.clients.lookup import * -from pypjua.clients import format_cmdline_uri +from pypjua.clients.dns_lookup import * +from pypjua.clients import * class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy, "use_presence_agent": datatypes.Boolean} sip_address = None password = None display_name = None outbound_proxy = None use_presence_agent = True process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("General", GeneralConfig) queue = Queue() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() logger = None return_code = 1 def format_note(note): text = "Note" if note.lang is not None: text += "(%s)" % note.lang if note.since is not None or note.until is not None: text += " valid" if note.since is not None: text += " from %s" % note.since if note.until is not None: text += " until %s" % note.until text += ": %s" % note return text def display_person(person, pidf, buf): # display class if person.rpid_class is not None: buf.append(" Class: %s" % person.rpid_class) # display timestamp if person.timestamp is not None: buf.append(" Timestamp: %s" % person.timestamp) # display notes if len(person.notes) > 0: for note in person.notes: buf.append(" %s" % format_note(note)) elif len(pidf.notes) > 0: for note in pidf.notes: buf.append(" %s" % format_note(note)) # display activities if person.activities is not None: activities = person.activities.values if len(activities) > 0: text = " Activities" if person.activities.since is not None or person.activities.until is not None: text += " valid" if person.activities.since is not None: text += " from %s" % person.activities.since if person.activities.until is not None: text += " until %s" % person.activities.until text += ": %s" % ', '.join(str(activity) for activity in activities) buf.append(text) if len(person.activities.notes) > 0: for note in person.activities.notes: buf.append(" %s" % format_note(note)) elif len(person.activities.notes) > 0: buf.append(" Activities") for note in person.activities.notes: buf.append(" %s" % format_note(note)) # display mood if person.mood is not None: moods = person.mood.values if len(moods) > 0: text = " Mood" if person.mood.since is not None or person.mood.until is not None: text += " valid" if person.mood.since is not None: text += " from %s" % person.mood.since if person.mood.until is not None: text += " until %s" % person.mood.until text += ": %s" % ', '.join(str(mood) for mood in moods) buf.append(text) if len(person.mood.notes) > 0: for note in person.mood.notes: buf.append(" %s" % format_note(note)) # display place is if person.place_is is not None: buf.append(" Place information:") if person.place_is.audio is not None: buf.append(" Audio: %s" % person.place_is.audio.values[0]) if person.place_is.video is not None: buf.append(" Video: %s" % person.place_is.video.values[0]) if person.place_is.text is not None: buf.append(" Text: %s" % person.place_is.text.values[0]) # display privacy if person.privacy is not None: text = " Communication that is private: " private = [] if person.privacy.audio: private.append("audio") if person.privacy.video: private.append("video") if person.privacy.text: private.append("text") text += ", ".join(private) buf.append(text) # display sphere if person.sphere is not None: buf.append(" Current sphere: %s" % person.sphere.values[0]) # display status icon if person.status_icon is not None: buf.append(" Status icon: %s" % person.status_icon) # display time offset if person.time_offset is not None: buf.append(" Time offset from UTC: %s minutes %s" % (person.time_offset, (person.time_offset.description is not None and ('(%s)' % person.time_offset.description) or ''))) # display user input if person.user_input is not None: buf.append(" User is %s" % person.user_input) if person.user_input.last_input: buf.append(" Last input at: %s" % person.user_input.last_input) if person.user_input.idle_threshold: buf.append(" Idle threshold: %s seconds" % person.user_input.idle_threshold) def display_service(service, pidf, buf): # display class if service.rpid_class is not None: buf.append(" Class: %s" % person.rpid_class) # display timestamp if service.timestamp is not None: buf.append(" Timestamp: %s" % service.timestamp) # display notes for note in service.notes: buf.append(" %s" % format_note(note)) # display status if service.status is not None and service.status.basic is not None: buf.append(" Status: %s" % service.status.basic) # display contact if service.contact is not None: buf.append(" Contact%s: %s" % ((service.contact.priority is not None) and (' priority %s' % service.contact.priority) or '', service.contact)) # display device ID if service.device_id is not None: buf.append(" Service offered by device id: %s" % service.device_id) # display relationship if service.relationship is not None: buf.append(" Relationship: %s" % service.relationship.values[0]) # display service-class if service.service_class is not None: buf.append(" Service class: %s" % service.service_class.values[0]) # display status icon if service.status_icon is not None: buf.append(" Status icon: %s" % service.status_icon) # display user input if service.user_input is not None: buf.append(" Service is %s" % service.user_input) if service.user_input.last_input: buf.append(" Last input at: %s" % service.user_input.last_input) if service.user_input.idle_threshold: buf.append(" Idle threshold: %s seconds" % service.user_input.idle_threshold) def display_device(device, pidf, buf): # display device ID if device.device_id is not None: buf.append(" Device id: %s" % device.device_id) # display class if device.rpid_class is not None: buf.append(" Class: %s" % person.rpid_class) # display timestamp if device.timestamp is not None: buf.append(" Timestamp: %s" % device.timestamp) # display notes for note in device.notes: buf.append(" %s" % format_note(note)) # display user input if device.user_input is not None: buf.append(" Service is %s" % device.user_input) if device.user_input.last_input: buf.append(" Last input at: %s" % device.user_input.last_input) if device.user_input.idle_threshold: buf.append(" Idle threshold: %s seconds" % device.user_input.idle_threshold) def handle_pidf(pidf): buf = ["-"*16] buf.append("Presence for %s:" % pidf.entity) persons = {} devices = {} services = {} printed_sep = True for child in pidf: if isinstance(child, Person): persons[child.id] = child elif isinstance(child, Device): devices[child.id] = child elif isinstance(child, Tuple): services[child.id] = child # handle person information if len(persons) == 0: if len(pidf.notes) > 0: buf.append(" Person information:") for note in pidf.notes: buf.append(" %s" % format_note(note)) printed_sep = False else: for person in persons.values(): buf.append(" Person id %s" % person.id) display_person(person, pidf, buf) printed_sep = False # handle services informaation if len(services) > 0: if not printed_sep: buf.append(" " + "-"*3) for service in services.values(): buf.append(" Service id %s" % service.id) display_service(service, pidf, buf) # handle devices informaation if len(devices) > 0: if not printed_sep: buf.append(" " + "-"*3) for device in devices.values(): buf.append(" Device id %s" % device.id) display_device(device, pidf, buf) buf.append("-"*16) # push the data text = '\n'.join(buf) queue.put(("print", text)) def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(10) finally: termios_restore() else: return os.read(fd, 10) def event_handler(event_name, **kwargs): global start_time, packet_count, queue, do_trace_pjsip, logger, return_code if event_name == "Subscription_state": if kwargs["state"] == "ACTIVE": #queue.put(("print", "SUBSCRIBE was successful")) return_code = 0 elif kwargs["state"] == "TERMINATED": if kwargs.has_key("code"): if kwargs['code'] / 100 == 2: return_code = 0 queue.put(("print", "Unsubscribed: %(code)d %(reason)s" % kwargs)) else: queue.put(("print", "Unsubscribed")) queue.put(("quit", None)) elif kwargs["state"] == "PENDING": queue.put(("print", "Subscription is pending")) elif event_name == "Subscription_notify": return_code = 0 if ('%s/%s' % (kwargs['content_type'], kwargs['content_subtype'])) in PIDF.accept_types: queue.put(("print", "Received NOTIFY: %s" % kwargs)) elif event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) def read_queue(e, username, domain, password, display_name, presentity_uri, route, expires, content_type, do_trace_pjsip): global user_quit, lock, queue, logger lock.acquire() try: credentials = Credentials(SIPURI(user=username, host=domain, display=display_name), password) sub = Subscription(credentials, presentity_uri, 'presence', route=route, expires=expires, extra_headers={'Supported': 'eventlist'}) print 'Subscribing to "%s" for the presence event, at %s:%s:%d' % (presentity_uri, route.transport, route.host, route.port) sub.subscribe() while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if command == "user_input": key = data if command == "eof": command = "end" want_quit = True if command == "end": try: sub.unsubscribe() except: pass if command == "quit": user_quit = False break except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_subscribe(**kwargs): global user_quit, lock, queue, do_trace_pjsip, logger ctrl_d_pressed = False do_trace_pjsip = kwargs["do_trace_pjsip"] outbound_proxy = kwargs.pop("outbound_proxy") if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") events = Engine.init_options_defaults["events"] if kwargs['content_type'] is not None: events['presence'] = [kwargs['content_type']] else: events['presence'] = ['multipart/related', 'application/rlmi+xml', 'application/pidf+xml'] logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs['trace_sip']) if kwargs['trace_sip']: print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=kwargs.pop('trace_sip'), local_ip=kwargs.pop("local_ip"), local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port"), events=events) e.start(False) kwargs["presentity_uri"] = e.parse_sip_uri(kwargs["presentity_uri"]) start_new_thread(read_queue, (e,), kwargs) atexit.register(termios_restore) try: while True: char = getchar() if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_options(): retval = {} description = "This script will SUBSCRIBE to the presence event published by the specified SIP target assuming it is a resource list handled by a RLS server. The RLS server will then SUBSCRIBE in behalf of the account, collect NOTIFYs with the presence information of the recipients and provide periodically aggregated NOTIFYs back to the subscriber. If a target address is not specified, it will subscribe to the account's own address. It will then interprete PIDF bodies contained in NOTIFYs and display their meaning. The program will un-SUBSCRIBE and quit when CTRL+D is pressed." usage = "%prog [options] [target-user@target-domain.com]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-n", "--display-name", type="string", dest="display_name", help="Display name to use for the local account. This overrides the setting from the config file.") parser.add_option("-e", "--expires", type="int", dest="expires", help='"Expires" value to set in SUBSCRIBE. Default is 300 seconds.') parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-c", "--content-type", type="string", dest="content_type", help = '"Content-Type" the UA expects to receving in a NOTIFY for this subscription. For the known events this does not need to be specified, but may be overridden".') parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) if not AccountConfig.use_presence_agent: raise RuntimeError("Presence is not enabled for this account. Please set use_presence_agent=True in the config file") default_options = dict(expires=300, outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, content_type=None, trace_sip=GeneralConfig.trace_sip, do_trace_pjsip=GeneralConfig.trace_pjsip, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] if args: retval["presentity_uri"] = format_cmdline_uri(args[0], retval["domain"]) else: retval["presentity_uri"] = format_cmdline_uri('%s-buddies' % retval["username"], retval["domain"]) accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_subscribe(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) sys.exit(return_code) diff --git a/scripts/sip_subscribe_winfo.py b/scripts/sip_subscribe_winfo.py index fae61e9b..34714f67 100644 --- a/scripts/sip_subscribe_winfo.py +++ b/scripts/sip_subscribe_winfo.py @@ -1,478 +1,479 @@ #!/usr/bin/env python import sys import traceback import string import socket import os import atexit import select import termios import signal from collections import deque from thread import start_new_thread, allocate_lock from threading import Thread from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep from application.process import process from application.configuration import * from urllib2 import URLError from pypjua import * from pypjua.clients import enrollment from pypjua.clients.log import Logger from pypjua.applications import ParserError from pypjua.applications.watcherinfo import * from pypjua.applications.policy import * from pypjua.applications.presrules import * from pypjua.clients.clientconfig import get_path -from pypjua.clients.lookup import * +from pypjua.clients.dns_lookup import * +from pypjua.clients import * from xcaplib.client import XCAPClient from xcaplib.error import HTTPError class GeneralConfig(ConfigSection): _datatypes = {"local_ip": datatypes.IPAddress, "sip_transports": datatypes.StringList, "trace_pjsip": datatypes.Boolean, "trace_sip": datatypes.Boolean} local_ip = None sip_local_udp_port = 0 sip_local_tcp_port = 0 sip_local_tls_port = 0 sip_transports = ["tls", "tcp", "udp"] trace_pjsip = False trace_sip = False log_directory = '~/.sipclient/log' class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "outbound_proxy": OutboundProxy, "xcap_root": str, "use_presence_agent": datatypes.Boolean} sip_address = None password = None display_name = None outbound_proxy = None xcap_root = None use_presence_agent = True process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") configuration.read_settings("General", GeneralConfig) queue = Queue() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() sip_uri = None logger = None return_code = 1 pending = deque() winfo = None xcap_client = None prules = None prules_etag = None allow_rule = None allow_rule_identities = None block_rule = None block_rule_identities = None polite_block_rule = None polite_block_rule_identities = None def get_prules(): global prules, prules_etag, allow_rule, block_rule, allow_rule_identities, block_rule_identities prules = None prules_etag = None allow_rule = None allow_rule_identities = None block_rule = None block_rule_identities = None try: doc = xcap_client.get('pres-rules') except URLError, e: print "Cannot obtain 'pres-rules' document: %s" % str(e) except HTTPError, e: if e.response.status != 404: print "Cannot obtain 'pres-rules' document: %s %s" % (e.response.status, e.response.reason) else: prules = PresRules() else: try: prules = PresRules.parse(doc) except ParserError, e: print "Invalid 'pres-rules' document: %s" % str(e) else: prules_etag = doc.etag # find each rule type for rule in prules: if rule.actions is not None: for action in rule.actions: if isinstance(action, SubHandling): if action == 'allow': if rule.conditions is not None: for condition in rule.conditions: if isinstance(condition, Identity): allow_rule = rule allow_rule_identities = condition break elif action == 'block': if rule.conditions is not None: for condition in rule.conditions: if isinstance(condition, Identity): block_rule = rule block_rule_identities = condition break elif action == 'polite-block': if rule.conditions is not None: for condition in rule.conditions: if isinstance(condition, Identity): polite_block_rule = rule polite_block_rule_identities = condition break break def allow_watcher(watcher): global prules, prules_etag, allow_rule, allow_rule_identities for i in xrange(3): if prules is None: get_prules() if prules is not None: if allow_rule is None: allow_rule_identities = Identity() allow_rule = Rule('pres_whitelist', conditions=Conditions([allow_rule_identities]), actions=Actions([SubHandling('allow')]), transformations=Transformations([ProvideServices([AllServices()]), ProvidePersons([AllPersons()]), ProvideDevices([AllDevices()]), ProvideAllAttributes()])) prules.append(allow_rule) if str(watcher) not in allow_rule_identities: allow_rule_identities.append(IdentityOne(str(watcher))) try: res = xcap_client.put('pres-rules', prules.toxml(pretty_print=True), etag=prules_etag) except HTTPError, e: print "Cannot PUT 'pres-rules' document: %s" % str(e) prules = None else: prules_etag = res.etag print "Watcher %s is now allowed" % watcher break sleep(0.1) else: print "Could not allow watcher %s" % watcher def block_watcher(watcher): global prules, prules_etag, block_rule, block_rule_identities for i in xrange(3): if prules is None: get_prules() if prules is not None: if block_rule is None: block_rule_identities = Identity() block_rule = Rule('pres_blacklist', conditions=Conditions([block_rule_identities]), actions=Actions([SubHandling('block')]), transformations=Transformations()) prules.append(block_rule) if str(watcher) not in block_rule_identities: block_rule_identities.append(IdentityOne(str(watcher))) try: res = xcap_client.put('pres-rules', prules.toxml(pretty_print=True), etag=prules_etag) except HTTPError, e: print "Cannot PUT 'pres-rules' document: %s" % str(e) prules = None else: prules_etag = res.etag print "Watcher %s is now denied" % watcher break sleep(0.1) else: print "Could not deny watcher %s" % watcher def polite_block_watcher(watcher): global prules, prules_etag, polite_block_rule, polite_block_rule_identities for i in xrange(3): if prules is None: get_prules() if prules is not None: if polite_block_rule is None: polite_block_rule_identities = Identity() polite_block_rule = Rule('pres_polite_blacklist', conditions=Conditions([polite_block_rule_identities]), actions=Actions([SubHandling('polite-block')]), transformations=Transformations()) prules.append(polite_block_rule) if str(watcher) not in polite_block_rule_identities: polite_block_rule_identities.append(IdentityOne(str(watcher))) try: res = xcap_client.put('pres-rules', prules.toxml(pretty_print=True), etag=prules_etag) except HTTPError, e: print "Cannot PUT 'pres-rules' document: %s" % str(e) prules = None else: prules_etag = res.etag print "Watcher %s is now politely blocked" % watcher break sleep(0.1) else: print "Could not politely block authorization of watcher %s" % watcher def handle_winfo(result): buf = ["Received NOTIFY:", "----"] self = 'sip:%s@%s' % (sip_uri.user, sip_uri.host) wlist = winfo[self] buf.append("Active watchers:") for watcher in wlist.active: buf.append(" %s" % watcher) buf.append("Terminated watchers:") for watcher in wlist.terminated: buf.append(" %s" % watcher) buf.append("Pending watchers:") for watcher in wlist.pending: buf.append(" %s" % watcher) buf.append("Waiting watchers:") for watcher in wlist.waiting: buf.append(" %s" % watcher) buf.append("----") queue.put(("print", '\n'.join(buf))) if result.has_key(self): for watcher in result[self]: if (watcher.status == 'pending' or watcher.status == 'waiting') and watcher not in pending and xcap_client is not None: pending.append(watcher) def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(10) finally: termios_restore() else: return os.read(fd, 10) def event_handler(event_name, **kwargs): global start_time, packet_count, queue, do_trace_pjsip, winfo, logger, return_code if event_name == "Subscription_state": if kwargs["state"] == "ACTIVE": #queue.put(("print", "SUBSCRIBE was successful")) return_code = 0 elif kwargs["state"] == "TERMINATED": if kwargs.has_key("code"): if kwargs['code'] / 100 == 2: return_code = 0 queue.put(("print", "Unsubscribed: %(code)d %(reason)s" % kwargs)) else: queue.put(("print", "Unsubscribed")) queue.put(("quit", None)) elif kwargs["state"] == "PENDING": queue.put(("print", "Subscription is pending")) elif event_name == "Subscription_notify": return_code = 0 if ('%s/%s' % (kwargs['content_type'], kwargs['content_subtype'])) in WatcherInfo.accept_types: try: result = winfo.update(kwargs['body']) except ParserError, e: queue.put(("print", "Got illegal winfo document: %s\n%s" % (str(e), kwargs['body']))) else: handle_winfo(result) elif event_name == "siptrace": logger.log(event_name, **kwargs) elif event_name != "log": queue.put(("pypjua_event", (event_name, kwargs))) elif do_trace_pjsip: queue.put(("print", "%(timestamp)s (%(level)d) %(sender)14s: %(message)s" % kwargs)) def read_queue(e, username, domain, password, display_name, route, xcap_root, expires, do_trace_pjsip): global user_quit, lock, queue, sip_uri, winfo, xcap_client, logger lock.acquire() try: sip_uri = SIPURI(user=username, host=domain, display=display_name) sub = Subscription(Credentials(sip_uri, password), sip_uri, 'presence.winfo', route=route, expires=expires) winfo = WatcherInfo() if xcap_root is not None: xcap_client = XCAPClient(xcap_root, '%s@%s' % (sip_uri.user, sip_uri.host), password=password, auth=None) print 'Retrieving current presence rules from %s' % xcap_root get_prules() print 'Allowed list:' if allow_rule_identities is not None: for identity in allow_rule_identities: print '\t%s' % identity print 'Blocked list:' if block_rule_identities is not None: for identity in block_rule_identities: print '\t%s' % identity print 'Polite-blocked list:' if polite_block_rule_identities is not None: for identity in polite_block_rule_identities: print '\t%s' % identity print 'Subscribing to "%s@%s" for the presence.winfo event, at %s:%d' % (sip_uri.user, sip_uri.host, route.host, route.port) sub.subscribe() while True: command, data = queue.get() if command == "print": print data if len(pending) > 0: print "%s watcher %s wants to subscribe to your presence information. Press (a) for allow, (d) for deny or (p) for polite blocking:" % (pending[0].status.capitalize(), pending[0]) if command == "pypjua_event": event_name, args = data if command == "user_input": key = data if len(pending) > 0: if key == 'a': watcher = pending.popleft() allow_watcher(watcher) elif key == 'd': watcher = pending.popleft() block_watcher(watcher) elif key == 'p': watcher = pending.popleft() polite_block_watcher(watcher) else: print "Please select a valid choice. Press (a) to allow, (d) to deny, (p) to polite block" if len(pending) > 0: print "%s watcher %s wants to subscribe to your presence information. Press (a) for allow, (d) for deny or (p) for polite blocking:" % (pending[0].status.capitalize(), pending[0]) if command == "eof": command = "end" want_quit = True if command == "end": try: sub.unsubscribe() except: pass if command == "quit": user_quit = False break except: user_quit = False traceback.print_exc() finally: e.stop() logger.stop() if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_subscribe(**kwargs): global user_quit, lock, queue, do_trace_pjsip, logger ctrl_d_pressed = False do_trace_pjsip = kwargs["do_trace_pjsip"] outbound_proxy = kwargs.pop("outbound_proxy") if outbound_proxy is None: routes = lookup_routes_for_sip_uri(SIPURI(host=kwargs["domain"]), kwargs.pop("sip_transports")) else: routes = lookup_routes_for_sip_uri(outbound_proxy, kwargs.pop("sip_transports")) # Only try the first Route for now try: kwargs["route"] = routes[0] except IndexError: raise RuntimeError("No route found to SIP proxy") logger = Logger(AccountConfig, GeneralConfig.log_directory, trace_sip=kwargs['trace_sip']) if kwargs['trace_sip']: print "Logging SIP trace to file '%s'" % logger._siptrace_filename e = Engine(event_handler, trace_sip=kwargs.pop('trace_sip'), local_ip=kwargs.pop("local_ip"), local_udp_port=kwargs.pop("local_udp_port"), local_tcp_port=kwargs.pop("local_tcp_port"), local_tls_port=kwargs.pop("local_tls_port")) e.start(False) start_new_thread(read_queue, (e,), kwargs) atexit.register(termios_restore) try: while True: char = getchar() if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_outbound_proxy(option, opt_str, value, parser): try: parser.values.outbound_proxy = OutboundProxy(value) except ValueError, e: raise OptionValueError(e.message) def parse_options(): retval = {} description = "This script displays the current presence rules, SUBSCRIBEs to the presence.winfo event of itself and prompts the user to update the presence rules document when a new watcher is in 'pending'/'waiting' state. The program will un-SUBSCRIBE and quit when CTRL+D is pressed." usage = "%prog [options]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-n", "--display-name", type="string", dest="display_name", help="Display name to use for the local account. This overrides the setting from the config file.") parser.add_option("-e", "--expires", type="int", dest="expires", help='"Expires" value to set in SUBSCRIBE. Default is 300 seconds.') parser.add_option("-o", "--outbound-proxy", type="string", action="callback", callback=parse_outbound_proxy, help="Outbound SIP proxy to use. By default a lookup of the domain is performed based on SRV and A records. This overrides the setting from the config file.", metavar="IP[:PORT]") parser.add_option("-x", "--xcap-root", type="string", dest="xcap_root", help = 'The XCAP root to use to access the pres-rules document for authorizing subscriptions to presence.') parser.add_option("-s", "--trace-sip", action="store_true", dest="trace_sip", help="Dump the raw contents of incoming and outgoing SIP messages (disabled by default).") parser.add_option("-j", "--trace-pjsip", action="store_true", dest="do_trace_pjsip", help="Print PJSIP logging output (disabled by default).") options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) if not AccountConfig.use_presence_agent: raise RuntimeError("Presence is not enabled for this account. Please set use_presence_agent=True in the config file") default_options = dict(expires=300, outbound_proxy=AccountConfig.outbound_proxy, sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, trace_sip=GeneralConfig.trace_sip, do_trace_pjsip=GeneralConfig.trace_pjsip, xcap_root=AccountConfig.xcap_root, local_ip=GeneralConfig.local_ip, local_udp_port=GeneralConfig.sip_local_udp_port, local_tcp_port=GeneralConfig.sip_local_tcp_port, local_tls_port=GeneralConfig.sip_local_tls_port, sip_transports=GeneralConfig.sip_transports) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) for transport in set(["tls", "tcp", "udp"]) - set(options.sip_transports): setattr(options, "local_%s_port" % transport, None) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_subscribe(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) except PyPJUAError, e: print "Error: %s" % str(e) sys.exit(1) sys.exit(return_code) diff --git a/scripts/xcap_pres_rules.py b/scripts/xcap_pres_rules.py index 8f06f690..89819de6 100644 --- a/scripts/xcap_pres_rules.py +++ b/scripts/xcap_pres_rules.py @@ -1,462 +1,463 @@ #!/usr/bin/env python import sys import traceback import string import socket import os import atexit import select import termios import signal from collections import deque from thread import start_new_thread, allocate_lock from threading import Thread, Event from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep from application.process import process from application.configuration import * from urllib2 import URLError from pypjua import * from pypjua.clients import enrollment from pypjua.applications import ParserError from pypjua.applications.watcherinfo import * from pypjua.applications.policy import * from pypjua.applications.presrules import * from pypjua.clients.clientconfig import get_path -from pypjua.clients.lookup import * +from pypjua.clients.dns_lookup import * +from pypjua.clients import * from xcaplib.client import XCAPClient from xcaplib.error import HTTPError class Boolean(int): def __new__(typ, value): if value.lower() == 'true': return True else: return False class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "xcap_root": str, "use_presence_agent": Boolean} sip_address = None password = None display_name = None xcap_root = None use_presence_agent = True process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") queue = Queue() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() string = None getstr_event = Event() show_xml = False sip_uri = None xcap_client = None prules = None prules_etag = None allow_rule = None allow_rule_identities = None block_rule = None block_rule_identities = None polite_block_rule = None polite_block_rule_identities = None def get_prules(): global prules, prules_etag, allow_rule, block_rule, allow_rule_identities, block_rule_identities prules = None prules_etag = None allow_rule = None allow_rule_identities = None block_rule = None block_rule_identities = None try: doc = xcap_client.get('pres-rules') except URLError, e: print "Cannot obtain 'pres-rules' document: %s" % str(e) except HTTPError, e: if e.response.status != 404: print "Cannot obtain 'pres-rules' document: %s %s" % (e.response.status, e.response.reason) else: prules = PresRules() else: try: prules = PresRules.parse(doc) except ParserError, e: print "Invalid 'pres-rules' document: %s" % str(e) else: prules_etag = doc.etag # find each rule type for rule in prules: if rule.actions is not None: for action in rule.actions: if isinstance(action, SubHandling): if action == 'allow': if rule.conditions is not None: for condition in rule.conditions: if isinstance(condition, Identity): allow_rule = rule allow_rule_identities = condition break elif action == 'block': if rule.conditions is not None: for condition in rule.conditions: if isinstance(condition, Identity): block_rule = rule block_rule_identities = condition break elif action == 'polite-block': if rule.conditions is not None: for condition in rule.conditions: if isinstance(condition, Identity): polite_block_rule = rule polite_block_rule_identities = condition break break def allow_watcher(watcher): global prules, prules_etag, allow_rule, allow_rule_identities, show_xml for i in xrange(3): if prules is None: get_prules() if prules is not None: if allow_rule is None: allow_rule_identities = Identity() allow_rule = Rule('pres_whitelist', conditions=Conditions([allow_rule_identities]), actions=Actions([SubHandling('allow')]), transformations=Transformations([ProvideServices([AllServices()]), ProvidePersons([AllPersons()]), ProvideDevices([AllDevices()]), ProvideAllAttributes()])) prules.append(allow_rule) if str(watcher) not in allow_rule_identities: allow_rule_identities.append(IdentityOne(str(watcher))) try: res = xcap_client.put('pres-rules', prules.toxml(pretty_print=True), etag=prules_etag) except HTTPError, e: print "Cannot PUT 'pres-rules' document: %s" % str(e) prules = None else: prules_etag = res.etag if show_xml: print "Presence rules document:" print prules.toxml(pretty_print=True) print "Watcher %s is now authorized" % watcher break sleep(0.1) else: print "Could not authorized watcher %s" % watcher def block_watcher(watcher): global prules, prules_etag, block_rule, block_rule_identities for i in xrange(3): if prules is None: get_prules() if prules is not None: if block_rule is None: block_rule_identities = Identity() block_rule = Rule('pres_blacklist', conditions=Conditions([block_rule_identities]), actions=Actions([SubHandling('block')]), transformations=Transformations()) prules.append(block_rule) if str(watcher) not in block_rule_identities: block_rule_identities.append(IdentityOne(str(watcher))) try: res = xcap_client.put('pres-rules', prules.toxml(pretty_print=True), etag=prules_etag) except HTTPError, e: print "Cannot PUT 'pres-rules' document: %s" % str(e) prules = None else: prules_etag = res.etag if show_xml: print "Presence rules document:" print prules.toxml(pretty_print=True) print "Watcher %s is now denied authorization" % watcher break sleep(0.1) else: print "Could not deny authorization of watcher %s" % watcher def polite_block_watcher(watcher): global prules, prules_etag, polite_block_rule, polite_block_rule_identities for i in xrange(3): if prules is None: get_prules() if prules is not None: if polite_block_rule is None: polite_block_rule_identities = Identity() polite_block_rule = Rule('pres_polite_blacklist', conditions=Conditions([polite_block_rule_identities]), actions=Actions([SubHandling('polite-block')]), transformations=Transformations()) prules.append(polite_block_rule) if str(watcher) not in polite_block_rule_identities: polite_block_rule_identities.append(IdentityOne(str(watcher))) try: res = xcap_client.put('pres-rules', prules.toxml(pretty_print=True), etag=prules_etag) except HTTPError, e: print "Cannot PUT 'pres-rules' document: %s" % str(e) prules = None else: prules_etag = res.etag if show_xml: print "Presence rules document:" print prules.toxml(pretty_print=True) print "Watcher %s is now politely blocked" % watcher break sleep(0.1) else: print "Could not politely block authorization of watcher %s" % watcher def remove_watcher(watcher): global prules, prules_etag, allow_rule_identities, block_rule_identities, polite_block_rule_identities for i in xrange(3): if prules is None: get_prules() if prules is not None: if allow_rule_identities is not None and str(watcher) in allow_rule_identities: allow_rule_identities.remove(str(watcher)) if len(allow_rule_identities) == 0: prules.remove(allow_rule) if block_rule_identities is not None and str(watcher) in block_rule_identities: block_rule_identities.remove(str(watcher)) if len(block_rule_identities) == 0: prules.remove(block_rule) if polite_block_rule_identities is not None and str(watcher) in polite_block_rule_identities: polite_block_rule_identities.remove(str(watcher)) if len(polite_block_rule_identities) == 0: prules.remove(polite_block_rule) try: res = xcap_client.put('pres-rules', prules.toxml(pretty_print=True), etag=prules_etag) except HTTPError, e: print "Cannot PUT 'pres-rules' document: %s" % str(e) prules = None else: prules_etag = res.etag if show_xml: print "Presence rules document:" print prules.toxml(pretty_print=True) print "Watcher %s has been removed from the rules" % watcher break sleep(0.1) else: print "Could not politely block authorization of watcher %s" % watcher def print_prules(): global allow_rule_identities, block_rule_identities, polite_block_rule_identities print 'Allowed watchers:' if allow_rule_identities is not None: for identity in allow_rule_identities: print '\t%s' % str(identity).replace('sip:', '') print 'Blocked watchers:' if block_rule_identities is not None: for identity in block_rule_identities: print '\t%s' % str(identity).replace('sip:', '') print 'Polite-blocked watchers:' if polite_block_rule_identities is not None: for identity in polite_block_rule_identities: print '\t%s' % str(identity).replace('sip:', '') print "Press (a) to allow, (d) to deny, (p) to politely block a new watcher or (r) to remove a watcher from the rules. (s) will show the presence rules xml." def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(10) finally: termios_restore() else: return os.read(fd, 10) def getstr(prompt='selection'): global string, getstr_event string = '' sys.stdout.write("%s> " % prompt) sys.stdout.flush() getstr_event.wait() getstr_event.clear() sys.stdout.write("\n") ret = string string = None return ret def read_queue(username, domain, password, display_name, xcap_root): global user_quit, lock, queue, sip_uri, xcap_client lock.acquire() try: sip_uri = SIPURI(user=username, host=domain, display=display_name) if xcap_root is not None: xcap_client = XCAPClient(xcap_root, '%s@%s' % (sip_uri.user, sip_uri.host), password=password, auth=None) print 'Retrieving current presence rules from %s' % xcap_root get_prules() if show_xml and prules is not None: print "Presence rules document:" print prules.toxml(pretty_print=True) print_prules() while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if command == "user_input": key = data if key == 'a': watcher = getstr('watcher') if watcher != '': watcher = 'sip:' + watcher allow_watcher(watcher) elif key == 'd': watcher = getstr('watcher') if watcher != '': watcher = 'sip:' + watcher block_watcher(watcher) elif key == 'p': watcher = getstr('watcher') if watcher != '': watcher = 'sip:' + watcher polite_block_watcher(watcher) elif key == 'r': watcher = getstr('watcher') if watcher != '': watcher = 'sip:' + watcher remove_watcher(watcher) elif key == 's': if prules is not None: print "Presence rules document:" print prules.toxml(pretty_print=True) print_prules() if command == "eof": command = "end" want_quit = True if command == "quit" or command == "end": user_quit = False break except: user_quit = False traceback.print_exc() finally: if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_xcap_pres_rules(**kwargs): global user_quit, lock, queue, string, getstr_event, old, show_xml ctrl_d_pressed = False show_xml = kwargs.pop('show_xml') start_new_thread(read_queue,(), kwargs) atexit.register(termios_restore) try: while True: char = getchar() if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True else: if string is not None: if char == "\x7f": if len(string) > 0: char = "\x08" sys.stdout.write("\x08 \x08") sys.stdout.flush() string = string[:-1] else: if old is not None: sys.stdout.write(char) sys.stdout.flush() if char == "\x0A": getstr_event.set() else: string += char else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_options(): retval = {} description = "This example script will use the specified SIP account to manage presence rules via XCAP. The program will quit when CTRL+D is pressed." usage = "%prog [options]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-x", "--xcap-root", type="string", dest="xcap_root", help = 'The XCAP root to use to access the pres-rules document for authorizing subscriptions to presence.') parser.add_option("-s", "--show-xml", action="store_true", dest="show_xml", help = 'Show the presence rules XML whenever it is changed and at start-up.') options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) if not AccountConfig.use_presence_agent: raise RuntimeError("Presence is not enabled for this account. Please set use_presence_agent=True in the config file") default_options = dict(sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, xcap_root=AccountConfig.xcap_root, show_xml=False) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) return retval def main(): do_xcap_pres_rules(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1) diff --git a/scripts/xcap_rls_services.py b/scripts/xcap_rls_services.py index 04d43b79..e163f9cd 100644 --- a/scripts/xcap_rls_services.py +++ b/scripts/xcap_rls_services.py @@ -1,398 +1,399 @@ #!/usr/bin/env python import sys import traceback import string import socket import os import atexit import select import termios import signal from collections import deque from thread import start_new_thread, allocate_lock from threading import Thread, Event from Queue import Queue from optparse import OptionParser, OptionValueError from time import sleep from application.process import process from application.configuration import * from urllib2 import URLError from pypjua import * from pypjua.clients import enrollment from pypjua.applications import ParserError from pypjua.applications.resourcelists import * from pypjua.applications.rlsservices import * from pypjua.clients.clientconfig import get_path -from pypjua.clients.lookup import * +from pypjua.clients.dns_lookup import * +from pypjua.clients import * from xcaplib.client import XCAPClient from xcaplib.error import HTTPError class Boolean(int): def __new__(typ, value): if value.lower() == 'true': return True else: return False class AccountConfig(ConfigSection): _datatypes = {"sip_address": str, "password": str, "display_name": str, "xcap_root": str, "use_presence_agent": Boolean} sip_address = None password = None display_name = None xcap_root = None use_presence_agent = True process._system_config_directory = os.path.expanduser("~/.sipclient") enrollment.verify_account_config() configuration = ConfigFile("config.ini") queue = Queue() packet_count = 0 start_time = None old = None user_quit = True lock = allocate_lock() string = None getstr_event = Event() show_xml = False sip_uri = None xcap_client = None service_uri = None rls_services = None buddy_service = None def get_rls_services(): global rls_services, rls_services_etag, buddy_service rls_services = None rls_services_etag = None buddy_service = None try: doc = xcap_client.get('rls-services') except URLError, e: print "Cannot obtain 'rls-services' document: %s" % str(e) except HTTPError, e: if e.response.status != 404: print "Cannot obtain 'rls-services' document: %s %s" % (e.response.status, e.response.reason) else: buddy_service = Service(service_uri, list=RLSList(), packages=['presence']) rls_services = RLSServices([buddy_service]) else: try: rls_services = RLSServices.parse(doc) except ParserError, e: print "Invalid 'rls-services' document: %s" % str(e) else: rls_services_etag = doc.etag if service_uri in rls_services: buddy_service = rls_services[service_uri] if not isinstance(buddy_service.list, RLSList): raise RuntimeError("service element `%s' must contain a `list' element, not a `resource-list' element in order to be managed" % service_uri) else: buddy_service = Service(service_uri, list=RLSList(), packages=['presence']) rls_services.append(buddy_service) def add_buddy(buddy): global rls_services, rls_services_etag, buddy_service, show_xml for i in xrange(3): if rls_services is None: get_rls_services() if rls_services is not None: if buddy not in buddy_service.list: buddy_service.list.append(Entry(buddy)) try: res = xcap_client.put('rls-services', rls_services.toxml(pretty_print=True), etag=rls_services_etag) except HTTPError, e: print "Cannot PUT 'rls-services' document: %s" % str(e) rls_services = None else: rls_services_etag = res.etag if show_xml: print "RLS Services document:" print rls_services.toxml(pretty_print=True) print "Buddy %s has been added" % buddy break else: print "Buddy %s already in list" % buddy break sleep(0.1) else: print "Could not add buddy %s" % buddy def remove_buddy(buddy): global rls_services, rls_services_etag, buddy_service, show_xml for i in xrange(3): if rls_services is None: get_rls_services() if rls_services is not None: if buddy in buddy_service.list: buddy_service.list.remove(buddy) try: res = xcap_client.put('rls-services', rls_services.toxml(pretty_print=True), etag=rls_services_etag) except HTTPError, e: print "Cannot PUT 'rls-services' document: %s" % str(e) rls_services = None else: rls_services_etag = res.etag if show_xml: print "RLS Services document:" print rls_services.toxml(pretty_print=True) print "Buddy %s has been removed" % buddy break else: print "No such buddy: %s" % buddy break sleep(0.1) else: print "Could not remove buddy %s" % buddy def delete_service(): global rls_services, rls_services_etag, buddy_service, show_xml for i in xrange(3): if rls_services is None: get_rls_services() if rls_services is not None: if buddy_service.uri in rls_services: rls_services.remove(buddy_service.uri) try: res = xcap_client.put('rls-services', rls_services.toxml(pretty_print=True), etag=rls_services_etag) except HTTPError, e: print "Cannot PUT 'rls-services' document: %s" % str(e) rls_services = None else: rls_services_etag = res.etag if show_xml: print "RLS Services document:" print rls_services.toxml(pretty_print=True) print "Service %s has been removed" % buddy_service.uri queue.put(("quit", None)) break else: print "No such service: %s" % buddy_service.uri queue.put(("quit", None)) break sleep(0.1) else: print "Could not delete service %s" % buddy_service.uri def print_rls_services(): global rls_services, rls_services_etag, buddy_service, show_xml print '\nBuddies:' for buddy in buddy_service.list: print '\t%s' % str(buddy).replace('sip:', '') print "Press (a) to add or (r) to remove a buddy. (s) will show the RLS services xml. (d) will delete the currently selected service." def termios_restore(): global old if old is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) def getchar(): global old fd = sys.stdin.fileno() if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~termios.ICANON & ~termios.ECHO new[6][termios.VMIN] = '\000' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) if select.select([fd], [], [], None)[0]: return sys.stdin.read(4192) finally: termios_restore() else: return os.read(fd, 4192) def getstr(prompt='selection'): global string, getstr_event string = '' sys.stdout.write("%s> " % prompt) sys.stdout.flush() getstr_event.wait() getstr_event.clear() sys.stdout.write("\n") ret = string string = None return ret def read_queue(username, domain, password, display_name, xcap_root): global user_quit, lock, queue, sip_uri, xcap_client, service_uri lock.acquire() try: sip_uri = SIPURI(user=username, host=domain, display=display_name) if xcap_root is not None: xcap_client = XCAPClient(xcap_root, '%s@%s' % (sip_uri.user, sip_uri.host), password=password, auth=None) print 'Retrieving current RLS services from %s' % xcap_root get_rls_services() if show_xml and rls_services is not None: print "RLS services document:" print rls_services.toxml(pretty_print=True) print 'Managing service URI %s' % service_uri print_rls_services() while True: command, data = queue.get() if command == "print": print data if command == "pypjua_event": event_name, args = data if command == "user_input": key = data if key == 'a': buddy = getstr('new buddy') if buddy != '': if '@' not in buddy: buddy = 'sip:%s@%s' % (buddy, domain) else: buddy = 'sip:%s' % buddy add_buddy(buddy) elif key == 'r': buddy = getstr('buddy to delete') if buddy != '': if '@' not in buddy: buddy = 'sip:%s@%s' % (buddy, domain) else: buddy = 'sip:%s' % buddy remove_buddy(buddy) elif key == 's': if rls_services is not None: print "RLS services document:" print rls_services.toxml(pretty_print=True) elif key == 'd': delete_service() if key != 'd': print_rls_services() if command == "eof": command = "end" want_quit = True if command == "quit" or command == "end": user_quit = False break except: user_quit = False traceback.print_exc() finally: if not user_quit: os.kill(os.getpid(), signal.SIGINT) lock.release() def do_xcap_rls_services(**kwargs): global user_quit, lock, queue, string, getstr_event, old, show_xml ctrl_d_pressed = False show_xml = kwargs.pop('show_xml') start_new_thread(read_queue,(), kwargs) atexit.register(termios_restore) try: while True: for char in getchar(): if char == "\x04": if not ctrl_d_pressed: queue.put(("eof", None)) ctrl_d_pressed = True break else: if string is not None: if char == "\x7f": if len(string) > 0: char = "\x08" sys.stdout.write("\x08 \x08") sys.stdout.flush() string = string[:-1] else: if old is not None: sys.stdout.write(char) sys.stdout.flush() if char == "\x0A": getstr_event.set() break else: string += char else: queue.put(("user_input", char)) except KeyboardInterrupt: if user_quit: print "Ctrl+C pressed, exiting instantly!" queue.put(("quit", True)) lock.acquire() return def parse_options(): global service_uri retval = {} description = "This example script will use the specified SIP account to manage rls services via XCAP. The program will quit when CTRL+D is pressed. You can specify the service URI as an argument (if domain name is not specified, the user's domain name will be used). If it is not specified, it defaults to username-buddies@domain." usage = "%prog [options] [service URI]" parser = OptionParser(usage=usage, description=description) parser.print_usage = parser.print_help parser.add_option("-a", "--account-name", type="string", dest="account_name", help="The account name from which to read account settings. Corresponds to section Account_NAME in the configuration file. If not supplied, the section Account will be read.", metavar="NAME") parser.add_option("--sip-address", type="string", dest="sip_address", help="SIP address of the user in the form user@domain") parser.add_option("-p", "--password", type="string", dest="password", help="Password to use to authenticate the local account. This overrides the setting from the config file.") parser.add_option("-x", "--xcap-root", type="string", dest="xcap_root", help = 'The XCAP root to use to access the rls-services document to manage.') parser.add_option("-s", "--show-xml", action="store_true", dest="show_xml", help = 'Show the RLS services XML whenever it is changed and at start-up.') options, args = parser.parse_args() if options.account_name is None: account_section = "Account" else: account_section = "Account_%s" % options.account_name if account_section not in configuration.parser.sections(): raise RuntimeError("There is no account section named '%s' in the configuration file" % account_section) configuration.read_settings(account_section, AccountConfig) if not AccountConfig.use_presence_agent: raise RuntimeError("Presence is not enabled for this account. Please set use_presence_agent=True in the config file") default_options = dict(sip_address=AccountConfig.sip_address, password=AccountConfig.password, display_name=AccountConfig.display_name, xcap_root=AccountConfig.xcap_root, show_xml=False) options._update_loose(dict((name, value) for name, value in default_options.items() if getattr(options, name, None) is None)) if not all([options.sip_address, options.password]): raise RuntimeError("No complete set of SIP credentials specified in config file and on commandline.") for attr in default_options: retval[attr] = getattr(options, attr) try: retval["username"], retval["domain"] = options.sip_address.split("@") except ValueError: raise RuntimeError("Invalid value for sip_address: %s" % options.sip_address) else: del retval["sip_address"] accounts = [(acc == 'Account') and 'default' or "'%s'" % acc[8:] for acc in configuration.parser.sections() if acc.startswith('Account')] accounts.sort() print "Accounts available: %s" % ', '.join(accounts) if options.account_name is None: print "Using default account: %s" % options.sip_address else: print "Using account '%s': %s" % (options.account_name, options.sip_address) if len(args) > 0: if '@' not in args[0]: service_uri = 'sip:%s@%s' % (args[0], retval["domain"]) else: service_uri = 'sip:%s' % args[0] else: service_uri = 'sip:%s-buddies@%s' % (retval["username"], retval["domain"]) return retval def main(): do_xcap_rls_services(**parse_options()) if __name__ == "__main__": try: main() except RuntimeError, e: print "Error: %s" % str(e) sys.exit(1)