class JitsiPlugin(StandardPlugin): IS_PLUGIN = True _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) root_resource = Resource() root_resource.putChild('jitsi', JitsiHTTPService(self._tftpboot_dir)) self.http_service = root_resource http_dev_info_extractor = JitsiHTTPDeviceInfoExtractor() pg_associator = JitsiPgAssociator() def _device_config_filename(self, device): # Return the device specific filename (not pathname) of device return device[u'uuid'] def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'uuid' not in device: raise Exception('UUID needed for device configuration') if not is_normed_uuid(device[u'uuid']): # non normalized uuid can lead to security issue raise Exception('non normalized UUID: %s', device[u'uuid']) def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._device_config_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._device_config_filename(device)) try: os.remove(path) except OSError, e: logger.warning('error while deconfiguring device: %s', e)
class BaseCiscoSipPlugin(StandardPlugin): """Base classes MUST have a '_COMMON_FILENAMES' attribute which is a sequence of filenames that will be generated by the common template in the common_configure function. """ _ENCODING = 'UTF-8' _NB_FKEY = { # <model>: (<nb keys>, <nb expansion modules>) u'ATA191': (0, 0), u'ATA192': (0, 0), } _DEFAULT_LOCALE = u'en_US' _LANGUAGE = { u'de_DE': u'German', u'en_US': u'English', u'es_ES': u'Spanish', u'fr_FR': u'French', u'fr_CA': u'French', } _LOCALE = { u'de_DE': u'de-DE', u'en_US': u'en-US', u'es_ES': u'es-ES', u'fr_FR': u'fr-FR', u'fr_CA': u'fr-CA', } _DIRECTORY_NAME = { u'en_US': u'Wazo Directory', u'fr_FR': u'Répertoire Wazo', } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self._tpl_helper = TemplatePluginHelper(plugin_dir) self.http_service = HTTPNoListingFileService(self._tftpboot_dir) self.tftp_service = TFTPFileService(self._tftpboot_dir) dhcp_dev_info_extractor = BaseCiscoDHCPDeviceInfoExtractor() http_dev_info_extractor = BaseCiscoHTTPDeviceInfoExtractor() tftp_dev_info_extractor = BaseCiscoTFTPDeviceInfoExtractor() def configure_common(self, raw_config): tpl = self._tpl_helper.get_template('common/model.cfg.tpl') common_filenames = self._COMMON_FILENAMES for filename in common_filenames: dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _add_fkeys(self, raw_config, model): if model not in self._NB_FKEY: logger.info(u'Unknown model or model with no funckeys: %s', model) return nb_keys, nb_expmods = self._NB_FKEY[model] lines = [] for funckey_no, funckey_dict in sorted( raw_config[u'funckeys'].iteritems(), key=itemgetter(0)): funckey_type = funckey_dict[u'type'] value = funckey_dict[u'value'] label = escape(funckey_dict.get(u'label', value)) if funckey_type == u'speeddial': function = u'fnc=sd;ext=%s@$PROXY;nme=%s' % (value, label) elif funckey_type == u'blf': function = u'fnc=sd+blf+cp;sub=%s@$PROXY;nme=%s' % (value, label) else: logger.info('Unsupported funckey type: %s', funckey_type) continue keynum = int(funckey_no) if keynum <= nb_keys: lines.append(u'<Extension_%s_>Disabled</Extension_%s_>' % (funckey_no, funckey_no)) lines.append(u'<Short_Name_%s_>%s</Short_Name_%s_>' % (funckey_no, label, funckey_no)) lines.append( u'<Extended_Function_%s_>%s</Extended_Function_%s_>' % (funckey_no, function, funckey_no)) else: expmod_keynum = keynum - nb_keys - 1 expmod_no = expmod_keynum // 32 + 1 if expmod_no > nb_expmods: logger.info('Model %s has less than %s function keys', model, funckey_no) else: expmod_key_no = expmod_keynum % 32 + 1 lines.append(u'<Unit_%s_Key_%s>%s</Unit_%s_Key_%s>' % (expmod_no, expmod_key_no, function, expmod_no, expmod_key_no)) raw_config[u'XX_fkeys'] = u'\n'.join(lines) def _format_dst_change(self, dst_change): _day = dst_change['day'] if _day.startswith('D'): day = _day[1:] weekday = '0' else: week, weekday = _day[1:].split('.') weekday = tzinform.week_start_on_monday(int(weekday)) if week == '5': day = '-1' else: day = (int(week) - 1) * 7 + 1 h, m, s = dst_change['time'].as_hms return u'%s/%s/%s/%s:%s:%s' % (dst_change['month'], day, weekday, h, m, s) def _format_tzinfo(self, tzinfo): lines = [] hours, minutes = tzinfo['utcoffset'].as_hms[:2] lines.append(u'<Time_Zone>GMT%+03d:%02d</Time_Zone>' % (hours, minutes)) if tzinfo['dst'] is None: lines.append( u'<Daylight_Saving_Time_Enable>no</Daylight_Saving_Time_Enable>' ) else: lines.append( u'<Daylight_Saving_Time_Enable>yes</Daylight_Saving_Time_Enable>' ) h, m, s = tzinfo['dst']['save'].as_hms lines.append( u'<Daylight_Saving_Time_Rule>start=%s;end=%s;save=%d:%d:%s</Daylight_Saving_Time_Rule>' % ( self._format_dst_change(tzinfo['dst']['start']), self._format_dst_change(tzinfo['dst']['end']), h, m, s, )) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.info('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._format_tzinfo(tzinfo)
class BaseGigasetPlugin(StandardPlugin): _ENCODING = 'UTF-8' _TZ_GIGASET = { (-12, 0): 0x00, (-11, 0): 0x01, (-10, 0): 0x02, (-9, 0): 0x03, (-8, 0): 0x04, (-7, 0): 0x07, (-6, 0): 0x09, (-5, 0): 0x0d, (-4, 0): 0x10, (-3, 0): 0x12, (-2, 0): 0x16, (-1, 0): 0x18, (0, 0): 0x1a, (+1, 0): 0x1b, (+2, 0): 0x20, (+3, 0): 0x28, (+4, 0): 0x2c, (+4, 30): 0x2d, (+5, 0): 0x2f, (+5, 30): 0x30, (+5, 45): 0x31, (+6, 00): 0x33, (+6, 30): 0x35, (+7, 0): 0x36, (+8, 0): 0x38, (+9, 0): 0x3d, (+9, 30): 0x40, (+10, 0): 0x43, (+11, 0): 0x47, (+12, 0): 0x48, (+13, 0): 0x50, } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._app = app self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPServiceWrapper(self._tftpboot_dir) dhcp_dev_info_extractor = GigasetDHCPDeviceInfoExtractor() http_dev_info_extractor = GigasetHTTPDeviceInfoExtractor() def _check_device(self, device): if u'ip' not in device: raise Exception('IP address needed for Gigaset configuration') def _check_config(self, raw_config): pass def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=True) return fmted_mac + '.xml' def _add_phonebook(self, raw_config): uuid_format = u'{scheme}://{hostname}:{port}/0.1/directories/lookup/default/gigaset/{user_uuid}?' plugins.add_xivo_phonebook_url_from_format(raw_config, uuid_format) def _add_timezone_code(self, raw_config): timezone = raw_config.get(u'timezone', 'Etc/UTC') tz_db = tzinform.TextTimezoneInfoDB() tz_info = tz_db.get_timezone_info(timezone)['utcoffset'].as_hms offset_hour = tz_info[0] offset_minutes = tz_info[1] raw_config[u'XX_timezone_code'] = self._TZ_GIGASET[(offset_hour, offset_minutes)] def _add_xx_vars(self, device, raw_config): raw_config[u'XX_mac_addr'] = format_mac(device[u'mac'], separator='', uppercase=True) cur_datetime = datetime.datetime.now() raw_config[u'XX_version_date'] = cur_datetime.strftime('%d%m%y%H%M') if u'dns_enabled' in raw_config: ip = raw_config[u'dns_ip'] ip_str = '0x' + ''.join(['%x' % int(p) for p in ip.split('.')]) raw_config[u'XX_dns_ip_hex'] = ip_str self._add_timezone_code(raw_config) def _add_sip_info(self, raw_config): if u'1' in raw_config[u'sip_lines']: line = raw_config[u'sip_lines'][u'1'] raw_config[u'sip_proxy_ip'] = line[u'proxy_ip'] raw_config[u'sip_proxy_port'] = line.get(u'proxy_port', 5060) raw_config[u'sip_registrar_ip'] = line.get(u'registrar_ip') raw_config[u'sip_registrar_port'] = line.get(u'registrar_port', 5060) def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) self._add_sip_info(raw_config) self._add_xx_vars(device, raw_config) self._add_phonebook(raw_config) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) def is_sensitive_filename(self, filename): return bool(self._SENSITIVE_FILENAME_REGEX.match(filename)) _SENSITIVE_FILENAME_REGEX = re.compile(r'^[0-9A-F]{12}\.xml$') def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device)
class BasePanasonicPlugin(StandardPlugin): _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Panasonic') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) http_dev_info_extractor = BasePanasonicHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=True) return 'Config' + fmted_mac + '.cfg' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def _common_templates(self): for tpl_format, file_format in [('common/%s.tpl', '%s.cfg'),]: for model in self._MODELS: yield tpl_format % model, file_format % model def configure_common(self, raw_config): for tpl_filename, filename in self._common_templates(): tpl = self._tpl_helper.get_template(tpl_filename) dst = os.path.join(self._base_tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail(Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail(Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device)
class BaseSnomPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { u'de_DE': (u'Deutsch', u'GER'), u'en_US': (u'English', u'USA'), u'es_ES': (u'Espanol', u'ESP'), u'fr_FR': (u'Francais', u'FRA'), u'fr_CA': (u'Francais', u'USA'), u'it_IT': (u'Italiano', u'ITA'), u'nl_NL': (u'Dutch', u'NLD'), } _SIP_DTMF_MODE = { u'RTP-in-band': u'off', u'RTP-out-of-band': u'off', u'SIP-INFO': u'sip_info_only' } _XX_DICT_DEF = u'en' _XX_DICT = { u'en': { u'remote_directory': u'Directory', }, u'fr': { u'remote_directory': u'Annuaire', }, } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseSnomHTTPDeviceInfoExtractor() def _common_templates(self): yield ('common/gui_lang.xml.tpl', 'gui_lang.xml') yield ('common/web_lang.xml.tpl', 'web_lang.xml') for tpl_format, file_format in [('common/snom%s.htm.tpl', 'snom%s.htm'), ('common/snom%s.xml.tpl', 'snom%s.xml'), ('common/snom%s-firmware.xml.tpl', 'snom%s-firmware.xml')]: for model in self._MODELS: yield tpl_format % model, file_format % model def configure_common(self, raw_config): for tpl_filename, filename in self._common_templates(): tpl = self._tpl_helper.get_template(tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): proxy_ip = raw_config.get(u'sip_proxy_ip') backup_proxy_ip = raw_config.get(u'sip_backup_proxy_ip') voicemail = raw_config.get(u'exten_voicemail') for line in raw_config[u'sip_lines'].itervalues(): if proxy_ip: line.setdefault(u'proxy_ip', proxy_ip) if backup_proxy_ip: line.setdefault(u'backup_proxy_ip', backup_proxy_ip) if voicemail: line.setdefault(u'voicemail', voicemail) def _get_fkey_domain(self, raw_config): # Return None if there's no usable domain if u'sip_proxy_ip' in raw_config: return raw_config[u'sip_proxy_ip'] else: lines = raw_config[u'sip_lines'] if lines: return lines[min(lines.iterkeys())][u'proxy_ip'] return None def _add_fkeys(self, raw_config, model): domain = self._get_fkey_domain(raw_config) if domain is None: if raw_config[u'funckeys']: logger.warning('Could not set funckeys: no domain part') else: lines = [] for funckey_no, funckey_dict in sorted(raw_config[u'funckeys'].iteritems(), key=itemgetter(0)): funckey_type = funckey_dict[u'type'] if funckey_type == u'speeddial': type_ = u'speed' suffix = '' elif funckey_type == u'park': if model in ['710', '720', '715', '760']: type_ = u'orbit' suffix = '' else: type_ = u'speed' suffix = '' elif funckey_type == u'blf': if u'exten_pickup_call' in raw_config: type_ = u'blf' suffix = '|%s' % raw_config[u'exten_pickup_call'] else: logger.warning('Could not set funckey %s: no exten_pickup_call', funckey_no) continue else: logger.info('Unsupported funckey type: %s', funckey_type) continue value = funckey_dict[u'value'] label = escape(funckey_dict.get(u'label', value)) fkey_value = self._format_fkey_value(type_, value, domain, suffix) lines.append(u'<fkey idx="%d" label="%s" context="active" perm="R">%s</fkey>' % (int(funckey_no) - 1, label, fkey_value)) raw_config[u'XX_fkeys'] = u'\n'.join(lines) def _format_fkey_value(self, fkey_type, value, domain, suffix): return '%s <sip:%s@%s>%s' % (fkey_type, value, domain, suffix) def _add_lang(self, raw_config): if u'locale' in raw_config: locale = raw_config[u'locale'] if locale in self._LOCALE: raw_config[u'XX_lang'] = self._LOCALE[locale] def _format_dst_change(self, dst_change): fmted_time = u'%02d:%02d:%02d' % tuple(dst_change['time'].as_hms) day = dst_change['day'] if day.startswith('D'): return u'%02d.%02d %s' % (int(day[1:]), dst_change['month'], fmted_time) else: week, weekday = map(int, day[1:].split('.')) weekday = tzinform.week_start_on_monday(weekday) return u'%02d.%02d.%02d %s' % (dst_change['month'], week, weekday, fmted_time) def _format_tzinfo(self, tzinfo): lines = [] lines.append(u'<timezone perm="R"></timezone>') lines.append(u'<utc_offset perm="R">%+d</utc_offset>' % tzinfo['utcoffset'].as_seconds) if tzinfo['dst'] is None: lines.append(u'<dst perm="R"></dst>') else: lines.append(u'<dst perm="R">%d %s %s</dst>' % (tzinfo['dst']['save'].as_seconds, self._format_dst_change(tzinfo['dst']['start']), self._format_dst_change(tzinfo['dst']['end']))) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.warning('Unknown timezone %s: %s', raw_config[u'timezone'], e) else: raw_config[u'XX_timezone'] = self._format_tzinfo(tzinfo)
class BaseCiscoSipPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { # <locale>: (<name>, <lang code>, <network locale>) u'de_DE': (u'german_germany', u'de', u'germany'), u'en_US': (u'english_united_states', u'en', u'united_states'), u'es_ES': (u'spanish_spain', u'es', u'spain'), u'fr_FR': (u'french_france', u'fr', u'france'), u'fr_CA': (u'french_france', u'fr', u'canada') } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) handlers = FetchfwPluginHelper.new_handlers(gen_cfg.get('proxies')) downloaders = FetchfwPluginHelper.new_downloaders_from_handlers(handlers) cisco_dler = common['CiscoDownloader'](handlers) downloaders['x-cisco'] = cisco_dler fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) cfg_service = common['CiscoConfigureService'](cisco_dler, spec_cfg.get('username'), spec_cfg.get('password')) persister = JsonConfigPersister(os.path.join(self._plugin_dir, 'var', 'config.json')) cfg_service = PersistentConfigureServiceDecorator(cfg_service, persister) self.services = {'configure': cfg_service, 'install': fetchfw_helper} self.tftp_service = TFTPFileService(self._tftpboot_dir) dhcp_dev_info_extractor = common['BaseCiscoDHCPDeviceInfoExtractor']() tftp_dev_info_extractor = common['BaseCiscoTFTPDeviceInfoExtractor']() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=True) return 'SEP%s.cfg.xml' % fmted_mac def _check_config(self, raw_config): if u'tftp_port' not in raw_config: raise RawConfigError('only support configuration via TFTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError, e: # ignore logger.info('error while removing file: %s', e)
class BaseCiscoPlugin(StandardPlugin): """Base classes MUST have a '_COMMON_FILENAMES' attribute which is a sequence of filenames that will be generated by the common template in the common_configure function. """ _ENCODING = 'UTF-8' _NB_FKEY = { # <model>: (<nb keys>, <nb expansion modules>) u'SPA941': (4, 0), u'SPA942': (4, 0), u'SPA962': (6, 2), u'SPA303': (3, 2), u'SPA501G': (8, 2), u'SPA502G': (0, 2), u'SPA504G': (4, 2), u'SPA508G': (8, 2), u'SPA509G': (12, 2), u'SPA512G': (0, 2), u'SPA514G': (4, 2), u'SPA525G': (5, 2), u'SPA525G2': (5, 2) } _DEFAULT_LOCALE = u'en_US' _LANGUAGE = { u'de_DE': u'German', u'en_US': u'English', u'es_ES': u'Spanish', u'fr_FR': u'French', u'fr_CA': u'French', } _LOCALE = { u'de_DE': u'de-DE', u'en_US': u'en-US', u'es_ES': u'es-ES', u'fr_FR': u'fr-FR', u'fr_CA': u'fr-CA', } _DIRECTORY_NAME = { u'en_US': u'Wazo Directory', u'fr_FR': u'Répertoire Wazo', } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) if 'cisco' not in downloaders: logger.warning('cisco downloader not found (xivo is probably not up to date); not loading plugin packages') else: fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) self.tftp_service = TFTPFileService(self._tftpboot_dir) dhcp_dev_info_extractor = BaseCiscoDHCPDeviceInfoExtractor() http_dev_info_extractor = BaseCiscoHTTPDeviceInfoExtractor() tftp_dev_info_extractor = BaseCiscoTFTPDeviceInfoExtractor() def configure_common(self, raw_config): tpl = self._tpl_helper.get_template('common/model.cfg.tpl') common_filenames = self._COMMON_FILENAMES for filename in common_filenames: dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _add_fkeys(self, raw_config, model): if model not in self._NB_FKEY: logger.info(u'Unknown model or model with no funckeys: %s', model) return nb_keys, nb_expmods = self._NB_FKEY[model] lines = [] for funckey_no, funckey_dict in sorted(raw_config[u'funckeys'].iteritems(), key=itemgetter(0)): funckey_type = funckey_dict[u'type'] value = funckey_dict[u'value'] label = escape(funckey_dict.get(u'label', value)) if funckey_type == u'speeddial': function = u'fnc=sd;ext=%s@$PROXY;nme=%s' % (value, label) elif funckey_type == u'blf': function = u'fnc=sd+blf+cp;sub=%s@$PROXY;nme=%s' % (value, label) else: logger.info('Unsupported funckey type: %s', funckey_type) continue keynum = int(funckey_no) if keynum <= nb_keys: lines.append(u'<Extension_%s_>Disabled</Extension_%s_>' % (funckey_no, funckey_no)) lines.append(u'<Short_Name_%s_>%s</Short_Name_%s_>' % (funckey_no, label, funckey_no)) lines.append(u'<Extended_Function_%s_>%s</Extended_Function_%s_>' % (funckey_no, function, funckey_no)) else: expmod_keynum = keynum - nb_keys - 1 expmod_no = expmod_keynum // 32 + 1 if expmod_no > nb_expmods: logger.info('Model %s has less than %s function keys', model, funckey_no) else: expmod_key_no = expmod_keynum % 32 + 1 lines.append(u'<Unit_%s_Key_%s>%s</Unit_%s_Key_%s>' % (expmod_no, expmod_key_no, function, expmod_no, expmod_key_no)) raw_config[u'XX_fkeys'] = u'\n'.join(lines) def _format_dst_change(self, dst_change): _day = dst_change['day'] if _day.startswith('D'): day = _day[1:] weekday = '0' else: week, weekday = _day[1:].split('.') weekday = tzinform.week_start_on_monday(int(weekday)) if week == '5': day = '-1' else: day = (int(week) - 1) * 7 + 1 h, m, s = dst_change['time'].as_hms return u'%s/%s/%s/%s:%s:%s' % (dst_change['month'], day, weekday, h, m, s) def _format_tzinfo(self, tzinfo): lines = [] hours, minutes = tzinfo['utcoffset'].as_hms[:2] lines.append(u'<Time_Zone>GMT%+03d:%02d</Time_Zone>' % (hours, minutes)) if tzinfo['dst'] is None: lines.append(u'<Daylight_Saving_Time_Enable>no</Daylight_Saving_Time_Enable>') else: lines.append(u'<Daylight_Saving_Time_Enable>yes</Daylight_Saving_Time_Enable>') h, m, s = tzinfo['dst']['save'].as_hms lines.append(u'<Daylight_Saving_Time_Rule>start=%s;end=%s;save=%d:%d:%s</Daylight_Saving_Time_Rule>' % (self._format_dst_change(tzinfo['dst']['start']), self._format_dst_change(tzinfo['dst']['end']), h, m, s, )) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.info('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._format_tzinfo(tzinfo)
class BasePattonPlugin(StandardPlugin): _ENCODING = 'ascii' _SIP_DTMF_MODE = { u'RTP-in-band': u'default', u'RTP-out-of-band': u'rtp', u'SIP-INFO': u'signaling' } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BasePattonHTTPDeviceInfoExtractor() def _add_syslog_level(self, raw_config): if u'syslog_level' in raw_config: if raw_config[u'syslog_level'] == u'info': raw_config[u'XX_syslog_level'] = u'informational' else: raw_config[u'XX_syslog_level'] = raw_config[u'syslog_level'] def _add_timezone_and_dst(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError as e: logger.info('Unknown timezone: %s', e) else: converter = _TimezoneConverter(tzinfo) raw_config[u'XX_timezone_offset'] = converter.default_offset() if converter.has_dst(): raw_config[u'XX_dst_offset'] = converter.dst_offset() raw_config[u'XX_dst_start'] = converter.dst_start() raw_config[u'XX_dst_end'] = converter.dst_end() def _update_sip_transport(self, raw_config): if u'sip_transport' not in raw_config: raw_config[u'sip_transport'] = u'udp' elif raw_config.get(u'sip_transport') == u'tls': logger.warning("Patton doesn't support the SIP transport tls: fallback to udp") raw_config[u'sip_transport'] = u'udp' def _add_dtmf_relay(self, raw_config): if u'sip_dtmf_mode' in raw_config: raw_config[u'XX_dtmf_relay'] = self._SIP_DTMF_MODE[raw_config[u'sip_dtmf_mode']] def _add_lines_and_servers(self, raw_config): converter = _SIPLinesConverter() for sip_line_no, sip_line in raw_config[u'sip_lines'].iteritems(): converter.add_sip_line(sip_line_no, sip_line) raw_config[u'XX_lines'] = converter.lines() raw_config[u'XX_servers'] = converter.servers() _SENSITIVE_FILENAME_REGEX = re.compile(r'^[0-9a-f]{12}\.cfg$') def _dev_specific_filename(self, device): fmted_mac = format_mac(device[u'mac'], separator='') return fmted_mac + '.cfg' def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) self._add_syslog_level(raw_config) self._add_timezone_and_dst(raw_config) self._update_sip_transport(raw_config) self._add_dtmf_relay(raw_config) self._add_lines_and_servers(raw_config) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING, errors='ignore') def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail(Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail(Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def is_sensitive_filename(self, filename): return bool(self._SENSITIVE_FILENAME_REGEX.match(filename))
class BasePanasonicPlugin(StandardPlugin): _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Panasonic') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) http_dev_info_extractor = BasePanasonicHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=True) return 'Config' + fmted_mac + '.cfg' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def _common_templates(self): for tpl_format, file_format in [ ('common/%s.tpl', '%s.cfg'), ]: for model in self._MODELS: yield tpl_format % model, file_format % model def configure_common(self, raw_config): for tpl_filename, filename in self._common_templates(): tpl = self._tpl_helper.get_template(tpl_filename) dst = os.path.join(self._base_tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device)
class BaseSnomPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { u'de_DE': (u'Deutsch', u'GER'), u'en_US': (u'English', u'USA'), u'es_ES': (u'Espanol', u'ESP'), u'fr_FR': (u'Francais', u'FRA'), u'fr_CA': (u'Francais', u'USA'), u'it_IT': (u'Italiano', u'ITA'), u'nl_NL': (u'Dutch', u'NLD'), } _SIP_DTMF_MODE = { u'RTP-in-band': u'off', u'RTP-out-of-band': u'off', u'SIP-INFO': u'sip_info_only' } _XX_DICT_DEF = u'en' _XX_DICT = { u'en': { u'remote_directory': u'Directory', }, u'fr': { u'remote_directory': u'Annuaire', }, } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseSnomDECTHTTPDeviceInfoExtractor() def _common_templates(self): yield ('common/snom-general.xml.tpl', 'snom-general.xml') for tpl_format, file_format in [ ('common/snom%s.htm.tpl', 'snom%s.htm'), ('common/snom%s.xml.tpl', 'snom%s.xml'), ('common/snom%s-firmware.xml.tpl', 'snom%s-firmware.xml') ]: for model in self._MODELS: yield tpl_format % model, file_format % model def configure_common(self, raw_config): for tpl_filename, filename in self._common_templates(): tpl = self._tpl_helper.get_template(tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): proxy_ip = raw_config.get(u'sip_proxy_ip') backup_proxy_ip = raw_config.get(u'sip_backup_proxy_ip') voicemail = raw_config.get(u'exten_voicemail') for line in raw_config[u'sip_lines'].itervalues(): if proxy_ip: line.setdefault(u'proxy_ip', proxy_ip) if backup_proxy_ip: line.setdefault(u'backup_proxy_ip', backup_proxy_ip) if voicemail: line.setdefault(u'voicemail', voicemail) # set SIP server to use server_id = raw_config['XX_servers'].get( (line.get(u'proxy_ip'), line.get(u'proxy_port', 5060)), {}).get('id') line[u'XX_server_id'] = server_id or 1 def _add_sip_servers(self, raw_config): servers = dict() server_number = 1 for line_no, line in raw_config[u'sip_lines'].iteritems(): proxy_ip = line.get(u'proxy_ip') or raw_config.get(u'sip_proxy_ip') proxy_port = line.get(u'proxy_port') or raw_config.get( u'sip_proxy_port') backup_proxy_ip = line.get(u'backup_proxy_ip') or raw_config.get( u'sip_backup_proxy_ip') backup_proxy_port = line.get( u'backup_proxy_port') or raw_config.get( u'sip_backup_proxy_port') dtmf_mode = self._SIP_DTMF_MODE.get( line.get(u'dtmf_mode') or raw_config.get(u'sip_dtmf_mode'), 'off', ) if (proxy_ip, proxy_port) not in servers: servers[(proxy_ip, proxy_port)] = { u'id': server_number, u'proxy_ip': proxy_ip, u'proxy_port': proxy_port, u'backup_proxy_ip': backup_proxy_ip, u'backup_proxy_port': backup_proxy_port, u'dtmf_mode': dtmf_mode, } server_number += 1 if server_number > 10: logger.warning('Maximum number of valid server reached') raw_config[u'XX_servers'] = servers def _format_fkey_value(self, fkey_type, value, suffix): return '%s %s%s' % (fkey_type, value, suffix) def _add_lang(self, raw_config): if u'locale' in raw_config: locale = raw_config[u'locale'] if locale in self._LOCALE: raw_config[u'XX_lang'] = self._LOCALE[locale] def _add_user_dtmf_info(self, raw_config): dtmf_mode = raw_config.get(u'sip_dtmf_mode') for line in raw_config[u'sip_lines'].itervalues(): cur_dtmf_mode = line.get(u'dtmf_mode', dtmf_mode) line[u'XX_user_dtmf_info'] = self._SIP_DTMF_MODE.get( cur_dtmf_mode, u'off') def _add_xivo_phonebook_url(self, raw_config): if hasattr(plugins, 'add_xivo_phonebook_url') and raw_config.get( u'config_version', 0) >= 1: plugins.add_xivo_phonebook_url(raw_config, u'snom') else: self._add_xivo_phonebook_url_compat(raw_config) def _add_xivo_phonebook_url_compat(self, raw_config): hostname = raw_config.get(u'X_xivo_phonebook_ip') if hostname: raw_config[ u'XX_xivo_phonebook_url'] = u'http://{hostname}/service/ipbx/web_services.php/phonebook/search/'.format( hostname=hostname) def _gen_xx_dict(self, raw_config): xx_dict = self._XX_DICT[self._XX_DICT_DEF] if u'locale' in raw_config: locale = raw_config[u'locale'] lang = locale.split('_', 1)[0] if lang in self._XX_DICT: xx_dict = self._XX_DICT[lang] return xx_dict _SENSITIVE_FILENAME_REGEX = re.compile(r'^[0-9A-F]{12}\.xml') def _dev_specific_filenames(self, device): # Return a tuple (htm filename, xml filename) fmted_mac = format_mac(device[u'mac'], separator='', uppercase=True) return 'snom%s-%s.htm' % (device[u'model'], fmted_mac), fmted_mac + '.xml' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') # model is needed since filename has model name in it. if u'model' not in device: raise Exception('model needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) htm_filename, xml_filename = self._dev_specific_filenames(device) # generate xml file tpl = self._tpl_helper.get_dev_template(xml_filename, device) self._add_sip_servers(raw_config) self._update_sip_lines(raw_config) self._add_lang(raw_config) self._add_xivo_phonebook_url(raw_config) raw_config[u'XX_dict'] = self._gen_xx_dict(raw_config) raw_config[u'XX_options'] = device.get(u'options', {}) path = os.path.join(self._tftpboot_dir, xml_filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) # generate htm file tpl = self._tpl_helper.get_template('other/base.htm.tpl') raw_config[u'XX_xml_filename'] = xml_filename path = os.path.join(self._tftpboot_dir, htm_filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): for filename in self._dev_specific_filenames(device): try: os.remove(os.path.join(self._tftpboot_dir, filename)) except OSError, e: # ignore logger.info('error while removing file: %s', e)
class BaseGrandstreamPlugin(StandardPlugin): _ENCODING = 'UTF-8' DTMF_MODES = { # mode: (in audio, in RTP, in SIP) u'RTP-in-band': ('Yes', 'Yes', 'No'), u'RTP-out-of-band': ('No', 'Yes', 'No'), u'SIP-INFO': ('No', 'No', 'Yes'), } SIP_TRANSPORTS = { u'udp': u'UDP', u'tcp': u'TCP', u'tls': u'TlsOrTcp', } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseGrandstreamHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return 'cfg' + fmted_mac + '.xml' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._check_lines_password(raw_config) self._add_sip_transport(raw_config) self._add_timezone(raw_config) self._add_locale(raw_config) self._add_dtmf_mode(raw_config) self._add_dns(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older wazo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' in device: return self._dev_specific_filename(device) def _check_lines_password(self, raw_config): for line in raw_config[u'sip_lines'].itervalues(): if line[u'password'] == u'autoprov': line[u'password'] = u'' def _add_timezone(self, raw_config): if u'timezone' in raw_config and raw_config[u'timezone'] in TZ_NAME: raw_config[u'XX_timezone'] = TZ_NAME[raw_config[u'timezone']] else: raw_config['timezone'] = TZ_NAME['Europe/Paris'] def _add_locale(self, raw_config): locale = raw_config.get(u'locale') if locale in LOCALE: raw_config[u'XX_locale'] = LOCALE[locale] def _add_dns(self, raw_config): if raw_config.get(u'dns_enabled'): dns_parts = raw_config[u'dns_ip'].split('.') for part_nb, part in enumerate(dns_parts, start=1): raw_config[u'XX_dns_%s' % part_nb] = part def _add_dtmf_mode(self, raw_config): if raw_config.get(u'sip_dtmf_mode'): dtmf_info = self.DTMF_MODES[raw_config[u'sip_dtmf_mode']] raw_config['XX_dtmf_in_audio'] = dtmf_info[0] raw_config['XX_dtmf_in_rtp'] = dtmf_info[1] raw_config['XX_dtmf_in_sip'] = dtmf_info[2] def _add_sip_transport(self, raw_config): sip_transport = raw_config.get(u'sip_transport') if sip_transport in self.SIP_TRANSPORTS: raw_config[u'XX_sip_transport'] = self.SIP_TRANSPORTS[ sip_transport]
class BaseZenitelPlugin(StandardPlugin): _ENCODING = 'UTF-8' _VALID_FUNCKEY_NO = [u'1', u'2', u'3'] def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) cfg_service = ZenitelConfigureService(downloaders['auth'], spec_cfg.get('username'), spec_cfg.get('password')) persister = JsonConfigPersister( os.path.join(self._plugin_dir, 'var', 'config.json')) cfg_service = PersistentConfigureServiceDecorator( cfg_service, persister) self.services = {'configure': cfg_service, 'install': fetchfw_helper} self.tftp_service = TFTPFileService(self._tftpboot_dir) tftp_dev_info_extractor = BaseZenitelTFTPDeviceInfoExtractor() pg_associator = BaseZenitelPgAssociator() def _add_sip_section_info(self, raw_config): if u'1' in raw_config[u'sip_lines']: line = raw_config[u'sip_lines'][u'1'] raw_config[u'XX_sip'] = True raw_config[u'XX_nick_name'] = line[u'display_name'] raw_config[u'XX_sip_id'] = line[u'username'] raw_config[u'XX_domain'] = line.get( u'proxy_ip') or raw_config[u'sip_proxy_ip'] raw_config[u'XX_domain2'] = line.get(u'backup_proxy_ip') or \ raw_config.get(u'backup_proxy_ip', u'') raw_config[u'XX_auth_user'] = line[u'auth_username'] raw_config[u'XX_auth_pwd'] = line[u'password'] def _add_fkeys(self, raw_config): lines = [] for funckey_no, funckey_dict in sorted( raw_config[u'funckeys'].iteritems(), key=itemgetter(0)): if funckey_no in self._VALID_FUNCKEY_NO: if funckey_dict[u'type'] == u'speeddial': exten = funckey_dict[u'value'] lines.append(u'speeddial_%s_c1=%s' % (funckey_no, exten)) else: logger.info('Unsupported funckey type: %s', funckey_dict[u'type']) else: logger.info('Out of range funckey no: %s', funckey_no) raw_config[u'XX_fkeys'] = u'\n'.join(' ' + s for s in lines) def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='_', uppercase=False) return 'ipst_config_%s.cfg' % fmted_mac def _check_config(self, raw_config): if u'tftp_port' not in raw_config: raise RawConfigError('only support configuration via TFTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) self._add_sip_section_info(raw_config) self._add_fkeys(raw_config) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError, e: # ignore logger.info('error while removing file: %s', e)
class BaseGrandstreamPlugin(StandardPlugin): _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Grandstream') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) http_dev_info_extractor = BaseGrandstreamHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return 'cfg' + fmted_mac + '.xml' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._check_lines_password(raw_config) self._add_timezone(raw_config) self._add_locale(raw_config) self._add_fkeys(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def _check_lines_password(self, raw_config): for line in raw_config[u'sip_lines'].itervalues(): if line[u'password'] == u'autoprov': line[u'password'] = u'' def _add_timezone(self, raw_config): if u'timezone' in raw_config and raw_config[u'timezone'] in TZ_NAME: raw_config[u'XX_timezone'] = TZ_NAME[raw_config[u'timezone']] else: raw_config['timezone'] = TZ_NAME['Europe/Paris'] def _add_locale(self, raw_config): locale = raw_config.get(u'locale') if locale in LOCALE: raw_config[u'XX_locale'] = LOCALE[locale] def _add_fkeys(self, raw_config): lines = [] for funckey_no, funckey_dict in raw_config[u'funckeys'].iteritems(): i_funckey_no = int(funckey_no) funckey_type = funckey_dict[u'type'] if funckey_type not in FUNCKEY_TYPES: logger.info('Unsupported funckey type: %s', funckey_type) continue type_code = u'P32%s' % (i_funckey_no + 2) lines.append( self._format_line(type_code, FUNCKEY_TYPES[funckey_type])) line_code = self._format_code(3 * i_funckey_no - 2) lines.append( self._format_line(line_code, int(funckey_dict[u'line']) - 1)) if u'label' in funckey_dict: label_code = self._format_code(3 * i_funckey_no - 1) lines.append( self._format_line(label_code, funckey_dict[u'label'])) value_code = self._format_code(3 * i_funckey_no) lines.append(self._format_line(value_code, funckey_dict[u'value'])) raw_config[u'XX_fkeys'] = u'\n'.join(lines) def _format_line(self, code, value): return u' <%s>%s</%s>' % (code, value, code) def _format_code(self, code): if code >= 10: str_code = str(code) else: str_code = u'0%s' % code return u'P3%s' % str_code
class BaseGrandstreamPlugin(StandardPlugin): _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Grandstream') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) http_dev_info_extractor = BaseGrandstreamHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return 'cfg' + fmted_mac + '.xml' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._check_lines_password(raw_config) self._add_timezone(raw_config) self._add_locale(raw_config) self._add_fkeys(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail(Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail(Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def _check_lines_password(self, raw_config): for line in raw_config[u'sip_lines'].itervalues(): if line[u'password'] == u'autoprov': line[u'password'] = u'' def _add_timezone(self, raw_config): if u'timezone' in raw_config and raw_config[u'timezone'] in TZ_NAME: raw_config[u'XX_timezone'] = TZ_NAME[raw_config[u'timezone']] else: raw_config['timezone'] = TZ_NAME['Europe/Paris'] def _add_locale(self, raw_config): locale = raw_config.get(u'locale') if locale in LOCALE: raw_config[u'XX_locale'] = LOCALE[locale] def _add_fkeys(self, raw_config): lines = [] for funckey_no, funckey_dict in raw_config[u'funckeys'].iteritems(): i_funckey_no = int(funckey_no) funckey_type = funckey_dict[u'type'] if funckey_type not in FUNCKEY_TYPES: logger.info('Unsupported funckey type: %s', funckey_type) continue type_code = u'P32%s' % (i_funckey_no + 2) lines.append(self._format_line(type_code, FUNCKEY_TYPES[funckey_type])) line_code = self._format_code(3*i_funckey_no - 2) lines.append(self._format_line(line_code, int(funckey_dict[u'line']) - 1)) if u'label' in funckey_dict : label_code = self._format_code(3*i_funckey_no - 1) lines.append(self._format_line(label_code, funckey_dict[u'label'])) value_code = self._format_code(3*i_funckey_no) lines.append(self._format_line(value_code, funckey_dict[u'value'])) raw_config[u'XX_fkeys'] = u'\n'.join(lines) def _format_line(self, code, value): return u' <%s>%s</%s>' % (code, value, code) def _format_code(self, code): if code >= 10: str_code = str(code) else: str_code = u'0%s' % code return u'P3%s' % str_code
class BaseYealinkPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { u'de_DE': (u'German', u'Germany', u'2'), u'en_US': (u'English', u'United States', u'0'), u'es_ES': (u'Spanish', u'Spain', u'6'), u'fr_FR': (u'French', u'France', u'1'), u'fr_CA': (u'French', u'United States', u'1'), } _SIP_DTMF_MODE = { u'RTP-in-band': u'0', u'RTP-out-of-band': u'1', u'SIP-INFO': u'2', } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseYealinkHTTPDeviceInfoExtractor() def configure_common(self, raw_config): for filename, fw_filename, tpl_filename in self._COMMON_FILES: tpl = self._tpl_helper.get_template('common/%s' % tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) raw_config[u'XX_fw_filename'] = fw_filename self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): for line_no, line in raw_config['sip_lines'].iteritems(): # set line number line[u'XX_line_no'] = int(line_no) # set dtmf inband transfer dtmf_mode = line.get(u'dtmf_mode') or raw_config.get(u'sip_dtmf_mode') if dtmf_mode in self._SIP_DTMF_MODE: line[u'XX_dtmf_type'] = self._SIP_DTMF_MODE[dtmf_mode] # set voicemail if u'voicemail' not in line and u'exten_voicemail' in raw_config: line[u'voicemail'] = raw_config[u'exten_voicemail'] # set proxy_ip if u'proxy_ip' not in line: line[u'proxy_ip'] = raw_config[u'sip_proxy_ip'] # set proxy_port if u'proxy_port' not in line and u'sip_proxy_port' in raw_config: line[u'proxy_port'] = raw_config[u'sip_proxy_port'] def _format_funckey_speeddial(self, funckey_no, funckey_dict): lines = [] lines.append(u'memorykey.%s.line = %s' % (funckey_no, funckey_dict.get(u'line', 1))) lines.append(u'memorykey.%s.value = %s' % (funckey_no, funckey_dict[u'value'])) lines.append(u'memorykey.%s.type = 13' % funckey_no) lines.append(u'memorykey.%s.label = %s' % (funckey_no, funckey_dict.get(u'label', u''))) return lines def _format_funckey_call_park(self, funckey_no, funckey_dict): lines = [] lines.append(u'memorykey.%s.line = %s' % (funckey_no, funckey_dict.get(u'line', 1))) lines.append(u'memorykey.%s.value = %s' % (funckey_no, funckey_dict[u'value'])) lines.append(u'memorykey.%s.type = 10' % funckey_no) lines.append(u'memorykey.%s.label = %s' % (funckey_no, funckey_dict.get(u'label', u''))) return lines def _format_funckey_blf(self, funckey_no, funckey_dict, exten_pickup_call=None): # Be warned that blf works only for DSS keys. lines = [] lines.append(u'memorykey.%s.line = %s' % (funckey_no, funckey_dict.get(u'line', 1) - 1)) value = funckey_dict[u'value'] lines.append(u'memorykey.%s.value = %s' % (funckey_no, value)) lines.append(u'memorykey.%s.label = %s' % (funckey_no, funckey_dict.get(u'label', u''))) lines.append(u'memorykey.%s.sub_type = blf' % funckey_no) if exten_pickup_call: lines.append(u'memorykey.%s.pickup_value = %s' % (funckey_no, exten_pickup_call)) lines.append(u'memorykey.%s.type = 16' % funckey_no) return lines def _add_fkeys(self, raw_config, device): # XXX maybe rework this, a bit ugly lines = [] exten_pickup_call = raw_config.get('exten_pickup_call') for funckey_no, funckey_dict in sorted(raw_config[u'funckeys'].iteritems(), key=itemgetter(0)): keynum = int(funckey_no) funckey_type = funckey_dict[u'type'] if funckey_type == u'speeddial': lines.extend(self._format_funckey_speeddial(funckey_no, funckey_dict)) elif funckey_type == u'blf': if keynum <= 10: lines.extend(self._format_funckey_blf(funckey_no, funckey_dict, exten_pickup_call)) else: logger.info('For Yealink, blf is only available on DSS keys') lines.extend(self._format_funckey_speeddial(funckey_no, funckey_dict)) elif funckey_type == u'park': lines.extend(self._format_funckey_call_park(funckey_no, funckey_dict)) else: logger.info('Unsupported funckey type: %s', funckey_type) continue lines.append(u'') raw_config[u'XX_fkeys'] = u'\n'.join(lines) def _add_country_and_lang(self, raw_config): locale = raw_config.get(u'locale') if locale in self._LOCALE: (raw_config[u'XX_lang'], raw_config[u'XX_country'], raw_config[u'XX_handset_lang']) = self._LOCALE[locale] def _format_dst_change(self, dst_change): if dst_change['day'].startswith('D'): return u'%02d/%02d/%02d' % (dst_change['month'], dst_change['day'][1:], dst_change['time'].as_hour) else: week, weekday = map(int, dst_change['day'][1:].split('.')) weekday = tzinform.week_start_on_monday(weekday) return u'%d/%d/%d/%d' % (dst_change['month'], week, weekday, dst_change['time'].as_hours) def _format_tz_info(self, tzinfo): lines = [] lines.append(u'local_time.time_zone = %+d' % min(max(tzinfo['utcoffset'].as_hours, -11), 12)) if tzinfo['dst'] is None: lines.append(u'local_time.summer_time = 0') else: lines.append(u'local_time.summer_time = 1') if tzinfo['dst']['start']['day'].startswith('D'): lines.append(u'local_time.dst_time_type = 0') else: lines.append(u'local_time.dst_time_type = 1') lines.append(u'local_time.start_time = %s' % self._format_dst_change(tzinfo['dst']['start'])) lines.append(u'local_time.end_time = %s' % self._format_dst_change(tzinfo['dst']['end'])) lines.append(u'local_time.offset_time = %s' % tzinfo['dst']['save'].as_minutes) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.warning('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._format_tz_info(tzinfo)
class BaseFanvilPlugin(StandardPlugin): _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Fanvil') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) http_dev_info_extractor = BaseFanvilHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return fmted_mac + '.cfg' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._check_lines_password(raw_config) self._add_timezone(raw_config) self._add_locale(raw_config) self._add_fkeys(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def configure_common(self, raw_config): for filename, fw_filename, tpl_filename in self._COMMON_FILES: tpl = self._tpl_helper.get_template('common/%s' % tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) raw_config[u'XX_fw_filename'] = fw_filename self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail(Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail(Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def _check_lines_password(self, raw_config): for line in raw_config[u'sip_lines'].itervalues(): if line[u'password'] == u'autoprov': line[u'password'] = u'' def _format_dst_change(self, suffix, dst_change): lines = [] lines.append(u'<DST_%s_Mon>%d</DST_%s_Mon>' % (suffix, dst_change['month'], suffix)) lines.append(u'<DST_%s_Hour>%d</DST_%s_Hour>' % (suffix, min(dst_change['time'].as_hours, 23), suffix)) if dst_change['day'].startswith('D'): lines.append(u'<DST_%s_Wday>%s</DST_%s_Wday>' % (suffix, dst_change['day'][1:], suffix)) else: week, weekday = dst_change['day'][1:].split('.') if week == '5': lines.append(u'<DST_%s_Week>-1</DST_%s_Week>' % (suffix, suffix)) else: lines.append(u'<DST_%s_Week>%s</DST_%s_Week>' % (suffix, week, suffix)) lines.append(u'<DST_%s_Wday>%s</DST_%s_Wday>' % (suffix, weekday, suffix)) lines.append(u'<DST_%s_Min>0</DST_%s_Min>' % (suffix, suffix)) return lines def _format_tzinfo(self, tzinfo): lines = [] utc = tzinfo['utcoffset'].as_hours utc_list = TZ_INFO[utc] for time_zone_name, time_zone in utc_list: lines.append(u'<Time_Zone>%s</Time_Zone>' % (time_zone)) lines.append(u'<Time_Zone_Name>%s</Time_Zone_Name>' % (time_zone_name)) if tzinfo['dst'] is None: lines.append(u'<Enable_DST>0</Enable_DST>') else: lines.append(u'<Enable_DST>2</Enable_DST>') lines.append(u'<DST_Min_Offset>%d</DST_Min_Offset>' % (min(tzinfo['dst']['save'].as_minutes, 60))) lines.extend(self._format_dst_change('Start', tzinfo['dst']['start'])) lines.extend(self._format_dst_change('End', tzinfo['dst']['end'])) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.info('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._format_tzinfo(tzinfo)
class BasePattonPlugin(StandardPlugin): _ENCODING = 'ascii' _SIP_DTMF_MODE = { u'RTP-in-band': u'default', u'RTP-out-of-band': u'rtp', u'SIP-INFO': u'signaling' } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BasePattonHTTPDeviceInfoExtractor() def _add_syslog_level(self, raw_config): if u'syslog_level' in raw_config: if raw_config[u'syslog_level'] == u'info': raw_config[u'XX_syslog_level'] = u'informational' else: raw_config[u'XX_syslog_level'] = raw_config[u'syslog_level'] def _add_timezone_and_dst(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError as e: logger.info('Unknown timezone: %s', e) else: converter = _TimezoneConverter(tzinfo) raw_config[u'XX_timezone_offset'] = converter.default_offset() if converter.has_dst(): raw_config[u'XX_dst_offset'] = converter.dst_offset() raw_config[u'XX_dst_start'] = converter.dst_start() raw_config[u'XX_dst_end'] = converter.dst_end() def _update_sip_transport(self, raw_config): if u'sip_transport' not in raw_config: raw_config[u'sip_transport'] = u'udp' elif raw_config.get(u'sip_transport') == u'tls': logger.warning( "Patton doesn't support the SIP transport tls: fallback to udp" ) raw_config[u'sip_transport'] = u'udp' def _add_dtmf_relay(self, raw_config): if u'sip_dtmf_mode' in raw_config: raw_config[u'XX_dtmf_relay'] = self._SIP_DTMF_MODE[ raw_config[u'sip_dtmf_mode']] def _add_lines_and_servers(self, raw_config): converter = _SIPLinesConverter() for sip_line_no, sip_line in raw_config[u'sip_lines'].iteritems(): converter.add_sip_line(sip_line_no, sip_line) raw_config[u'XX_lines'] = converter.lines() raw_config[u'XX_servers'] = converter.servers() _SENSITIVE_FILENAME_REGEX = re.compile(r'^[0-9a-f]{12}\.cfg$') def _dev_specific_filename(self, device): fmted_mac = format_mac(device[u'mac'], separator='') return fmted_mac + '.cfg' def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) self._add_syslog_level(raw_config) self._add_timezone_and_dst(raw_config) self._update_sip_transport(raw_config) self._add_dtmf_relay(raw_config) self._add_lines_and_servers(raw_config) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING, errors='ignore') def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older wazo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def is_sensitive_filename(self, filename): return bool(self._SENSITIVE_FILENAME_REGEX.match(filename))
class BaseSnomPlugin(StandardPlugin): _ENCODING = "UTF-8" _LOCALE = { u"de_DE": (u"Deutsch", u"GER"), u"en_US": (u"English", u"USA"), u"es_ES": (u"Espanol", u"ESP"), u"fr_FR": (u"Francais", u"FRA"), u"fr_CA": (u"Francais", u"USA"), u"it_IT": (u"Italiano", u"ITA"), u"nl_NL": (u"Dutch", u"NLD"), } _SIP_DTMF_MODE = {u"RTP-in-band": u"off", u"RTP-out-of-band": u"off", u"SIP-INFO": u"sip_info_only"} _XX_DICT_DEF = u"en" _XX_DICT = {u"en": {u"remote_directory": u"Directory"}, u"fr": {u"remote_directory": u"Annuaire"}} def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get("proxies")) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseSnomHTTPDeviceInfoExtractor() def _common_templates(self): yield ("common/gui_lang.xml.tpl", "gui_lang.xml") yield ("common/web_lang.xml.tpl", "web_lang.xml") for tpl_format, file_format in [ ("common/snom%s.htm.tpl", "snom%s.htm"), ("common/snom%s.xml.tpl", "snom%s.xml"), ("common/snom%s-firmware.xml.tpl", "snom%s-firmware.xml"), ]: for model in self._MODELS: yield tpl_format % model, file_format % model def configure_common(self, raw_config): for tpl_filename, filename in self._common_templates(): tpl = self._tpl_helper.get_template(tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): proxy_ip = raw_config.get(u"sip_proxy_ip") backup_proxy_ip = raw_config.get(u"sip_backup_proxy_ip") voicemail = raw_config.get(u"exten_voicemail") for line in raw_config[u"sip_lines"].itervalues(): if proxy_ip: line.setdefault(u"proxy_ip", proxy_ip) if backup_proxy_ip: line.setdefault(u"backup_proxy_ip", backup_proxy_ip) if voicemail: line.setdefault(u"voicemail", voicemail) def _add_fkeys(self, raw_config, model): lines = [] for funckey_no, funckey_dict in sorted(raw_config[u"funckeys"].iteritems(), key=itemgetter(0)): funckey_type = funckey_dict[u"type"] if funckey_type == u"speeddial": type_ = u"speed" suffix = "" elif funckey_type == u"park": if model in [u"710", u"715", u"720", u"725", u"760", u"D765"]: type_ = u"orbit" suffix = "" else: type_ = u"speed" suffix = "" elif funckey_type == u"blf": if u"exten_pickup_call" in raw_config: type_ = u"blf" suffix = "|%s" % raw_config[u"exten_pickup_call"] else: logger.warning("Could not set funckey %s: no exten_pickup_call", funckey_no) continue else: logger.info("Unsupported funckey type: %s", funckey_type) continue value = funckey_dict[u"value"] label = escape(funckey_dict.get(u"label", value)) fkey_value = self._format_fkey_value(type_, value, suffix) lines.append( u'<fkey idx="%d" label="%s" context="active" perm="R">%s</fkey>' % (int(funckey_no) - 1, label, fkey_value) ) raw_config[u"XX_fkeys"] = u"\n".join(lines) def _format_fkey_value(self, fkey_type, value, suffix): return "%s %s%s" % (fkey_type, value, suffix) def _add_lang(self, raw_config): if u"locale" in raw_config: locale = raw_config[u"locale"] if locale in self._LOCALE: raw_config[u"XX_lang"] = self._LOCALE[locale] def _format_dst_change(self, dst_change): fmted_time = u"%02d:%02d:%02d" % tuple(dst_change["time"].as_hms) day = dst_change["day"] if day.startswith("D"): return u"%02d.%02d %s" % (int(day[1:]), dst_change["month"], fmted_time) else: week, weekday = map(int, day[1:].split(".")) weekday = tzinform.week_start_on_monday(weekday) return u"%02d.%02d.%02d %s" % (dst_change["month"], week, weekday, fmted_time) def _format_tzinfo(self, tzinfo): lines = [] lines.append(u'<timezone perm="R"></timezone>') lines.append(u'<utc_offset perm="R">%+d</utc_offset>' % tzinfo["utcoffset"].as_seconds) if tzinfo["dst"] is None: lines.append(u'<dst perm="R"></dst>') else: lines.append( u'<dst perm="R">%d %s %s</dst>' % ( tzinfo["dst"]["save"].as_seconds, self._format_dst_change(tzinfo["dst"]["start"]), self._format_dst_change(tzinfo["dst"]["end"]), ) ) return u"\n".join(lines) def _add_timezone(self, raw_config): if u"timezone" in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u"timezone"]) except tzinform.TimezoneNotFoundError, e: logger.warning("Unknown timezone %s: %s", raw_config[u"timezone"], e) else: raw_config[u"XX_timezone"] = self._format_tzinfo(tzinfo)
class BaseTechnicolorPlugin(StandardPlugin): _ENCODING = 'ISO-8859-1' _TZ_MAP = _gen_tz_map() _LOCALE = { # <locale id>, (<langage type>, <country code>) u'de_DE': (u'3', u'DE'), u'en_US': (u'0', u'US'), u'es_ES': (u'2', u'ES'), u'fr_FR': (u'1', u'FR'), u'fr_CA': (u'1', u'US'), } _LOCALE_DEF = (u'0', u'US') _SIP_DTMF_MODE = { u'RTP-in-band': u'0', u'RTP-out-of-band': u'1', u'SIP-INFO': u'4' } _DTMF_DEF = u'1' _SIP_TRANSPORT = { u'udp': u'0', u'tcp': u'1', u'tls': u'2' } _TRANSPORT_DEF = u'0' _NTP_ZONE_NUM_DEF = u'23' _XX_PHONEBOOK_NAME = { u'fr': u'Annuaire entreprise', u'en': u'Enterprise directory' } _XX_PHONEBOOK_NAME_DEF = u'' _NB_FKEYS = 66 _NB_LINES = 4 def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseTechnicolorHTTPDeviceInfoExtractor() def configure_common(self, raw_config): for tpl_filename, filename in self._COMMON_TEMPLATES: tpl = self._tpl_helper.get_template(tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _add_country_and_lang(self, raw_config): locale = raw_config.get(u'locale') raw_config[u'XX_language_type'], raw_config[u'XX_country_code'] = \ self._LOCALE.get(locale, self._LOCALE_DEF) def _add_config_sn(self, raw_config): # The only thing config_sn needs to be is 12 digit long and different # from one config file to another. raw_config[u'XX_config_sn'] = '%012.f' % time.time() def _add_dtmf_mode_flag(self, raw_config): dtmf_mode = raw_config.get(u'sip_dtmf_mode') raw_config[u'XX_dtmf_mode_flag'] = self._SIP_DTMF_MODE.get(dtmf_mode, self._DTMF_DEF) def _add_transport_flg(self, raw_config): sip_transport = raw_config.get(u'sip_transport') raw_config[u'XX_transport_flg'] = self._SIP_TRANSPORT.get(sip_transport, self._TRANSPORT_DEF) def _gen_xx_phonebook_name(self, raw_config): if u'locale' in raw_config: language = raw_config[u'locale'].split('_')[0] return self._XX_PHONEBOOK_NAME.get(language, self._XX_PHONEBOOK_NAME_DEF) else: return self._XX_PHONEBOOK_NAME_DEF def _tzinfo_to_zone_num(self, tzinfo): utcoffset_m = tzinfo['utcoffset'].as_minutes if utcoffset_m not in self._TZ_MAP: # No UTC offset matching. Let's try finding one relatively close... for supp_offset in [30, -30, 60, -60]: if utcoffset_m + supp_offset in self._TZ_MAP: utcoffset_m += supp_offset break else: return self._XX_NTP_ZONE_NUM_DEF dst_map = self._TZ_MAP[utcoffset_m] if tzinfo['dst']: dst_key = tzinfo['dst']['as_string'] else: dst_key = None if dst_key not in dst_map: # No DST rules matching. Fallback on all-standard time or random # DST rule in last resort... if None in dst_map: dst_key = None else: dst_key = dst_map.keys[0] return dst_map[dst_key] def _add_ntp_zone_num(self, raw_config): raw_config[u'XX_ntp_zone_num'] = self._NTP_ZONE_NUM_DEF if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.info('Unknown timezone: %s', e) else: raw_config[u'XX_ntp_zone_num'] = self._tzinfo_to_zone_num(tzinfo)
class BaseGrandstreamPlugin(StandardPlugin): _ENCODING = 'UTF-8' # VPKs are the virtual phone keys on the main display # MPKs are the physical programmable keys on some models MODEL_FKEYS = { u'GRP2612': { u'vpk': 16, u'mpk': 0, }, u'GRP2613': { u'vpk': 24, u'mpk': 0, }, u'GRP2614': { u'vpk': 16, u'mpk': 24, }, u'GRP2615': { u'vpk': 40, u'mpk': 0, }, u'GRP2616': { u'vpk': 16, u'mpk': 24, }, } DTMF_MODES = { # mode: (in audio, in RTP, in SIP) u'RTP-in-band': ('Yes', 'Yes', 'No'), u'RTP-out-of-band': ('No', 'Yes', 'No'), u'SIP-INFO': ('No', 'No', 'Yes'), } SIP_TRANSPORTS = { u'udp': u'UDP', u'tcp': u'TCP', u'tls': u'TlsOrTcp', } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Grandstream') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) http_dev_info_extractor = BaseGrandstreamHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return 'cfg' + fmted_mac + '.xml' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._check_lines_password(raw_config) self._add_sip_transport(raw_config) self._add_timezone(raw_config) self._add_locale(raw_config) self._add_dtmf_mode(raw_config) self._add_fkeys(raw_config) self._add_mpk(raw_config) self._add_v2_fkeys(raw_config, device.get(u'model')) self._add_dns(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older wazo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' in device: return self._dev_specific_filename(device) def _check_lines_password(self, raw_config): for line in raw_config[u'sip_lines'].itervalues(): if line[u'password'] == u'autoprov': line[u'password'] = u'' def _add_timezone(self, raw_config): if u'timezone' in raw_config and raw_config[u'timezone'] in TZ_NAME: raw_config[u'XX_timezone'] = TZ_NAME[raw_config[u'timezone']] else: raw_config['timezone'] = TZ_NAME['Europe/Paris'] def _add_locale(self, raw_config): locale = raw_config.get(u'locale') if locale in LOCALE: raw_config[u'XX_locale'] = LOCALE[locale] def _add_fkeys(self, raw_config): lines = [] for funckey_no, funckey_dict in raw_config[u'funckeys'].iteritems(): i_funckey_no = int(funckey_no) funckey_type = funckey_dict[u'type'] if funckey_type not in FUNCKEY_TYPES: logger.info('Unsupported funckey type: %s', funckey_type) continue type_code = u'P32%s' % (i_funckey_no + 2) lines.append((type_code, FUNCKEY_TYPES[funckey_type])) line_code = self._format_code(3 * i_funckey_no - 2) lines.append((line_code, int(funckey_dict[u'line']) - 1)) if u'label' in funckey_dict: label_code = self._format_code(3 * i_funckey_no - 1) lines.append((label_code, funckey_dict[u'label'])) value_code = self._format_code(3 * i_funckey_no) lines.append((value_code, funckey_dict[u'value'])) raw_config[u'XX_fkeys'] = lines def _add_mpk(self, raw_config): lines = [] start_code = 23000 for funckey_no, funckey_dict in raw_config[u'funckeys'].iteritems(): i_funckey_no = int(funckey_no) # starts at 1 funckey_type = funckey_dict[u'type'] if funckey_type not in FUNCKEY_TYPES: logger.info('Unsupported funckey type: %s', funckey_type) continue start_p_code = start_code + (i_funckey_no - 1) * 5 type_code = u'P{}'.format(start_p_code) lines.append((type_code, FUNCKEY_TYPES[funckey_type])) line_code = u'P{}'.format(start_p_code + 1) lines.append((line_code, int(funckey_dict[u'line']) - 1)) if u'label' in funckey_dict: label_code = u'P{}'.format(start_p_code + 2) lines.append((label_code, funckey_dict[u'label'])) value_code = u'P{}'.format(start_p_code + 3) lines.append((value_code, funckey_dict[u'value'])) raw_config[u'XX_mpk'] = lines def _add_v2_fkeys(self, raw_config, model): lines = [] model_fkeys = self.MODEL_FKEYS.get(model) if not model_fkeys: logger.info('Unknown model: "%s"', model) return for funckey_no in range(1, model_fkeys[u'vpk'] + 1): funckey = raw_config[u'funckeys'].get(str(funckey_no), {}) funckey_type = funckey.get(u'type', 'disabled') if funckey_type not in FUNCKEY_TYPES: logger.info('Unsupported funckey type: %s', funckey_type) continue if str(funckey_no) in raw_config[u'sip_lines']: logger.info( 'Function key %s would conflict with an existing line', funckey_no) continue lines.append(( funckey_no, { u'section': u'vpk', u'type': FUNCKEY_TYPES[funckey_type], u'label': funckey.get(u'label') or u'', u'value': funckey.get(u'value') or u'', }, )) for funckey_no in range(1, model_fkeys[u'mpk'] + 1): funckey = raw_config[u'funckeys'].get( str(funckey_no + model_fkeys[u'vpk']), {}) funckey_type = funckey.get(u'type', 'disabled') if funckey_type not in FUNCKEY_TYPES: logger.info('Unsupported funckey type: %s', funckey_type) lines.append(( funckey_no, { u'section': u'mpk', u'type': FUNCKEY_TYPES[funckey_type], u'label': funckey.get(u'label') or u'', u'value': funckey.get(u'value') or u'', }, )) raw_config[u'XX_v2_fkeys'] = lines def _format_code(self, code): if code >= 10: str_code = str(code) else: str_code = u'0%s' % code return u'P3%s' % str_code def _add_dns(self, raw_config): if raw_config.get(u'dns_enabled'): dns_parts = raw_config[u'dns_ip'].split('.') for part_nb, part in enumerate(dns_parts, start=1): raw_config[u'XX_dns_%s' % part_nb] = part def _add_dtmf_mode(self, raw_config): if raw_config.get(u'sip_dtmf_mode'): dtmf_info = self.DTMF_MODES[raw_config[u'sip_dtmf_mode']] raw_config['XX_dtmf_in_audio'] = dtmf_info[0] raw_config['XX_dtmf_in_rtp'] = dtmf_info[1] raw_config['XX_dtmf_in_sip'] = dtmf_info[2] def _add_sip_transport(self, raw_config): sip_transport = raw_config.get(u'sip_transport') if sip_transport in self.SIP_TRANSPORTS: raw_config[u'XX_sip_transport'] = self.SIP_TRANSPORTS[ sip_transport]
class BaseAlcatelPlugin(StandardPlugin): _ENCODING = 'UTF-8' _SIP_DTMF_MODE = { u'off': 0, u'RTP-in-band': 1, u'RTP-out-of-band': 2, u'SIP-INFO': 4, } _NB_FUNCKEYS = { u'M3': 20, u'M5': 28, u'M7': 28, } _FUNCKEY_TYPE = { u'blf': 59, u'speeddial': 1, } _LANG = { u'en': 0, u'fr': 1, u'de': 2, u'it': 3, u'es': 4, u'nl': 5, u'pt': 6, u'hu': 7, u'cs': 8, u'sk': 9, u'sl': 10, u'et': 11, u'pl': 12, u'lt': 13, u'lv': 14, u'tr': 15, u'el': 16, u'sv': 17, u'no': 18, u'da': 19, u'fi': 20, u'is': 21, u'zh': 22, } _SENSITIVE_FILENAME_REGEX = re.compile(r'^config\.[0-9a-f]{12}\.xml') http_dev_info_extractor = BaseAlcatelMyriadHTTPDeviceInfoExtractor() def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) def _common_templates(self): for tpl_format, file_format in [('common/config.model.xml.tpl', 'config.{}.xml')]: for model in self._MODELS_VERSIONS: yield tpl_format.format(model), file_format.format(model) def configure_common(self, raw_config): self._add_server_url(raw_config) for tpl_filename, filename in self._common_templates(): tpl = self._tpl_helper.get_template(tpl_filename) dest_file = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dest_file, self._ENCODING) def _update_sip_lines(self, raw_config): proxy_ip = raw_config.get(u'sip_proxy_ip') proxy_port = raw_config.get(u'sip_proxy_port') backup_proxy_ip = raw_config.get(u'sip_backup_proxy_ip') backup_proxy_port = raw_config.get(u'sip_backup_proxy_port') outbound_proxy_ip = raw_config.get(u'sip_outbound_proxy_ip') outbound_proxy_port = raw_config.get(u'sip_outbound_proxy_port') voicemail = raw_config.get(u'exten_voicemail') for line in raw_config[u'sip_lines'].itervalues(): if proxy_ip: line.setdefault(u'proxy_ip', proxy_ip) if proxy_port: line.setdefault(u'proxy_port', proxy_port) if backup_proxy_ip: line.setdefault(u'backup_proxy_ip', backup_proxy_ip) if backup_proxy_port: line.setdefault(u'backup_proxy_port', backup_proxy_port) if outbound_proxy_ip: line.setdefault(u'outbound_proxy_ip', outbound_proxy_ip) if outbound_proxy_port: line.setdefault(u'outbound_proxy_port', outbound_proxy_port) if voicemail: line.setdefault(u'voicemail', voicemail) def _add_fkeys(self, raw_config, model): nb_funckeys = self._NB_FUNCKEYS.get(model) if not nb_funckeys: logger.warning( 'Unknown model: "%s". Skipping function key configuration.', model) return raw_config[u'XX_fkeys'] = [] for funckey_no, funckey_dict in raw_config[u'funckeys'].iteritems(): position = int(funckey_no) + 1 fkey_type = self._FUNCKEY_TYPE.get( funckey_dict[u'type'], self._FUNCKEY_TYPE[u'speeddial']) fkey_label = funckey_dict[u'label'] fkey_extension = funckey_dict[u'value'] if position > nb_funckeys: logger.warning( 'Function key "%s" outside range supported by phone.', position) continue fkey_data = { u'position': position, u'type': fkey_type, u'label': fkey_label, u'extension': fkey_extension, u'value': fkey_extension, } raw_config[u'XX_fkeys'].append(fkey_data) def _format_tzinfo(self, tzinfo): tz_hms = tzinfo['utcoffset'].as_hms offset_hour = tz_hms[0] offset_minutes = tz_hms[1] return '{:+02d}:{:02d}'.format(offset_hour, offset_minutes) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError as e: logger.warning('Unknown timezone "%s": "%s"', raw_config[u'timezone'], e) else: raw_config[u'XX_timezone'] = self._format_tzinfo(tzinfo) def _add_language(self, raw_config): locale = raw_config[u'locale'] if '_' in locale: lang, _ = locale.split('_') else: lang = locale lang_code = self._LANG.get(lang, self._LANG['en']) raw_config[u'XX_lang'] = lang_code def _add_user_dtmf_info(self, raw_config): dtmf_mode = raw_config.get(u'sip_dtmf_mode') for line in raw_config[u'sip_lines'].itervalues(): cur_dtmf_mode = line.get(u'dtmf_mode', dtmf_mode) line[u'XX_user_dtmf_info'] = self._SIP_DTMF_MODE.get( cur_dtmf_mode, 'off') def _add_xivo_phonebook_url(self, raw_config): if hasattr(plugins, 'add_xivo_phonebook_url') and raw_config.get( u'config_version', 0) >= 1: plugins.add_xivo_phonebook_url(raw_config, u'snom') else: self._add_xivo_phonebook_url_compat(raw_config) def _add_xivo_phonebook_url_compat(self, raw_config): hostname = raw_config.get(u'X_xivo_phonebook_ip') if hostname: raw_config[ u'XX_xivo_phonebook_url'] = u'http://{hostname}/service/ipbx/web_services.php/phonebook/search/'.format( hostname=hostname) def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') if u'model' not in device: raise Exception('Model name needed for device configuration') def _dev_specific_filename(self, device): return u'config.{}.xml'.format(format_mac(device[u'mac'], separator='')) def _add_server_url(self, raw_config): ip = raw_config[u'ip'] http_port = raw_config[u'http_port'] raw_config[u'XX_server_url'] = u'http://{}:{}'.format(ip, http_port) def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) xml_filename = self._dev_specific_filename(device) # generate xml file tpl = self._tpl_helper.get_dev_template(xml_filename, device) model = device.get(u'model') self._update_sip_lines(raw_config) self._add_fkeys(raw_config, model) self._add_timezone(raw_config) self._add_user_dtmf_info(raw_config) self._add_xivo_phonebook_url(raw_config) self._add_server_url(raw_config) self._add_language(raw_config) raw_config[u'XX_options'] = device.get(u'options', {}) path = os.path.join(self._tftpboot_dir, xml_filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): filename = self._dev_specific_filename(device) try: os.remove(os.path.join(self._tftpboot_dir, filename)) except OSError as e: # ignore logger.warning('error while removing file: "%s"', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device, event='check-sync') else: # backward compatibility with older wazo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync;reboot=true') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def is_sensitive_filename(self, filename): return bool(self._SENSITIVE_FILENAME_REGEX.match(filename))
class BaseGigasetPlugin(StandardPlugin): _ENCODING = 'UTF-8' _SIP_DTMF_MODE = { u'RTP-in-band': u'1', u'RTP-out-of-band': u'2', u'SIP-INFO': u'4', } _SIP_SRTP_MODE = { u'disabled': u'0', u'preferred': u'1', u'required': u'1', } _SIP_TRANSPORT = { u'udp': u'1', u'tcp': u'2', u'tls': u'3', } _VALID_TZ_GIGASET = set(( 'Pacific/Honolulu', 'America/Anchorage', 'America/Los_Angeles', 'America/Denver', 'America/Chicago', 'America/New_York', 'America/Caracas', 'America/Sao_Paulo', 'Europe/Belfast', 'Europe/Dublin', 'Europe/Guernsey', 'Europe/Isle_of_Man', 'Europe/Jersey', 'Europe/Lisbon', 'Europe/London', 'Greenwich', 'Europe/Amsterdam', 'Europe/Andorra', 'Europe/Belgrade', 'Europe/Berlin', 'Europe/Bratislava', 'Europe/Brussels', 'Europe/Budapest', 'Europe/Busingen', 'Europe/Copenhagen', 'Europe/Gibraltar', 'Europe/Ljubljana', 'Europe/Luxembourg', 'Europe/Madrid', 'Europe/Malta', 'Europe/Monaco', 'Europe/Oslo', 'Europe/Paris', 'Europe/Podgorica', 'Europe/Prague', 'Europe/Rome', 'Europe/San_Marino', 'Europe/Sarajevo', 'Europe/Skopje', 'Europe/Stockholm', 'Europe/Tirane', 'Europe/Vaduz', 'Europe/Vatican', 'Europe/Vienna', 'Europe/Warsaw', 'Europe/Zagreb', 'Europe/Zurich', 'Africa/Cairo', 'Europe/Athens', 'Europe/Bucharest', 'Europe/Chisinau', 'Europe/Helsinki', 'Europe/Kaliningrad', 'Europe/Kiev', 'Europe/Mariehamn', 'Europe/Nicosia', 'Europe/Riga', 'Europe/Sofia', 'Europe/Tallinn', 'Europe/Tiraspol', 'Europe/Uzhgorod', 'Europe/Vilnius', 'Europe/Zaporozhye', 'Europe/Istanbul', 'Europe/Kirov', 'Europe/Minsk', 'Europe/Moscow', 'Europe/Simferopol', 'Europe/Volgograd', 'Asia/Dubai', 'Europe/Astrakhan', 'Europe/Samara', 'Europe/Ulyanovsk', 'Asia/Karachi', 'Asia/Dhaka', 'Asia/Hong_Kong', 'Asia/Tokyo', 'Australia/Adelaide', 'Australia/Darwin', 'Australia/Brisbane', 'Australia/Sydney', 'Pacific/Noumea', )) _FALLBACK_TZ = { (-3, 0): 'America/Sao_Paulo', (-4, 0): 'America/New_York', (-5, 0): 'America/Chicago', (-6, 0): 'America/Denver', (-7, 0): 'America/Los_Angeles', (-8, 0): 'America/Anchorage', (-10, 0): 'Pacific/Honolulu', (0, 0): 'Greenwich', (1, 0): 'Europe/London', (2, 0): 'Europe/Paris', (3, 0): 'Europe/Moscow', (4, 0): 'Asia/Dubai', (5, 0): 'Asia/Karachi', (6, 0): 'Asia/Dhaka', (8, 0): 'Asia/Hong_Kong', (9, 0): 'Asia/Tokyo', (9, 3): 'Australia/Adelaide', (10, 0): 'Australia/Sydney', (11, 0): 'Pacific/Noumea', } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._app = app self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = GigasetHTTPDeviceInfoExtractor() def _check_device(self, device): if u'ip' not in device: raise Exception('IP address needed for Gigaset configuration') def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return fmted_mac + '.xml' def _add_phonebook(self, raw_config): uuid_format = u'{scheme}://{hostname}:{port}/0.1/directories/lookup/{profile}/gigaset/{user_uuid}?' plugins.add_xivo_phonebook_url_from_format(raw_config, uuid_format) def _fix_timezone(self, raw_config): timezone = raw_config.get(u'timezone', 'Greenwich') if timezone not in self._VALID_TZ_GIGASET: tz_db = tzinform.TextTimezoneInfoDB() tz_info = tz_db.get_timezone_info(timezone)['utcoffset'].as_hms offset_hour = tz_info[0] offset_minutes = tz_info[1] raw_config[u'timezone'] = self._FALLBACK_TZ[(offset_hour, offset_minutes)] def _add_xx_vars(self, device, raw_config): raw_config[u'XX_epoch'] = int(time.time()) self._fix_timezone(raw_config) def _add_voip_providers(self, raw_config): voip_providers = dict() provider_id = 0 sip_lines = raw_config.get(u'sip_lines') dtmf_mode = raw_config.get(u'sip_dtmf_mode', '1') srtp_mode = raw_config.get(u'sip_srtp_mode', '0') sip_transport = self._SIP_TRANSPORT.get( raw_config.get(u'sip_transport', '1')) if sip_lines: for line in sip_lines.itervalues(): proxy_ip = line.get(u'proxy_ip') proxy_port = line.get(u'proxy_port', 5060) line_dtmf_mode = self._SIP_DTMF_MODE.get( line.get(u'dtmf_mode', dtmf_mode)) line_srtp_mode = self._SIP_SRTP_MODE.get( line.get(u'strp_mode', srtp_mode)) if (proxy_ip, proxy_port) not in voip_providers: provider = { u'id': provider_id, u'sip_proxy_ip': proxy_ip, u'sip_proxy_port': proxy_port, u'dtmf_mode': line_dtmf_mode, u'srtp_mode': line_srtp_mode, u'sip_transport': sip_transport, } line[u'provider_id'] = provider_id voip_providers[(proxy_ip, proxy_port)] = provider provider_id += 1 else: line[u'provider_id'] = voip_providers[(proxy_ip, proxy_port)]['id'] raw_config[u'XX_voip_providers'] = voip_providers.values() def _add_ac_code(self, raw_config): sip_lines = raw_config.get(u'sip_lines') if sip_lines: for line in sip_lines.itervalues(): number = line.get(u'number') if number.startswith(u'auto'): line[u'XX_hs_code'] = '0000' else: line[u'XX_hs_code'] = number[-4:].zfill(4) def configure(self, device, raw_config): self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) self._add_voip_providers(raw_config) self._add_ac_code(raw_config) self._add_xx_vars(device, raw_config) self._add_phonebook(raw_config) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) def is_sensitive_filename(self, filename): return bool(self._SENSITIVE_FILENAME_REGEX.match(filename)) _SENSITIVE_FILENAME_REGEX = re.compile(r'^[0-9a-f]{12}\.xml$') if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync')
class BaseDigiumPlugin(StandardPlugin): _ENCODING = 'UTF-8' _CONTACT_TEMPLATE = 'contact.tpl' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) self._digium_dir = os.path.join(self._tftpboot_dir, 'Digium') downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) dhcp_dev_info_extractor = DigiumDHCPDeviceInfoExtractor() http_dev_info_extractor = DigiumHTTPDeviceInfoExtractor() def configure(self, device, raw_config): self._check_device(device) filename = self._dev_specific_filename(device) contact_filename = self._dev_contact_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) contact_tpl = self._tpl_helper.get_template(self._CONTACT_TEMPLATE) raw_config['XX_mac'] = self._format_mac(device) raw_config['XX_main_proxy_ip'] = self._get_main_proxy_ip(raw_config) raw_config['XX_funckeys'] = self._transform_funckeys(raw_config) raw_config['XX_lang'] = raw_config.get(u'locale') path = os.path.join(self._digium_dir, filename) contact_path = os.path.join(self._digium_dir, contact_filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) self._tpl_helper.dump(contact_tpl, raw_config, contact_path, self._ENCODING) def deconfigure(self, device): filenames = [ self._dev_specific_filename(device), self._dev_contact_filename(device) ] for filename in filenames: path = os.path.join(self._digium_dir, filename) try: os.remove(path) except OSError as e: logger.info('error while removing file %s: %s', (path, e)) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail(Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail(Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed to configure device') def _get_main_proxy_ip(self, raw_config): if raw_config[u'sip_lines']: line_no = min(int(x) for x in raw_config[u'sip_lines'].keys()) line_no = str(line_no) return raw_config[u'sip_lines'][line_no][u'proxy_ip'] else: return raw_config[u'ip'] def _format_mac(self, device): return format_mac(device[u'mac'], separator='', uppercase=False) def _dev_specific_filename(self, device): filename = '%s.cfg' % self._format_mac(device) return filename def _dev_contact_filename(self, device): contact_filename = '%s-contacts.xml' % self._format_mac(device) return contact_filename def _transform_funckeys(self, raw_config): return dict( (int(k), v) for k, v in raw_config['funckeys'].iteritems() )
class BaseFanvilPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = {} _TZ_INFO = {} _SIP_DTMF_MODE = { u'RTP-in-band': u'0', u'RTP-out-of-band': u'1', u'SIP-INFO': u'2', } _SIP_TRANSPORT = { u'udp': u'0', u'tcp': u'1', u'tls': u'3', } _DIRECTORY_KEY = { u'en': u'Directory', u'fr': u'Annuaire', } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Fanvil') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return fmted_mac + '.cfg' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._check_lines_password(raw_config) self._add_timezone(device, raw_config) self._add_locale(device, raw_config) self._add_sip_transport(raw_config) self._update_lines(raw_config) self._add_fkeys(raw_config) self._add_phonebook_url(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def configure_common(self, raw_config): for filename, (_, fw_filename, tpl_filename) in self._COMMON_FILES.iteritems(): tpl = self._tpl_helper.get_template('common/%s' % tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) raw_config[u'XX_fw_filename'] = fw_filename self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older wazo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def _check_lines_password(self, raw_config): for line in raw_config[u'sip_lines'].itervalues(): if line[u'password'] == u'autoprov': line[u'password'] = u'' def _extract_dst_change(self, dst_change): lines = {} lines['month'] = dst_change['month'] lines['hour'] = min(dst_change['time'].as_hours, 23) if dst_change['day'].startswith('D'): lines['dst_wday'] = dst_change['day'][1:] else: week, weekday = dst_change['day'][1:].split('.') if week == '5': lines['dst_week'] = -1 else: lines['dst_week'] = week lines['dst_wday'] = weekday return lines def _extract_tzinfo(self, device, tzinfo): tz_all = {} utc = tzinfo['utcoffset'].as_hours utc_list = self._TZ_INFO[utc] for time_zone_name, time_zone in utc_list: tz_all['time_zone'] = time_zone tz_all['time_zone_name'] = time_zone_name if tzinfo['dst'] is None: tz_all['enable_dst'] = False else: tz_all['enable_dst'] = True tz_all['dst_min_offset'] = min(tzinfo['dst']['save'].as_minutes, 60) tz_all['dst_start'] = self._extract_dst_change( tzinfo['dst']['start']) tz_all['dst_end'] = self._extract_dst_change(tzinfo['dst']['end']) return tz_all def _add_timezone(self, device, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.info('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._extract_tzinfo( device, tzinfo)
class BaseSnomPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { u'de_DE': (u'Deutsch', u'GER'), u'en_US': (u'English', u'USA'), u'es_ES': (u'Espanol', u'ESP'), u'fr_FR': (u'Francais', u'FRA'), u'fr_CA': (u'Francais', u'USA'), u'it_IT': (u'Italiano', u'ITA'), u'nl_NL': (u'Dutch', u'NLD'), } _SIP_DTMF_MODE = { u'RTP-in-band': u'off', u'RTP-out-of-band': u'off', u'SIP-INFO': u'sip_info_only' } _XX_DICT_DEF = u'en' _XX_DICT = { u'en': { u'remote_directory': u'Directory', }, u'fr': { u'remote_directory': u'Annuaire', }, } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseSnomHTTPDeviceInfoExtractor() def _common_templates(self): yield ('common/gui_lang.xml.tpl', 'gui_lang.xml') yield ('common/web_lang.xml.tpl', 'web_lang.xml') for tpl_format, file_format in [ ('common/snom%s.htm.tpl', 'snom%s.htm'), ('common/snom%s.xml.tpl', 'snom%s.xml'), ('common/snom%s-firmware.xml.tpl', 'snom%s-firmware.xml') ]: for model in self._MODELS: yield tpl_format % model, file_format % model def configure_common(self, raw_config): for tpl_filename, filename in self._common_templates(): tpl = self._tpl_helper.get_template(tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): proxy_ip = raw_config.get(u'sip_proxy_ip') backup_proxy_ip = raw_config.get(u'sip_backup_proxy_ip') voicemail = raw_config.get(u'exten_voicemail') for line in raw_config[u'sip_lines'].itervalues(): if proxy_ip: line.setdefault(u'proxy_ip', proxy_ip) if backup_proxy_ip: line.setdefault(u'backup_proxy_ip', backup_proxy_ip) if voicemail: line.setdefault(u'voicemail', voicemail) def _add_fkeys(self, raw_config, model): lines = [] for funckey_no, funckey_dict in sorted( raw_config[u'funckeys'].iteritems(), key=itemgetter(0)): funckey_type = funckey_dict[u'type'] if funckey_type == u'speeddial': type_ = u'speed' suffix = '' elif funckey_type == u'park': if model in [u'710', u'715', u'720', u'725', u'760', u'D765']: type_ = u'orbit' suffix = '' else: type_ = u'speed' suffix = '' elif funckey_type == u'blf': if u'exten_pickup_call' in raw_config: type_ = u'blf' suffix = '|%s' % raw_config[u'exten_pickup_call'] else: logger.warning( 'Could not set funckey %s: no exten_pickup_call', funckey_no) continue else: logger.info('Unsupported funckey type: %s', funckey_type) continue value = funckey_dict[u'value'] label = escape(funckey_dict.get(u'label') or value) fkey_value = self._format_fkey_value(type_, value, suffix) lines.append( u'<fkey idx="%d" label="%s" context="active" perm="R">%s</fkey>' % (int(funckey_no) - 1, label, fkey_value)) raw_config[u'XX_fkeys'] = u'\n'.join(lines) def _format_fkey_value(self, fkey_type, value, suffix): return '%s %s%s' % (fkey_type, value, suffix) def _add_lang(self, raw_config): if u'locale' in raw_config: locale = raw_config[u'locale'] if locale in self._LOCALE: raw_config[u'XX_lang'] = self._LOCALE[locale] def _format_dst_change(self, dst_change): fmted_time = u'%02d:%02d:%02d' % tuple(dst_change['time'].as_hms) day = dst_change['day'] if day.startswith('D'): return u'%02d.%02d %s' % (int( day[1:]), dst_change['month'], fmted_time) else: week, weekday = map(int, day[1:].split('.')) weekday = tzinform.week_start_on_monday(weekday) return u'%02d.%02d.%02d %s' % (dst_change['month'], week, weekday, fmted_time) def _format_tzinfo(self, tzinfo): lines = [] lines.append(u'<timezone perm="R"></timezone>') lines.append(u'<utc_offset perm="R">%+d</utc_offset>' % tzinfo['utcoffset'].as_seconds) if tzinfo['dst'] is None: lines.append(u'<dst perm="R"></dst>') else: lines.append(u'<dst perm="R">%d %s %s</dst>' % (tzinfo['dst']['save'].as_seconds, self._format_dst_change(tzinfo['dst']['start']), self._format_dst_change(tzinfo['dst']['end']))) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.warning('Unknown timezone %s: %s', raw_config[u'timezone'], e) else: raw_config[u'XX_timezone'] = self._format_tzinfo(tzinfo)
class BaseHtekPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { u'de_DE': (u'German', u'Germany'), u'en_US': (u'English', u'United States'), u'en_GB': (u'English', u'Great Britain'), u'es_ES': (u'Spanish', u'Spain'), u'fr_FR': (u'French', u'France'), u'fr_CA': (u'French', u'United States'), } # Used for tone select _COUNTRIES = { 'Custom': 0, 'Australia': 1, 'Austria': 2, 'Brazil': 3, 'Belgium': 4, 'China': 5, 'Chile': 6, 'Czech': 7, 'Denmark': 8, 'Finland': 9, 'France': 10, 'Germany': 11, 'Great Britain': 12, 'Greece': 13, 'Hungary': 14, 'Lithuania': 15, 'India': 16, 'Italy': 17, 'Japan': 18, 'Mexico': 19, 'New Zealand': 20, 'Netherlands': 21, 'Norway': 22, 'Portugal': 23, 'Spain': 24, 'Switzerland': 25, 'Sweden': 26, 'Russia': 27, 'United States': 28, } _SIP_DTMF_MODE = { u'RTP-out-of-band': u'0', u'RTP-in-band': u'1', u'SIP-INFO': u'2', } _SIP_TRANSPORT = { u'udp': u'0', u'tcp': u'1', u'tls': u'2', } _SIP_TRANSPORT_DEF = u'0' _NB_LINEKEYS = { u'UC926': 36, u'UC926E': 36, u'UC924': 28, u'UC924E': 28, u'UC923': 20, u'UC912': 12, u'UC912E': 12, u'UC912G': 12, u'UC903': 20, u'UC862': 14, u'UC860': 14, u'UC860P': 14, u'UC842': 4, u'UC840': 4, u'UC840P': 4, u'UC806': 4, u'UC806T': 4, u'UC804': 4, u'UC804T': 4, u'UC803': 0, u'UC803T': 0, u'UC802': 0, u'UC802T': 0, } _TZ_INFO = { (-11, 00): 105, (-10, 00): 2, (-9, 00): 3, (-8, 00): 6, (-7, 00): 10, (-6, 00): 14, (-5, 00): 18, (-4, 30): 19, (-4, 00): 20, (-3, 30): 26, (-3, 00): 30, (-2, 00): 31, (-1, 00): 32, (00, 00): 33, (1, 00): 49, (2, 00): 57, (3, 00): 73, (3, 30): 74, (4, 00): 77, (5, 00): 83, (5, 30): 84, (6, 00): 85, (7, 00): 88, (8, 00): 89, (9, 00): 93, (9, 30): 94, (10, 00): 96, (10, 30): 100, (11, 00): 101, (12, 00): 102, (12, 45): 103, (13, 00): 104, } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseHtekHTTPDeviceInfoExtractor() def configure_common(self, raw_config): for filename, tpl_filename in self._COMMON_FILES: tpl = self._tpl_helper.get_template('common/%s' % tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): for line_no, line in raw_config[u'sip_lines'].iteritems(): # set line number line[u'XX_line_no'] = int(line_no) # set dtmf inband transfer dtmf_mode = line.get(u'dtmf_mode') or raw_config.get( u'sip_dtmf_mode') if dtmf_mode in self._SIP_DTMF_MODE: line[u'XX_dtmf_type'] = self._SIP_DTMF_MODE[dtmf_mode] # set voicemail if u'voicemail' not in line and u'exten_voicemail' in raw_config: line[u'voicemail'] = raw_config[u'exten_voicemail'] # set proxy_ip if u'proxy_ip' not in line: line[u'proxy_ip'] = raw_config[u'sip_proxy_ip'] # set proxy_port if u'proxy_port' not in line and u'sip_proxy_port' in raw_config: line[u'proxy_port'] = raw_config[u'sip_proxy_port'] def _gen_param_num(self, line, offset=0): '''Method that generates line parameter numbers for the config file There are several steps in the numbering of parameters that need to be supported.''' param_nb = 0 mode_nb = 0 limit_step1 = 5 limit_step2 = 37 mode_base = 20600 param_step1_base = 41200 param_step2_base = 20200 param_step3_base = 23000 line = int(line) if line < limit_step1: param_nb = param_step1_base + line - 1 + 100 * offset mode_nb = mode_base + line - 1 elif line >= limit_step1 and line < limit_step2: param_nb = param_step2_base + offset + 5 * (line - limit_step1) mode_nb = mode_base + line - 1 else: param_nb = param_step3_base + offset + 5 * (line - limit_step2) mode_nb = param_nb return param_nb, mode_nb def _add_fkeys(self, device, raw_config): # Setting up the line/function keys complete_fkeys = {} fkeys = raw_config[u'funckeys'] fkey_type_assoc = {u'': 0, u'speeddial': 2, u'blf': 3, u'park': 8} if u'model' in device and device[u'model'] is not None and device[ u'model'] in self._NB_LINEKEYS: for key_nb in range(1, self._NB_LINEKEYS[device[u'model']] + 1): if str(key_nb) in raw_config[u'sip_lines']: sip_line = raw_config[u'sip_lines'][str(key_nb)] val = { u'type': 1, u'value': '', u'label': sip_line['number'] } else: val = fkeys.get(str(key_nb), { u'type': '', u'value': '', u'label': '' }) val[u'type'] = fkey_type_assoc[val[u'type']] complete_fkeys[key_nb] = { 'type': { 'p_nb': self._gen_param_num(key_nb)[0], 'val': val[u'type'] }, 'mode': { 'p_nb': self._gen_param_num(key_nb)[1] }, 'value': { 'p_nb': self._gen_param_num(key_nb, offset=1)[0], 'val': val[u'value'] }, 'label': { 'p_nb': self._gen_param_num(key_nb, offset=2)[0], 'val': val[u'label'] }, 'account': { 'p_nb': self._gen_param_num(key_nb, offset=3)[0] }, 'extension': { 'p_nb': self._gen_param_num(key_nb, offset=4)[0], 'val': val[u'value'] }, } raw_config[u'XX_fkeys'] = complete_fkeys def _add_country_and_lang(self, raw_config): locale = raw_config.get(u'locale') if locale in self._LOCALE: (lang, country) = self._LOCALE[locale] (raw_config[u'XX_lang'], raw_config[u'XX_country']) = (lang, self._COUNTRIES[country]) def _add_timezone(self, raw_config): timezone = raw_config.get(u'timezone', 'Etc/UTC') tz_db = tzinform.TextTimezoneInfoDB() tz_timezone_info = tz_db.get_timezone_info(timezone) tz_info = tz_timezone_info['utcoffset'].as_hms offset_hour = tz_info[0] offset_minutes = tz_info[1] if (offset_hour, offset_minutes) in self._TZ_INFO: raw_config[u'XX_timezone_code'] = self._TZ_INFO[(offset_hour, offset_minutes)] else: raw_config[u'XX_timezone_code'] = self._TZ_INFO[(-5, 0)] def _add_sip_transport(self, raw_config): raw_config[u'XX_sip_transport'] = self._SIP_TRANSPORT.get( raw_config.get(u'sip_transport'), self._SIP_TRANSPORT_DEF) def _add_xivo_phonebook_url(self, raw_config): if hasattr(plugins, 'add_xivo_phonebook_url') and raw_config.get( u'config_version', 0) >= 1: plugins.add_xivo_phonebook_url(raw_config, u'htek', entry_point=u'lookup') else: self._add_xivo_phonebook_url_compat(raw_config) def _add_xivo_phonebook_url_compat(self, raw_config): hostname = raw_config.get(u'X_xivo_phonebook_ip') if hostname: raw_config[ u'XX_xivo_phonebook_url'] = u'http://{hostname}/service/ipbx/web_services.php/phonebook/search/?name=#SEARCH'.format( hostname=hostname) _SENSITIVE_FILENAME_REGEX = re.compile(r'^cfg[0-9a-f]{12}\.xml') def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='') return 'cfg' + fmted_mac + '.xml' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) self._add_fkeys(device, raw_config) self._add_country_and_lang(raw_config) self._add_timezone(raw_config) self._add_sip_transport(raw_config) self._update_sip_lines(raw_config) self._add_xivo_phonebook_url(raw_config) raw_config[u'XX_options'] = device.get(u'options', {}) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError, e: # ignore logger.info('error while removing file: %s', e)
class BaseGrandstreamPlugin(StandardPlugin): _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = GrandstreamHTTPDeviceInfoExtractor() def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._add_timezone(raw_config) self._add_locale(raw_config) raw_config['XX_mac'] = self._format_mac(device) raw_config['XX_main_proxy_ip'] = self._get_main_proxy_ip(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older wazo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail(Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail(Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed to configure device') def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return 'cfg' + fmted_mac + '.xml' def _format_mac(self, device): return format_mac(device[u'mac'], separator='', uppercase=False) def _add_timezone(self, raw_config): if u'timezone' in raw_config and raw_config[u'timezone'] in TZ_NAME: raw_config[u'XX_timezone'] = TZ_NAME[raw_config[u'timezone']] else: raw_config['timezone'] = TZ_NAME['Europe/Paris'] def _add_locale(self, raw_config): locale = raw_config.get(u'locale') if locale in LOCALE: raw_config[u'XX_locale'] = LOCALE[locale] def _get_main_proxy_ip(self, raw_config): if raw_config[u'sip_lines']: line_no = min(int(x) for x in raw_config[u'sip_lines'].keys()) line_no = str(line_no) return raw_config[u'sip_lines'][line_no][u'proxy_ip'] else: return raw_config[u'ip']
class BaseFanvilPlugin(StandardPlugin): _ENCODING = 'UTF-8' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) # update to use the non-standard tftpboot directory self._base_tftpboot_dir = self._tftpboot_dir self._tftpboot_dir = os.path.join(self._tftpboot_dir, 'Fanvil') self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) # update to use the non-standard tftpboot directory fetchfw_helper.root_dir = self._tftpboot_dir self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._base_tftpboot_dir) http_dev_info_extractor = BaseFanvilHTTPDeviceInfoExtractor() def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='', uppercase=False) return fmted_mac + '.cfg' def _check_config(self, raw_config): if u'http_port' not in raw_config: raise RawConfigError('only support configuration via HTTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) self._check_lines_password(raw_config) self._add_timezone(raw_config) self._add_locale(raw_config) self._add_fkeys(raw_config) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): self._remove_configuration_file(device) def configure_common(self, raw_config): for filename, fw_filename, tpl_filename in self._COMMON_FILES: tpl = self._tpl_helper.get_template('common/%s' % tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) raw_config[u'XX_fw_filename'] = fw_filename self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _remove_configuration_file(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError as e: logger.info('error while removing configuration file: %s', e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older xivo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def _check_lines_password(self, raw_config): for line in raw_config[u'sip_lines'].itervalues(): if line[u'password'] == u'autoprov': line[u'password'] = u'' def _format_dst_change(self, suffix, dst_change): lines = [] lines.append(u'<DST_%s_Mon>%d</DST_%s_Mon>' % (suffix, dst_change['month'], suffix)) lines.append(u'<DST_%s_Hour>%d</DST_%s_Hour>' % (suffix, min(dst_change['time'].as_hours, 23), suffix)) if dst_change['day'].startswith('D'): lines.append(u'<DST_%s_Wday>%s</DST_%s_Wday>' % (suffix, dst_change['day'][1:], suffix)) else: week, weekday = dst_change['day'][1:].split('.') if week == '5': lines.append(u'<DST_%s_Week>-1</DST_%s_Week>' % (suffix, suffix)) else: lines.append(u'<DST_%s_Week>%s</DST_%s_Week>' % (suffix, week, suffix)) lines.append(u'<DST_%s_Wday>%s</DST_%s_Wday>' % (suffix, weekday, suffix)) lines.append(u'<DST_%s_Min>0</DST_%s_Min>' % (suffix, suffix)) return lines def _format_tzinfo(self, tzinfo): lines = [] utc = tzinfo['utcoffset'].as_hours utc_list = TZ_INFO[utc] for time_zone_name, time_zone in utc_list: lines.append(u'<Time_Zone>%s</Time_Zone>' % (time_zone)) lines.append(u'<Time_Zone_Name>%s</Time_Zone_Name>' % (time_zone_name)) if tzinfo['dst'] is None: lines.append(u'<Enable_DST>0</Enable_DST>') else: lines.append(u'<Enable_DST>2</Enable_DST>') lines.append(u'<DST_Min_Offset>%d</DST_Min_Offset>' % (min(tzinfo['dst']['save'].as_minutes, 60))) lines.extend( self._format_dst_change('Start', tzinfo['dst']['start'])) lines.extend(self._format_dst_change('End', tzinfo['dst']['end'])) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.info('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._format_tzinfo(tzinfo)
class BaseZenitelPlugin(StandardPlugin): _ENCODING = 'UTF-8' _VALID_FUNCKEY_NO = [u'1', u'2', u'3'] def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) cfg_service = ZenitelConfigureService(downloaders['auth'], spec_cfg.get('username'), spec_cfg.get('password')) persister = JsonConfigPersister(os.path.join(self._plugin_dir, 'var', 'config.json')) cfg_service = PersistentConfigureServiceDecorator(cfg_service, persister) self.services = {'configure': cfg_service, 'install': fetchfw_helper} self.tftp_service = TFTPFileService(self._tftpboot_dir) tftp_dev_info_extractor = BaseZenitelTFTPDeviceInfoExtractor() pg_associator = BaseZenitelPgAssociator() def _add_sip_section_info(self, raw_config): if u'1' in raw_config[u'sip_lines']: line = raw_config[u'sip_lines'][u'1'] raw_config[u'XX_sip'] = True raw_config[u'XX_nick_name'] = line[u'display_name'] raw_config[u'XX_sip_id'] = line[u'username'] raw_config[u'XX_domain'] = line.get(u'proxy_ip') or raw_config[u'sip_proxy_ip'] raw_config[u'XX_domain2'] = line.get(u'backup_proxy_ip') or \ raw_config.get(u'backup_proxy_ip', u'') raw_config[u'XX_auth_user'] = line[u'auth_username'] raw_config[u'XX_auth_pwd'] = line[u'password'] def _add_fkeys(self, raw_config): lines = [] for funckey_no, funckey_dict in sorted(raw_config[u'funckeys'].iteritems(), key=itemgetter(0)): if funckey_no in self._VALID_FUNCKEY_NO: if funckey_dict[u'type'] == u'speeddial': exten = funckey_dict[u'value'] lines.append(u'speeddial_%s_c1=%s' % (funckey_no, exten)) else: logger.info('Unsupported funckey type: %s', funckey_dict[u'type']) else: logger.info('Out of range funckey no: %s', funckey_no) raw_config[u'XX_fkeys'] = u'\n'.join(' ' + s for s in lines) def _dev_specific_filename(self, device): # Return the device specific filename (not pathname) of device fmted_mac = format_mac(device[u'mac'], separator='_', uppercase=False) return 'ipst_config_%s.cfg' % fmted_mac def _check_config(self, raw_config): if u'tftp_port' not in raw_config: raise RawConfigError('only support configuration via TFTP') def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed for device configuration') def configure(self, device, raw_config): self._check_config(raw_config) self._check_device(device) filename = self._dev_specific_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) self._add_sip_section_info(raw_config) self._add_fkeys(raw_config) path = os.path.join(self._tftpboot_dir, filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) def deconfigure(self, device): path = os.path.join(self._tftpboot_dir, self._dev_specific_filename(device)) try: os.remove(path) except OSError, e: # ignore logger.info('error while removing file: %s', e)
class BaseYealinkPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { u'de_DE': (u'German', u'Germany', u'2'), u'en_US': (u'English', u'United States', u'0'), u'es_ES': (u'Spanish', u'Spain', u'6'), u'fr_FR': (u'French', u'France', u'1'), u'fr_CA': (u'French', u'United States', u'1'), } _SIP_DTMF_MODE = { u'RTP-in-band': u'0', u'RTP-out-of-band': u'1', u'SIP-INFO': u'2', } _SIP_TRANSPORT = { u'udp': u'0', u'tcp': u'1', u'tls': u'2', } _SIP_TRANSPORT_DEF = u'0' _NB_SIP_ACCOUNTS = { u'CP860': 1, u'T19P': 1, u'T19P_E2': 1, u'T20P': 2, u'T21P': 2, u'T21P_E2': 2, u'T23P': 3, u'T23G': 3, u'T27P': 6, u'T27G': 6, u'T29G': 16, u'T32G': 3, u'T38G': 6, u'T40P': 3, u'T41P': 6, u'T41S': 6, u'T42G': 12, u'T42S': 12, u'T46G': 16, u'T46S': 16, u'T48G': 16, u'T48S': 16, u'T49G': 16, u'VP530P': 4, u'W52P': 5, } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders(gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseYealinkHTTPDeviceInfoExtractor() def configure_common(self, raw_config): for filename, fw_filename, tpl_filename in self._COMMON_FILES: tpl = self._tpl_helper.get_template('common/%s' % tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) raw_config[u'XX_fw_filename'] = fw_filename self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): for line_no, line in raw_config[u'sip_lines'].iteritems(): # set line number line[u'XX_line_no'] = int(line_no) # set dtmf inband transfer dtmf_mode = line.get(u'dtmf_mode') or raw_config.get(u'sip_dtmf_mode') if dtmf_mode in self._SIP_DTMF_MODE: line[u'XX_dtmf_type'] = self._SIP_DTMF_MODE[dtmf_mode] # set voicemail if u'voicemail' not in line and u'exten_voicemail' in raw_config: line[u'voicemail'] = raw_config[u'exten_voicemail'] # set proxy_ip if u'proxy_ip' not in line: line[u'proxy_ip'] = raw_config[u'sip_proxy_ip'] # set proxy_port if u'proxy_port' not in line and u'sip_proxy_port' in raw_config: line[u'proxy_port'] = raw_config[u'sip_proxy_port'] def _add_fkeys(self, device, raw_config): funckey_generator = BaseYealinkFunckeyGenerator(device, raw_config) raw_config[u'XX_fkeys'] = funckey_generator.generate() def _add_country_and_lang(self, raw_config): locale = raw_config.get(u'locale') if locale in self._LOCALE: (raw_config[u'XX_lang'], raw_config[u'XX_country'], raw_config[u'XX_handset_lang']) = self._LOCALE[locale] def _format_dst_change(self, dst_change): if dst_change['day'].startswith('D'): return u'%02d/%02d/%02d' % (dst_change['month'], int(dst_change['day'][1:]), dst_change['time'].as_hours) else: week, weekday = map(int, dst_change['day'][1:].split('.')) weekday = tzinform.week_start_on_monday(weekday) return u'%d/%d/%d/%d' % (dst_change['month'], week, weekday, dst_change['time'].as_hours) def _format_tz_info(self, tzinfo): lines = [] lines.append(u'local_time.time_zone = %+d' % min(max(tzinfo['utcoffset'].as_hours, -11), 12)) if tzinfo['dst'] is None: lines.append(u'local_time.summer_time = 0') else: lines.append(u'local_time.summer_time = 1') if tzinfo['dst']['start']['day'].startswith('D'): lines.append(u'local_time.dst_time_type = 0') else: lines.append(u'local_time.dst_time_type = 1') lines.append(u'local_time.start_time = %s' % self._format_dst_change(tzinfo['dst']['start'])) lines.append(u'local_time.end_time = %s' % self._format_dst_change(tzinfo['dst']['end'])) lines.append(u'local_time.offset_time = %s' % tzinfo['dst']['save'].as_minutes) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.warning('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._format_tz_info(tzinfo)
class BaseYealinkPlugin(StandardPlugin): _ENCODING = 'UTF-8' _LOCALE = { u'de_DE': (u'German', u'Germany', u'2'), u'en_US': (u'English', u'United States', u'0'), u'es_ES': (u'Spanish', u'Spain', u'6'), u'fr_FR': (u'French', u'France', u'1'), u'fr_CA': (u'French', u'United States', u'1'), } _SIP_DTMF_MODE = { u'RTP-in-band': u'0', u'RTP-out-of-band': u'1', u'SIP-INFO': u'2', } _SIP_TRANSPORT = { u'udp': u'0', u'tcp': u'1', u'tls': u'2', } _SIP_TRANSPORT_DEF = u'0' _NB_SIP_ACCOUNTS = { u'CP860': 1, u'T19P': 1, u'T19P_E2': 1, u'T20P': 2, u'T21P': 2, u'T21P_E2': 2, u'T22P': 3, u'T23P': 3, u'T23G': 3, u'T26P': 3, u'T27P': 6, u'T27G': 6, u'T28P': 6, u'T29G': 16, u'T32G': 3, u'T38G': 6, u'T40P': 3, u'T41P': 6, u'T41S': 6, u'T42G': 12, u'T42S': 12, u'T46G': 16, u'T46S': 16, u'T48G': 16, u'T48S': 16, u'T49G': 16, u'VP530P': 4, u'W52P': 5, } def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) http_dev_info_extractor = BaseYealinkHTTPDeviceInfoExtractor() def configure_common(self, raw_config): for filename, fw_filename, tpl_filename in self._COMMON_FILES: tpl = self._tpl_helper.get_template('common/%s' % tpl_filename) dst = os.path.join(self._tftpboot_dir, filename) raw_config[u'XX_fw_filename'] = fw_filename self._tpl_helper.dump(tpl, raw_config, dst, self._ENCODING) def _update_sip_lines(self, raw_config): for line_no, line in raw_config[u'sip_lines'].iteritems(): # set line number line[u'XX_line_no'] = int(line_no) # set dtmf inband transfer dtmf_mode = line.get(u'dtmf_mode') or raw_config.get( u'sip_dtmf_mode') if dtmf_mode in self._SIP_DTMF_MODE: line[u'XX_dtmf_type'] = self._SIP_DTMF_MODE[dtmf_mode] # set voicemail if u'voicemail' not in line and u'exten_voicemail' in raw_config: line[u'voicemail'] = raw_config[u'exten_voicemail'] # set proxy_ip if u'proxy_ip' not in line: line[u'proxy_ip'] = raw_config[u'sip_proxy_ip'] # set proxy_port if u'proxy_port' not in line and u'sip_proxy_port' in raw_config: line[u'proxy_port'] = raw_config[u'sip_proxy_port'] def _add_fkeys(self, device, raw_config): funckey_generator = BaseYealinkFunckeyGenerator(device, raw_config) raw_config[u'XX_fkeys'] = funckey_generator.generate() def _add_country_and_lang(self, raw_config): locale = raw_config.get(u'locale') if locale in self._LOCALE: (raw_config[u'XX_lang'], raw_config[u'XX_country'], raw_config[u'XX_handset_lang']) = self._LOCALE[locale] def _format_dst_change(self, dst_change): if dst_change['day'].startswith('D'): return u'%02d/%02d/%02d' % (dst_change['month'], int(dst_change['day'][1:]), dst_change['time'].as_hours) else: week, weekday = map(int, dst_change['day'][1:].split('.')) weekday = tzinform.week_start_on_monday(weekday) return u'%d/%d/%d/%d' % (dst_change['month'], week, weekday, dst_change['time'].as_hours) def _format_tz_info(self, tzinfo): lines = [] lines.append(u'local_time.time_zone = %+d' % min(max(tzinfo['utcoffset'].as_hours, -11), 12)) if tzinfo['dst'] is None: lines.append(u'local_time.summer_time = 0') else: lines.append(u'local_time.summer_time = 1') if tzinfo['dst']['start']['day'].startswith('D'): lines.append(u'local_time.dst_time_type = 0') else: lines.append(u'local_time.dst_time_type = 1') lines.append(u'local_time.start_time = %s' % self._format_dst_change(tzinfo['dst']['start'])) lines.append(u'local_time.end_time = %s' % self._format_dst_change(tzinfo['dst']['end'])) lines.append(u'local_time.offset_time = %s' % tzinfo['dst']['save'].as_minutes) return u'\n'.join(lines) def _add_timezone(self, raw_config): if u'timezone' in raw_config: try: tzinfo = tzinform.get_timezone_info(raw_config[u'timezone']) except tzinform.TimezoneNotFoundError, e: logger.warning('Unknown timezone: %s', e) else: raw_config[u'XX_timezone'] = self._format_tz_info(tzinfo)
class BaseDigiumPlugin(StandardPlugin): _ENCODING = 'UTF-8' _CONTACT_TEMPLATE = 'contact.tpl' def __init__(self, app, plugin_dir, gen_cfg, spec_cfg): StandardPlugin.__init__(self, app, plugin_dir, gen_cfg, spec_cfg) self._tpl_helper = TemplatePluginHelper(plugin_dir) self._digium_dir = os.path.join(self._tftpboot_dir, 'Digium') downloaders = FetchfwPluginHelper.new_downloaders( gen_cfg.get('proxies')) fetchfw_helper = FetchfwPluginHelper(plugin_dir, downloaders) self.services = fetchfw_helper.services() self.http_service = HTTPNoListingFileService(self._tftpboot_dir) dhcp_dev_info_extractor = DigiumDHCPDeviceInfoExtractor() http_dev_info_extractor = DigiumHTTPDeviceInfoExtractor() def configure(self, device, raw_config): self._check_device(device) filename = self._dev_specific_filename(device) contact_filename = self._dev_contact_filename(device) tpl = self._tpl_helper.get_dev_template(filename, device) contact_tpl = self._tpl_helper.get_template(self._CONTACT_TEMPLATE) raw_config['XX_mac'] = self._format_mac(device) raw_config['XX_main_proxy_ip'] = self._get_main_proxy_ip(raw_config) raw_config['XX_funckeys'] = self._transform_funckeys(raw_config) raw_config['XX_lang'] = raw_config.get(u'locale') path = os.path.join(self._digium_dir, filename) contact_path = os.path.join(self._digium_dir, contact_filename) self._tpl_helper.dump(tpl, raw_config, path, self._ENCODING) self._tpl_helper.dump(contact_tpl, raw_config, contact_path, self._ENCODING) def deconfigure(self, device): filenames = [ self._dev_specific_filename(device), self._dev_contact_filename(device) ] for filename in filenames: path = os.path.join(self._digium_dir, filename) try: os.remove(path) except OSError as e: logger.info('error while removing file %s: %s', path, e) if hasattr(synchronize, 'standard_sip_synchronize'): def synchronize(self, device, raw_config): return synchronize.standard_sip_synchronize(device) else: # backward compatibility with older wazo-provd server def synchronize(self, device, raw_config): try: ip = device[u'ip'].encode('ascii') except KeyError: return defer.fail( Exception('IP address needed for device synchronization')) else: sync_service = synchronize.get_sync_service() if sync_service is None or sync_service.TYPE != 'AsteriskAMI': return defer.fail( Exception('Incompatible sync service: %s' % sync_service)) else: return threads.deferToThread(sync_service.sip_notify, ip, 'check-sync') def get_remote_state_trigger_filename(self, device): if u'mac' not in device: return None return self._dev_specific_filename(device) def is_sensitive_filename(self, filename): return bool(self._SENSITIVE_FILENAME_REGEX.match(filename)) def _check_device(self, device): if u'mac' not in device: raise Exception('MAC address needed to configure device') def _get_main_proxy_ip(self, raw_config): if raw_config[u'sip_lines']: line_no = min(int(x) for x in raw_config[u'sip_lines'].keys()) line_no = str(line_no) return raw_config[u'sip_lines'][line_no][u'proxy_ip'] else: return raw_config[u'ip'] def _format_mac(self, device): return format_mac(device[u'mac'], separator='', uppercase=False) _SENSITIVE_FILENAME_REGEX = re.compile(r'^[0-9a-f]{12}\.cfg$') def _dev_specific_filename(self, device): filename = '%s.cfg' % self._format_mac(device) return filename def _dev_contact_filename(self, device): contact_filename = '%s-contacts.xml' % self._format_mac(device) return contact_filename def _transform_funckeys(self, raw_config): return dict((int(k), v) for k, v in raw_config['funckeys'].iteritems())