/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-iphone * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #import "LinphoneCoreSettingsStore.h" #import "Utils.h" #import "PhoneMainView.h" #include "linphone/linphone_tunnel.h" #include "linphone/lpconfig.h" #include #include @implementation LinphoneCoreSettingsStore - (id)init { self = [super init]; if (self) { dict = [[NSMutableDictionary alloc] init]; changedDict = [[NSMutableDictionary alloc] init]; } return self; } - (void)setCString:(const char *)value forKey:(NSString *)key { id obj = @""; if (value) obj = [[NSString alloc] initWithCString:value encoding:NSUTF8StringEncoding]; [self setObject:obj forKey:key]; } - (NSString *)stringForKey:(NSString *)key { return [self objectForKey:key]; } - (void)setObject:(id)value forKey:(NSString *)key { [dict setValue:value forKey:key]; [changedDict setValue:[NSNumber numberWithBool:TRUE] forKey:key]; } - (id)objectForKey:(NSString *)key { return [dict valueForKey:key]; } - (BOOL)valueChangedForKey:(NSString *)key { return [[changedDict valueForKey:key] boolValue]; } + (int)validPort:(int)port { if (port < 0) { return 0; } if (port > 65535) { return 65535; } return port; } + (BOOL)parsePortRange:(NSString *)text minPort:(int *)minPort maxPort:(int *)maxPort { /*NSError *error = nil; *minPort = -1; *maxPort = -1; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"([0-9]+)(([^0-9]+)([0-9]+))?" options:0 error:&error]; if (error != NULL) return FALSE; NSArray *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, [text length])]; if ([matches count] == 1) { NSTextCheckingResult *match = [matches objectAtIndex:0]; bool range = [match rangeAtIndex:2].length > 0; if (!range) { NSRange rangeMinPort = [match rangeAtIndex:1]; *minPort = [LinphoneCoreSettingsStore validPort:[[text substringWithRange:rangeMinPort] intValue]]; *maxPort = *minPort; return TRUE; } else { NSRange rangeMinPort = [match rangeAtIndex:1]; *minPort = [LinphoneCoreSettingsStore validPort:[[text substringWithRange:rangeMinPort] intValue]]; NSRange rangeMaxPort = [match rangeAtIndex:4]; *maxPort = [LinphoneCoreSettingsStore validPort:[[text substringWithRange:rangeMaxPort] intValue]]; if (*minPort > *maxPort) { *minPort = *maxPort; } return TRUE; } }*/ int err; err = sscanf(text.UTF8String, "%i - %i", minPort, maxPort); if (err == 0) { *minPort = *maxPort = -1; } else if (err == 1) { *maxPort = -1; } // Minimal port allowed if (*minPort < 1024) { *minPort = -1; } // Maximal port allowed if (*maxPort > 65535) { *maxPort = -1; } // minPort must be inferior or equal to maxPort if (*minPort > *maxPort) { *maxPort = *minPort; } return TRUE; } - (void)transformCodecsToKeys:(const MSList *)codecs { const MSList *elem = codecs; for (; elem != NULL; elem = elem->next) { PayloadType *pt = (PayloadType *)elem->data; NSString *pref = [LinphoneManager getPreferenceForCodec:pt->mime_type withRate:pt->clock_rate]; if (pref) { bool_t value = linphone_core_payload_type_enabled(LC, pt); [self setBool:value forKey:pref]; } else { LOGW(@"Codec %s/%i supported by core is not shown in iOS app config view.", pt->mime_type, pt->clock_rate); } } } - (void)transformAccountToKeys:(NSString *)username { const MSList *accountList = linphone_core_get_account_list(LC); while (username && accountList && strcmp(username.UTF8String, linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(accountList->data)))) != 0) { accountList = accountList->next; } LinphoneAccount *account = NULL; // default values { [self setBool:NO forKey:@"account_pushnotification_preference"]; [self setObject:@"" forKey:@"account_mandatory_username_preference"]; [self setObject:@"" forKey:@"account_mandatory_domain_preference"]; [self setCString:"" forKey:@"account_display_name_preference"]; [self setObject:@"" forKey:@"account_proxy_preference"]; [self setObject:@"udp" forKey:@"account_transport_preference"]; [self setBool:NO forKey:@"account_outbound_proxy_preference"]; [self setBool:NO forKey:@"account_avpf_preference"]; [self setBool:YES forKey:@"account_is_default_preference"]; [self setBool:YES forKey:@"account_is_enabled_preference"]; [self setCString:"" forKey:@"account_userid_preference"]; [self setCString:"" forKey:@"account_mandatory_password_preference"]; [self setCString:"" forKey:@"ha1_preference"]; [self setCString:"" forKey:@"ha1_algo_preference"]; [self setInteger:-1 forKey:@"account_expire_preference"]; [self setInteger:-1 forKey:@"current_proxy_config_preference"]; [self setCString:"" forKey:@"account_prefix_preference"]; [self setBool:NO forKey:@"account_substitute_+_by_00_preference"]; [self setBool:NO forKey:@"account_ice_preference"]; [self setCString:"" forKey:@"account_stun_preference"]; } if (accountList) { account = accountList->data; LinphoneAccountParams const *accountParams = linphone_account_get_params(account); // root section { BOOL pushEnabled = linphone_account_params_get_push_notification_allowed(accountParams); [self setBool:pushEnabled forKey:@"account_pushnotification_preference"]; const LinphoneAddress *identity_addr = linphone_account_params_get_identity_address(accountParams); const char *server_addr = linphone_account_params_get_server_addr(accountParams); LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, server_addr); if (identity_addr && proxy_addr) { int port = linphone_address_get_port(proxy_addr); [self setCString:linphone_address_get_username(identity_addr) forKey:@"account_mandatory_username_preference"]; [self setCString:linphone_address_get_display_name(identity_addr) forKey:@"account_display_name_preference"]; [self setCString:linphone_address_get_domain(identity_addr) forKey:@"account_mandatory_domain_preference"]; if (strcmp(linphone_address_get_domain(identity_addr), linphone_address_get_domain(proxy_addr)) != 0 || port > 0) { char tmp[256] = {0}; if (port > 0) { snprintf(tmp, sizeof(tmp) - 1, "%s:%i", linphone_address_get_domain(proxy_addr), port); } else snprintf(tmp, sizeof(tmp) - 1, "%s", linphone_address_get_domain(proxy_addr)); [self setCString:tmp forKey:@"account_proxy_preference"]; } const char *tname = "udp"; switch (linphone_address_get_transport(proxy_addr)) { case LinphoneTransportTcp: tname = "tcp"; break; case LinphoneTransportTls: tname = "tls"; break; default: break; } linphone_address_unref(proxy_addr); [self setCString:tname forKey:@"account_transport_preference"]; } [self setBool:(linphone_account_params_get_routes_addresses(accountParams) != NULL) forKey:@"account_outbound_proxy_preference"]; [self setBool:linphone_account_is_avpf_enabled(account) forKey:@"account_avpf_preference"]; [self setBool:linphone_account_params_get_register_enabled(accountParams) forKey:@"account_is_enabled_preference"]; [self setBool:(linphone_core_get_default_account(LC) == account) forKey:@"account_is_default_preference"]; const LinphoneAuthInfo *ai = linphone_core_find_auth_info( LC, NULL, [self stringForKey:@"account_mandatory_username_preference"].UTF8String, [self stringForKey:@"account_mandatory_domain_preference"].UTF8String); if (ai) { [self setCString:linphone_auth_info_get_userid(ai) forKey:@"account_userid_preference"]; [self setCString:linphone_auth_info_get_passwd(ai) forKey:@"account_mandatory_password_preference"]; // hidden but useful if provisioned [self setCString:linphone_auth_info_get_ha1(ai) forKey:@"ha1_preference"]; [self setCString:linphone_auth_info_get_algorithm(ai) forKey:@"ha1_algo_preference"]; } int idx = (int)bctbx_list_index(linphone_core_get_account_list(LC), account); [self setInteger:idx forKey:@"current_proxy_config_preference"]; int expires = linphone_account_params_get_expires(accountParams); [self setInteger:expires forKey:@"account_expire_preference"]; LinphoneNatPolicy *policy = linphone_account_params_get_nat_policy(accountParams); if (policy) { [self setBool:linphone_nat_policy_ice_enabled(policy) forKey:@"account_ice_preference"]; [self setCString:linphone_nat_policy_get_stun_server(policy) forKey:@"account_stun_preference"]; } } // call section { const char *dial_prefix = linphone_account_params_get_international_prefix(accountParams); [self setCString:dial_prefix forKey:@"account_prefix_preference"]; BOOL dial_escape_plus = linphone_account_params_get_dial_escape_plus_enabled(accountParams); [self setBool:dial_escape_plus forKey:@"account_substitute_+_by_00_preference"]; } } } - (void)transformLinphoneCoreToKeys { LinphoneManager *lm = LinphoneManager.instance; // root section { const bctbx_list_t *accounts = linphone_core_get_account_list(LC); size_t count = bctbx_list_size(accounts); for (size_t i = 1; i <= count; i++, accounts = accounts->next) { NSString *key = [NSString stringWithFormat:@"menu_account_%lu", i]; LinphoneAccount *account = (LinphoneAccount *)accounts->data; [self setCString:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account))) forKey:key]; } [self setBool:linphone_core_video_display_enabled(LC) forKey:@"enable_video_preference"]; [self setBool:[LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"] forKey:@"enable_auto_answer_preference"]; [self setBool:[lm lpConfigBoolForKey:@"account_mandatory_advanced_preference"] forKey:@"account_mandatory_advanced_preference"]; } // account section { [self transformAccountToKeys:nil]; } // audio section { [self transformCodecsToKeys:linphone_core_get_audio_codecs(LC)]; [self setFloat:linphone_core_get_playback_gain_db(LC) forKey:@"playback_gain_preference"]; [self setFloat:linphone_core_get_mic_gain_db(LC) forKey:@"microphone_gain_preference"]; [self setInteger:[lm lpConfigIntForKey:@"codec_bitrate_limit" inSection:@"audio" withDefault:kLinphoneAudioVbrCodecDefaultBitrate] forKey:@"audio_codec_bitrate_limit_preference"]; [self setInteger:[lm lpConfigIntForKey:@"voiceproc_preference" withDefault:1] forKey:@"voiceproc_preference"]; [self setInteger:[lm lpConfigIntForKey:@"eq_active" inSection:@"sound" withDefault:0] forKey:@"eq_active"]; } // video section { [self transformCodecsToKeys:linphone_core_get_video_codecs(LC)]; const LinphoneVideoPolicy *pol; pol = linphone_core_get_video_policy(LC); [self setBool:(pol->automatically_initiate) forKey:@"start_video_preference"]; [self setBool:(pol->automatically_accept) forKey:@"accept_video_preference"]; [self setBool:linphone_core_self_view_enabled(LC) forKey:@"self_video_preference"]; BOOL previewEnabled = [lm lpConfigBoolForKey:@"preview_preference" withDefault:YES]; [self setBool:IPAD && previewEnabled forKey:@"preview_preference"]; const char *preset = linphone_core_get_video_preset(LC); [self setCString:preset ? preset : "default" forKey:@"video_preset_preference"]; MSVideoSize vsize = linphone_core_get_preferred_video_size(LC); int index; if ((vsize.width == MS_VIDEO_SIZE_720P_W) && (vsize.height == MS_VIDEO_SIZE_720P_H)) { index = 0; } else if ((vsize.width == MS_VIDEO_SIZE_VGA_W) && (vsize.height == MS_VIDEO_SIZE_VGA_H)) { index = 1; } else { index = 2; } [self setInteger:index forKey:@"video_preferred_size_preference"]; [self setInteger:linphone_core_get_preferred_framerate(LC) forKey:@"video_preferred_fps_preference"]; [self setInteger:linphone_core_get_download_bandwidth(LC) forKey:@"download_bandwidth_preference"]; } // call section { [self setBool:linphone_core_get_use_info_for_dtmf(LC) forKey:@"sipinfo_dtmf_preference"]; [self setBool:linphone_core_get_use_rfc2833_for_dtmf(LC) forKey:@"rfc_dtmf_preference"]; [self setInteger:linphone_core_get_inc_timeout(LC) forKey:@"incoming_call_timeout_preference"]; [self setInteger:linphone_core_get_in_call_timeout(LC) forKey:@"in_call_timeout_preference"]; [self setBool:[lm lpConfigBoolForKey:@"repeat_call_notification"] forKey:@"repeat_call_notification_preference"]; [self setBool:linphone_core_is_media_encryption_mandatory(LC) forKey:@"media_encrption_mandatory_preference"]; [self setBool:[lm lpConfigBoolForKey:@"pref_accept_early_media"] forKey:@"pref_accept_early_media_preference"]; } // chat section { [self setCString:linphone_core_get_file_transfer_server(LC) forKey:@"file_transfer_server_url_preference"]; int maxSize = linphone_core_get_max_size_for_auto_download_incoming_files(LC); [self setObject:maxSize==0 ? @"Always" : (maxSize==-1 ? @"Nerver" : @"Customize") forKey:@"auto_download_mode"]; [self setInteger:maxSize forKey:@"auto_download_incoming_files_max_size"]; [self setBool:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] forKey:@"vfs_enabled_mode"]; [self setBool:[lm lpConfigBoolForKey:@"auto_write_to_gallery_preference" withDefault:YES] forKey:@"auto_write_to_gallery_mode"]; [self setBool:[lm lpConfigBoolForKey:@"ephemeral_feature" withDefault:NO] forKey:@"ephemeral_feature"]; } // network section { LinphoneNatPolicy *np = linphone_core_get_nat_policy(LC); [self setBool:[lm lpConfigBoolForKey:@"edge_opt_preference" withDefault:NO] forKey:@"edge_opt_preference"]; [self setBool:[lm lpConfigBoolForKey:@"wifi_only_preference" withDefault:NO] forKey:@"wifi_only_preference"]; [self setCString:linphone_nat_policy_get_stun_server(np) forKey:@"stun_preference"]; [self setBool:linphone_nat_policy_ice_enabled(np) forKey:@"ice_preference"]; [self setBool:linphone_nat_policy_turn_enabled(np) forKey:@"turn_preference"]; [self setCString:linphone_nat_policy_get_stun_server_username(np) forKey:@"turn_username"]; int random_port_preference = [lm lpConfigIntForKey:@"random_port_preference" withDefault:1]; [self setInteger:random_port_preference forKey:@"random_port_preference"]; int port = [lm lpConfigIntForKey:@"port_preference" withDefault:5060]; [self setInteger:port forKey:@"port_preference"]; { int minPort, maxPort; linphone_core_get_audio_port_range(LC, &minPort, &maxPort); if (minPort != maxPort) [self setObject:[NSString stringWithFormat:@"%d-%d", minPort, maxPort] forKey:@"audio_port_preference"]; else [self setObject:[NSString stringWithFormat:@"%d", minPort] forKey:@"audio_port_preference"]; } { int minPort, maxPort; linphone_core_get_video_port_range(LC, &minPort, &maxPort); if (minPort != maxPort) [self setObject:[NSString stringWithFormat:@"%d-%d", minPort, maxPort] forKey:@"video_port_preference"]; else [self setObject:[NSString stringWithFormat:@"%d", minPort] forKey:@"video_port_preference"]; } [self setBool:linphone_core_ipv6_enabled(LC) forKey:@"use_ipv6"]; LinphoneMediaEncryption menc = linphone_core_get_media_encryption(LC); const char *val; switch (menc) { case LinphoneMediaEncryptionSRTP: val = "SRTP"; break; case LinphoneMediaEncryptionZRTP: val = "ZRTP"; break; case LinphoneMediaEncryptionDTLS: val = "DTLS"; break; case LinphoneMediaEncryptionNone: val = "None"; break; } [self setCString:val forKey:@"media_encryption_preference"]; [self setInteger:linphone_core_get_upload_bandwidth(LC) forKey:@"upload_bandwidth_preference"]; [self setInteger:linphone_core_get_download_bandwidth(LC) forKey:@"download_bandwidth_preference"]; [self setBool:linphone_core_adaptive_rate_control_enabled(LC) forKey:@"adaptive_rate_control_preference"]; } // tunnel section if (linphone_core_tunnel_available()) { LinphoneTunnel *tunnel = linphone_core_get_tunnel(LC); [self setObject:[lm lpConfigStringForKey:@"tunnel_mode_preference" withDefault:@"off"] forKey:@"tunnel_mode_preference"]; const MSList *configs = linphone_tunnel_get_servers(tunnel); if (configs != NULL) { LinphoneTunnelConfig *ltc = (LinphoneTunnelConfig *)configs->data; [self setCString:linphone_tunnel_config_get_host(ltc) forKey:@"tunnel_address_preference"]; [self setInteger:linphone_tunnel_config_get_port(ltc) forKey:@"tunnel_port_preference"]; } else { [self setCString:"" forKey:@"tunnel_address_preference"]; [self setInteger:443 forKey:@"tunnel_port_preference"]; } } // advanced section { [self setObject:[lm lpConfigStringForKey:@"debugenable_preference"] forKey:@"debugenable_preference"]; [self setBool:ANIMATED forKey:@"animations_preference"]; [self setBool:[lm lpConfigBoolForKey:@"backgroundmode_preference"] forKey:@"backgroundmode_preference"]; [self setBool:[lm lpConfigBoolForKey:@"start_at_boot_preference"] forKey:@"start_at_boot_preference"]; [self setBool:[lm lpConfigBoolForKey:@"autoanswer_notif_preference"] forKey:@"autoanswer_notif_preference"]; [self setBool:[lm lpConfigBoolForKey:@"show_msg_in_notif" withDefault:YES] forKey:@"show_msg_in_notif"]; [self setBool:[lm lpConfigBoolForKey:@"use_rls_presence" withDefault:YES] forKey:@"use_rls_presence"]; [self setBool:[lm lpConfigBoolForKey:@"enable_first_login_view_preference"] forKey:@"enable_first_login_view_preference"]; LinphoneAddress *parsed = linphone_core_get_primary_contact_parsed(LC); if (parsed != NULL) { [self setCString:linphone_address_get_display_name(parsed) forKey:@"primary_displayname_preference"]; [self setCString:linphone_address_get_username(parsed) forKey:@"primary_username_preference"]; linphone_address_unref(parsed); } } changedDict = [[NSMutableDictionary alloc] init]; // Post event NSDictionary *eventDic = [NSDictionary dictionaryWithObject:self forKey:@"settings"]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneLogsUpdate object:self userInfo:eventDic]; } - (void)alertAccountError:(NSString *)error { UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", nil) message:error preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [errView addAction:defaultAction]; [PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; } - (void)synchronizeAccounts { LOGI(@"Account changed, synchronizing."); LinphoneManager *lm = LinphoneManager.instance; LinphoneAccount *account = NULL; LinphoneAccountParams *newAccountParams = NULL; NSString *error = nil; int port_preference = [self integerForKey:@"port_preference"]; BOOL random_port_preference = [self boolForKey:@"random_port_preference"]; [lm lpConfigSetInt:random_port_preference forKey:@"random_port_preference"]; if (random_port_preference) { port_preference = -1; } LCSipTransports transportValue = {port_preference, port_preference, -1, -1}; // will also update the sip_*_port section of the config if (linphone_core_set_sip_transports(LC, &transportValue)) { LOGE(@"cannot set transport"); } port_preference = linphone_core_get_sip_port(LC); [self setInteger:port_preference forKey:@"port_preference"]; // Update back preference BOOL enable_ipv6 = [self boolForKey:@"use_ipv6"]; [lm lpConfigSetBool:enable_ipv6 forKey:@"use_ipv6" inSection:@"sip"]; LOGD(@"%@ IPV6", enable_ipv6 ? @"ENABLING" : @"DISABLING"); linphone_core_enable_ipv6(LC, enable_ipv6); // configure sip account // mandatory parameters NSString *username = [self stringForKey:@"account_mandatory_username_preference"]; NSString *displayName = [self stringForKey:@"account_display_name_preference"]; NSString *userID = [self stringForKey:@"account_userid_preference"]; NSString *domain = [self stringForKey:@"account_mandatory_domain_preference"]; NSString *transport = [self stringForKey:@"account_transport_preference"]; NSString *accountHa1 = [self stringForKey:@"ha1_preference"]; NSString *accountPassword = [self stringForKey:@"account_mandatory_password_preference"]; NSString *accountAlgoPreference = [self stringForKey:@"ha1_algo_preference"]; BOOL isOutboundProxy = [self boolForKey:@"account_outbound_proxy_preference"]; BOOL use_avpf = [self boolForKey:@"account_avpf_preference"]; BOOL is_default = [self boolForKey:@"account_is_default_preference"]; BOOL is_enabled = [self boolForKey:@"account_is_enabled_preference"]; BOOL use_ice = [self boolForKey:@"account_ice_preference"]; NSString *stun_preference = [self stringForKey:@"account_stun_preference"]; if (username && [username length] > 0 && domain && [domain length] > 0) { int expire = [self integerForKey:@"account_expire_preference"]; BOOL pushnotification = [self boolForKey:@"account_pushnotification_preference"]; NSString *prefix = [self stringForKey:@"account_prefix_preference"]; NSString *proxyAddress = [self stringForKey:@"account_proxy_preference"]; if ((!proxyAddress || [proxyAddress length] < 1) && domain) { proxyAddress = domain; } if (![proxyAddress hasPrefix:@"sip:"] && ![proxyAddress hasPrefix:@"sips:"]) { proxyAddress = [NSString stringWithFormat:@"sip:%@", proxyAddress]; } LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, proxyAddress.UTF8String); if (proxy_addr) { LinphoneTransportType type = LinphoneTransportUdp; if ([transport isEqualToString:@"tcp"]) type = LinphoneTransportTcp; else if ([transport isEqualToString:@"tls"]) type = LinphoneTransportTls; linphone_address_set_transport(proxy_addr, type); } account = bctbx_list_nth_data(linphone_core_get_account_list(LC), [self integerForKey:@"current_proxy_config_preference"]); // if account was deleted, it is not present anymore if (account == NULL) goto bad_proxy; LinphoneAddress *linphoneAddress = linphone_core_interpret_url(LC, "sip:user@domain.com"); linphone_address_set_username(linphoneAddress, username.UTF8String); if ([LinphoneManager.instance lpConfigBoolForKey:@"use_phone_number" inSection:@"assistant"]) { char *user = linphone_account_normalize_phone_number(account, username.UTF8String); if (user) { linphone_address_set_username(linphoneAddress, user); ms_free(user); } } linphone_address_set_domain(linphoneAddress, [domain UTF8String]); linphone_address_set_display_name(linphoneAddress, (displayName.length ? displayName.UTF8String : NULL)); const char *password = [accountPassword UTF8String]; const char *ha1 = [accountHa1 UTF8String]; newAccountParams = linphone_account_params_clone(linphone_account_get_params(account)); if (linphone_account_params_set_identity_address(newAccountParams, linphoneAddress) == -1) { error = NSLocalizedString(@"Invalid username or domain", nil); goto bad_proxy; } // use proxy as route if outbound_proxy is enabled if (linphone_account_params_set_server_address(newAccountParams, proxy_addr) == -1) { error = NSLocalizedString(@"Invalid proxy address", nil); goto bad_proxy; } if (linphone_account_params_set_routes_addresses(newAccountParams, isOutboundProxy ? bctbx_list_new((void*)proxy_addr) : NULL) == -1) { error = NSLocalizedString(@"Invalid route", nil); goto bad_proxy; } LinphoneNatPolicy *policy = linphone_account_params_get_nat_policy(newAccountParams) ?: linphone_core_create_nat_policy(LC); linphone_nat_policy_enable_stun(policy, use_ice); // We always use STUN with ICE linphone_nat_policy_enable_ice(policy, use_ice); linphone_nat_policy_set_stun_server(policy, stun_preference.UTF8String); linphone_account_params_set_nat_policy(newAccountParams, policy); if ([prefix length] > 0) { linphone_account_params_set_international_prefix(newAccountParams, [prefix UTF8String]); } if ([self objectForKey:@"account_substitute_+_by_00_preference"]) { bool substitute_plus_by_00 = [self boolForKey:@"account_substitute_+_by_00_preference"]; linphone_account_params_set_dial_escape_plus_enabled(newAccountParams, substitute_plus_by_00); } // use empty string "" instead of NULL to avoid being overwritten by default proxy config values linphone_account_params_set_push_notification_allowed(newAccountParams, pushnotification); linphone_account_params_set_remote_push_notification_allowed(newAccountParams, pushnotification); linphone_account_params_set_register_enabled(newAccountParams, is_enabled); linphone_account_params_set_avpf_mode(newAccountParams, use_avpf); linphone_account_params_set_expires(newAccountParams, expire); linphone_account_set_params(account, newAccountParams); linphone_account_params_unref(newAccountParams); if (is_default) { linphone_core_set_default_account(LC, account); } else if (linphone_core_get_default_account(LC) == account) { linphone_core_set_default_account(LC, NULL); } LinphoneAuthInfo *proxyAi = (LinphoneAuthInfo *)linphone_account_find_auth_info(account); char *realm; if (proxyAi) { realm = ms_strdup(linphone_auth_info_get_realm(proxyAi)); } else { realm = NULL; } // modify auth info only after finishing editting the proxy config, so that // UNREGISTER succeed if (proxyAi) { linphone_core_remove_auth_info(LC, proxyAi); } if (strcmp(password,"") == 0) { password = NULL; } char *identity = linphone_address_as_string(linphoneAddress); LinphoneAddress *from = linphone_core_interpret_url(LC, identity); ms_free(identity); if (from) { const char *userid_str = (userID != nil) ? [userID UTF8String] : NULL; LinphoneAuthInfo *info; if (password) { info = linphone_auth_info_new(linphone_address_get_username(from), userid_str, password, NULL, linphone_account_params_get_realm(newAccountParams), linphone_account_params_get_domain(newAccountParams)); } else { info = linphone_auth_info_new_for_algorithm(linphone_address_get_username(from) , userid_str , NULL , ha1 , realm ? realm : linphone_account_params_get_realm(newAccountParams), linphone_account_params_get_domain(newAccountParams), [accountAlgoPreference UTF8String]); } linphone_address_unref(from); linphone_core_add_auth_info(LC, info); linphone_auth_info_destroy(info); ms_free(realm); } bad_proxy: if (linphoneAddress) linphone_address_destroy(linphoneAddress); // in case of error, show an alert to the user if (error != nil) { if (newAccountParams != NULL) { // If we get here, then we're also sure that Account != NULL linphone_account_params_unref(newAccountParams); } UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", nil) message:error preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [errView addAction:defaultAction]; [PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; } } // reload address book to prepend proxy config domain to contacts' phone number [[LinphoneManager.instance fastAddressBook] fetchContactsInBackGroundThread]; } - (void)synchronizeCodecs:(const MSList *)codecs { PayloadType *pt; const MSList *elem; for (elem = codecs; elem != NULL; elem = elem->next) { pt = (PayloadType *)elem->data; NSString *pref = [LinphoneManager getPreferenceForCodec:pt->mime_type withRate:pt->clock_rate]; linphone_core_enable_payload_type(LC, pt, [self boolForKey:pref]); } } - (BOOL)synchronize { @try { LinphoneManager *lm = LinphoneManager.instance; // root section BOOL account_changed = NO; for (NSString *key in self->changedDict) { if ([key hasPrefix:@"account_"] && [self valueChangedForKey:key]) { account_changed = YES; break; } } account_changed |= [self valueChangedForKey:@"port_preference"]; account_changed |= [self valueChangedForKey:@"random_port_preference"]; account_changed |= [self valueChangedForKey:@"use_ipv6"]; if (account_changed) [self synchronizeAccounts]; bool enableVideo = [self boolForKey:@"enable_video_preference"]; linphone_core_enable_video_capture(LC, enableVideo); linphone_core_enable_video_display(LC, enableVideo); bool enableAutoAnswer = [self boolForKey:@"enable_auto_answer_preference"]; [LinphoneManager.instance lpConfigSetBool:enableAutoAnswer forKey:@"auto_answer"]; // audio section [self synchronizeCodecs:linphone_core_get_audio_codecs(LC)]; float playback_gain = [self floatForKey:@"playback_gain_preference"]; linphone_core_set_playback_gain_db(LC, playback_gain); float mic_gain = [self floatForKey:@"microphone_gain_preference"]; linphone_core_set_mic_gain_db(LC, mic_gain); [lm lpConfigSetInt:[self integerForKey:@"audio_codec_bitrate_limit_preference"] forKey:@"codec_bitrate_limit" inSection:@"audio"]; BOOL voice_processing = [self boolForKey:@"voiceproc_preference"]; [lm lpConfigSetInt:voice_processing forKey:@"voiceproc_preference"]; BOOL equalizer = [self boolForKey:@"eq_active"]; [lm lpConfigSetBool:equalizer forKey:@"eq_active" inSection:@"sound"]; [LinphoneManager.instance configureVbrCodecs]; NSString *au_device = @"AU: Audio Unit Receiver"; if (!voice_processing) au_device = @"AU: Audio Unit NoVoiceProc"; linphone_core_set_capture_device(LC, [au_device UTF8String]); linphone_core_set_playback_device(LC, [au_device UTF8String]); // video section [self synchronizeCodecs:linphone_core_get_video_codecs(LC)]; LinphoneVideoPolicy policy; policy.automatically_initiate = [self boolForKey:@"start_video_preference"]; policy.automatically_accept = [self boolForKey:@"accept_video_preference"]; linphone_core_set_video_policy(LC, &policy); linphone_core_enable_self_view(LC, [self boolForKey:@"self_video_preference"]); BOOL preview_preference = IPAD && [self boolForKey:@"preview_preference"]; [lm lpConfigSetInt:preview_preference forKey:@"preview_preference"]; NSString *videoPreset = [self stringForKey:@"video_preset_preference"]; linphone_core_set_video_preset(LC, [videoPreset UTF8String]); MSVideoSize vsize; switch ([self integerForKey:@"video_preferred_size_preference"]) { case 0: MS_VIDEO_SIZE_ASSIGN(vsize, 720P); break; case 1: MS_VIDEO_SIZE_ASSIGN(vsize, VGA); break; case 2: default: MS_VIDEO_SIZE_ASSIGN(vsize, QVGA); break; } linphone_core_set_preferred_video_size(LC, vsize); if (![videoPreset isEqualToString:@"custom"]) { [self setInteger:0 forKey:@"video_preferred_fps_preference"]; [self setInteger:0 forKey:@"download_bandwidth_preference"]; } linphone_core_set_preferred_framerate(LC, [self integerForKey:@"video_preferred_fps_preference"]); linphone_core_set_download_bandwidth(LC, [self integerForKey:@"download_bandwidth_preference"]); linphone_core_set_upload_bandwidth(LC, [self integerForKey:@"download_bandwidth_preference"]); // call section linphone_core_set_use_rfc2833_for_dtmf(LC, [self boolForKey:@"rfc_dtmf_preference"]); linphone_core_set_use_info_for_dtmf(LC, [self boolForKey:@"sipinfo_dtmf_preference"]); linphone_core_set_inc_timeout(LC, [self integerForKey:@"incoming_call_timeout_preference"]); linphone_core_set_in_call_timeout(LC, [self integerForKey:@"in_call_timeout_preference"]); [lm lpConfigSetString:[self stringForKey:@"voice_mail_uri_preference"] forKey:@"voice_mail_uri"]; [lm lpConfigSetBool:[self boolForKey:@"repeat_call_notification_preference"] forKey:@"repeat_call_notification"]; [lm lpConfigSetBool:[self boolForKey:@"pref_accept_early_media_preference"] forKey:@"pref_accept_early_media"]; linphone_core_set_media_encryption_mandatory(LC, [self boolForKey:@"media_encrption_mandatory_preference"]); linphone_core_set_file_transfer_server(LC, [self stringForKey:@"file_transfer_server_url_preference"].UTF8String); int maxSize; NSString *downloadMode = [self stringForKey:@"auto_download_mode"]; if ([downloadMode isEqualToString:@"Never"]) { maxSize = -1; } else if ([downloadMode isEqualToString:@"Always"]) { maxSize = 0; } else { maxSize = [[self stringForKey:@"auto_download_incoming_files_max_size"] intValue]; } linphone_core_set_max_size_for_auto_download_incoming_files(LC, maxSize); [lm lpConfigSetString:[self stringForKey:@"auto_download_mode"] forKey:@"auto_download_mode"]; BOOL vfsPrefEnabled = [self boolForKey:@"vfs_enabled_mode"] || [VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]; if (vfsPrefEnabled && ![VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) { if (TARGET_IPHONE_SIMULATOR) { LOGW(@"[VFS] Can not active for simulators."); [VFSUtil setVfsEnabbledWithEnabled:false groupName:kLinphoneMsgNotificationAppGroupId]; [self setBool:FALSE forKey:@"vfs_enabled_mode"]; } else if (![VFSUtil activateVFSForFirstTime:true]) { [VFSUtil log:@"[VFS] Error unable to activate ! Warning disabling VFS enabled preference." :OS_LOG_TYPE_ERROR]; [VFSUtil setVfsEnabbledWithEnabled:false groupName:kLinphoneMsgNotificationAppGroupId]; [self setBool:FALSE forKey:@"vfs_enabled_mode"]; } else { [VFSUtil setVfsEnabbledWithEnabled:true groupName:kLinphoneMsgNotificationAppGroupId]; [self setBool:TRUE forKey:@"vfs_enabled_mode"]; } } [lm lpConfigSetBool:[self boolForKey:@"auto_write_to_gallery_mode"] forKey:@"auto_write_to_gallery_preference"]; [lm lpConfigSetBool:[self boolForKey:@"ephemeral_feature"] forKey:@"ephemeral_feature"]; // network section BOOL edgeOpt = [self boolForKey:@"edge_opt_preference"]; [lm lpConfigSetInt:edgeOpt forKey:@"edge_opt_preference"]; BOOL wifiOnly = [self boolForKey:@"wifi_only_preference"]; [lm lpConfigSetInt:wifiOnly forKey:@"wifi_only_preference"]; LinphoneNatPolicy *LNP = linphone_core_get_nat_policy(LC); BOOL ice_preference = [self boolForKey:@"ice_preference"]; linphone_nat_policy_enable_ice(LNP, ice_preference); linphone_nat_policy_enable_turn(LNP, [self boolForKey:@"turn_preference"]); NSString *stun_server = [self stringForKey:@"stun_preference"]; if ([stun_server length] > 0) { linphone_nat_policy_set_stun_server(LNP, [stun_server UTF8String]); linphone_nat_policy_enable_stun(LNP, ice_preference); /*we always use STUN with ICE*/ NSString *turn_username = [self stringForKey:@"turn_username"]; NSString *turn_password = [self stringForKey:@"turn_password"]; if ([turn_username length] > 0) { const LinphoneAuthInfo *turnAuthInfo = nil; if ([turn_password length] > 0){ turnAuthInfo = linphone_auth_info_new([turn_username UTF8String], NULL, [turn_password UTF8String], NULL, NULL, NULL); linphone_core_add_auth_info(LC, turnAuthInfo); } else turnAuthInfo = linphone_core_find_auth_info(LC, NULL, [turn_username UTF8String], NULL); linphone_nat_policy_set_stun_server_username(LNP, [turn_username UTF8String]); } } else { linphone_nat_policy_enable_stun(LNP, FALSE); linphone_nat_policy_set_stun_server(LNP, NULL); } linphone_core_set_nat_policy(LC, LNP); NSString *audio_port_preference = [self stringForKey:@"audio_port_preference"]; int audioMinPort, audioMaxPort; [LinphoneCoreSettingsStore parsePortRange:audio_port_preference minPort:&audioMinPort maxPort:&audioMaxPort]; linphone_core_set_audio_port_range(LC, audioMinPort, audioMaxPort); NSString *video_port_preference = [self stringForKey:@"video_port_preference"]; int videoMinPort, videoMaxPort; [LinphoneCoreSettingsStore parsePortRange:video_port_preference minPort:&videoMinPort maxPort:&videoMaxPort]; linphone_core_set_video_port_range(LC, videoMinPort, videoMaxPort); NSString *menc = [self stringForKey:@"media_encryption_preference"]; if (menc && [menc compare:@"SRTP"] == NSOrderedSame) linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionSRTP); else if (menc && [menc compare:@"ZRTP"] == NSOrderedSame) linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionZRTP); else if (menc && [menc compare:@"DTLS"] == NSOrderedSame) linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionDTLS); else linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionNone); linphone_core_enable_adaptive_rate_control(LC, [self boolForKey:@"adaptive_rate_control_preference"]); // tunnel section if (linphone_core_tunnel_available()) { NSString *lTunnelPrefMode = [self stringForKey:@"tunnel_mode_preference"]; NSString *lTunnelPrefAddress = [self stringForKey:@"tunnel_address_preference"]; int lTunnelPrefPort = [self integerForKey:@"tunnel_port_preference"]; LinphoneTunnel *tunnel = linphone_core_get_tunnel(LC); LinphoneTunnelMode mode = LinphoneTunnelModeDisable; int lTunnelPort = 443; if (lTunnelPrefPort) lTunnelPort = lTunnelPrefPort; linphone_tunnel_clean_servers(tunnel); if (lTunnelPrefAddress && [lTunnelPrefAddress length]) { LinphoneTunnelConfig *ltc = linphone_tunnel_config_new(); linphone_tunnel_config_set_host(ltc, [lTunnelPrefAddress UTF8String]); linphone_tunnel_config_set_port(ltc, lTunnelPort); linphone_tunnel_add_server(tunnel, ltc); if ([lTunnelPrefMode isEqualToString:@"off"]) mode = LinphoneTunnelModeDisable; else if ([lTunnelPrefMode isEqualToString:@"on"]) mode = LinphoneTunnelModeEnable; else if ([lTunnelPrefMode isEqualToString:@"auto"]) mode = LinphoneTunnelModeAuto; else LOGE(@"Unexpected tunnel mode [%s]", [lTunnelPrefMode UTF8String]); } [lm lpConfigSetString:lTunnelPrefMode forKey:@"tunnel_mode_preference"]; linphone_tunnel_set_mode(tunnel, mode); } // advanced section BOOL animations = [self boolForKey:@"animations_preference"]; [lm lpConfigSetInt:animations forKey:@"animations_preference"]; UIDevice *device = [UIDevice currentDevice]; BOOL backgroundSupported = [device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported]; BOOL isbackgroundModeEnabled = backgroundSupported && [self boolForKey:@"backgroundmode_preference"]; [lm lpConfigSetInt:isbackgroundModeEnabled forKey:@"backgroundmode_preference"]; [lm lpConfigSetInt:[self integerForKey:@"start_at_boot_preference"] forKey:@"start_at_boot_preference"]; [lm lpConfigSetInt:[self integerForKey:@"autoanswer_notif_preference"] forKey:@"autoanswer_notif_preference"]; [lm lpConfigSetInt:[self integerForKey:@"show_msg_in_notif"] forKey:@"show_msg_in_notif"]; if ([self integerForKey:@"use_rls_presence"]) { [self setInteger:0 forKey:@"use_rls_presence"]; NSString *rls_uri = [lm lpConfigStringForKey:@"rls_uri" inSection:@"sip" withDefault:@"sips:rls@sip.linphone.org"]; LinphoneAddress *rls_addr = linphone_address_new(rls_uri.UTF8String); const char *rls_domain = linphone_address_get_domain(rls_addr); const MSList *accounts = linphone_core_get_account_list(LC); if (!accounts) // Enable it if no proxy config for first launch of app [self setInteger:1 forKey:@"use_rls_presence"]; else { while (accounts) { const char *proxy_domain = linphone_account_params_get_domain(linphone_account_get_params(accounts->data)); if (strcmp(rls_domain, proxy_domain) == 0) { [self setInteger:1 forKey:@"use_rls_presence"]; break; } accounts = accounts->next; } } linphone_address_unref(rls_addr); } [lm lpConfigSetInt:[self integerForKey:@"use_rls_presence"] forKey:@"use_rls_presence"]; linphone_core_enable_friend_list_subscription(LC, [self integerForKey:@"use_rls_presence"]); BOOL firstloginview = [self boolForKey:@"enable_first_login_view_preference"]; [lm lpConfigSetInt:firstloginview forKey:@"enable_first_login_view_preference"]; NSString *displayname = [self stringForKey:@"primary_displayname_preference"]; NSString *username = [self stringForKey:@"primary_username_preference"]; LinphoneAddress *parsed = linphone_core_get_primary_contact_parsed(LC); if (parsed != NULL) { linphone_address_set_display_name(parsed, [displayname UTF8String]); linphone_address_set_username(parsed, [username UTF8String]); char *contact = linphone_address_as_string(parsed); linphone_core_set_primary_contact(LC, contact); ms_free(contact); linphone_address_destroy(parsed); } [lm lpConfigSetInt:[self integerForKey:@"account_mandatory_advanced_preference"] forKey:@"account_mandatory_advanced_preference"]; changedDict = [[NSMutableDictionary alloc] init]; // Post event NSDictionary *eventDic = [NSDictionary dictionaryWithObject:self forKey:@"settings"]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneSettingsUpdate object:self userInfo:eventDic]; return YES; } @catch (NSException *exception) { // may happen when application is terminated, since we are destroying the core if ([exception.name isEqualToString:@"LinphoneCoreException"]) { LOGE(@"Core already destroyed, settings not synchronized"); return NO; } LOGE(@"Uncaught exception : %@", exception.description); abort(); } return NO; } - (void)removeAccount { LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC), [self integerForKey:@"current_proxy_config_preference"]); const MSList *lists = linphone_core_get_friends_lists(LC); while (lists) { linphone_friend_list_enable_subscriptions(lists->data, FALSE); linphone_friend_list_update_subscriptions(lists->data); lists = lists->next; } BOOL isDefault = (linphone_core_get_default_account(LC) == account); const LinphoneAuthInfo *ai = linphone_account_find_auth_info(account); linphone_core_remove_account(LC, account); if (ai) { // Friend list unsubscription above is not instantanous, so give a bit of a time margin before finishing the removal of the auth info dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ linphone_core_remove_auth_info(LC, ai); }); } [self setInteger:-1 forKey:@"current_proxy_config_preference"]; if (isDefault) { // if we removed the default proxy config, set another one instead if (linphone_core_get_account_list(LC) != NULL) { linphone_core_set_default_account(LC, (LinphoneAccount *)(linphone_core_get_account_list(LC)->data)); } } [self transformLinphoneCoreToKeys]; } @end