diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index 94ede41ca7..a3f3d6bc09 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -533,6 +533,7 @@ -(void) presentChatWithContact:(MLContact*) contact -(void) presentChatWithContact:(MLContact*) contact andCompletion:(monal_id_block_t _Nullable) completion { + DDLogVerbose(@"presenting chat with contact: %@, stacktrace: %@", contact, [NSThread callStackSymbols]); dispatch_async(dispatch_get_main_queue(), ^{ DDLogVerbose(@"presenting chat with contact: %@", contact); [self dismissCompleteViewChainWithAnimation:YES andCompletion:^{ diff --git a/Monal/Classes/ContactResources.swift b/Monal/Classes/ContactResources.swift index 764202b000..65f133751b 100644 --- a/Monal/Classes/ContactResources.swift +++ b/Monal/Classes/ContactResources.swift @@ -94,7 +94,7 @@ struct ContactResources: View { if softwareInfo.fromJid == contact.obj.contactJid && xmppAccount.accountNo == contact.obj.accountId { DispatchQueue.main.async { DDLogVerbose("Successfully matched software version info update to current contact: \(contact.obj)") - self.contactVersionInfos[softwareInfo.resource] = ObservableKVOWrapper(softwareInfo) + self.contactVersionInfos[softwareInfo.resource ?? ""] = ObservableKVOWrapper(softwareInfo) } } } diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index bb162046e7..84d6672b2f 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -651,7 +651,7 @@ -(void) setResourceOnline:(XMPPPresence*) presenceObj forAccount:(NSNumber*) acc }]; } --(MLContactSoftwareVersionInfo* _Nullable) getSoftwareVersionInfoForContact:(NSString*)contact resource:(NSString*)resource andAccount:(NSNumber*)accountNo +-(MLContactSoftwareVersionInfo* _Nullable) getSoftwareVersionInfoForContact:(NSString*) contact resource:(NSString*) resource andAccount:(NSNumber*) accountNo { if(accountNo == nil) return nil; @@ -668,14 +668,14 @@ -(MLContactSoftwareVersionInfo* _Nullable) getSoftwareVersionInfoForContact:(NSS } } --(void) setSoftwareVersionInfoForContact:(NSString*)contact - resource:(NSString*)resource - andAccount:(NSNumber*)account +-(void) setSoftwareVersionInfoForContact:(NSString*) contact + resource:(NSString*) resource + andAccount:(NSNumber*) account withSoftwareInfo:(MLContactSoftwareVersionInfo*) newSoftwareInfo { [self.db voidWriteTransaction:^{ NSString* query = @"update buddy_resources set platform_App_Name=?, platform_App_Version=?, platform_OS=? where buddy_id in (select buddy_id from buddylist where account_id=? and buddy_name=?) and resource=?"; - NSArray* params = @[newSoftwareInfo.appName, newSoftwareInfo.appVersion, newSoftwareInfo.platformOs, account, contact, resource]; + NSArray* params = @[nilWrapper(newSoftwareInfo.appName), nilWrapper(newSoftwareInfo.appVersion), nilWrapper(newSoftwareInfo.platformOs), account, contact, resource]; [self.db executeNonQuery:query andArguments:params]; }]; } diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index 787b9eca16..b84c53f24e 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -50,6 +50,7 @@ #import "MLUDPLogger.h" #import "MLStreamRedirect.h" #import "commithash.h" +#import "MLContactSoftwareVersionInfo.h" @import UserNotifications; @import CoreImage; @@ -643,6 +644,7 @@ +(id) unserializeData:(NSData*) data [MLMessage class], [NSURL class], [OmemoState class], + [MLContactSoftwareVersionInfo class], ]] fromData:data error:&error]; if(error) @throw [NSException exceptionWithName:@"NSError" reason:[NSString stringWithFormat:@"%@", error] userInfo:@{@"error": error}]; diff --git a/Monal/Classes/MLContactSoftwareVersionInfo.h b/Monal/Classes/MLContactSoftwareVersionInfo.h index d209650ebb..dc6eb71ead 100644 --- a/Monal/Classes/MLContactSoftwareVersionInfo.h +++ b/Monal/Classes/MLContactSoftwareVersionInfo.h @@ -10,16 +10,17 @@ NS_ASSUME_NONNULL_BEGIN -@interface MLContactSoftwareVersionInfo : NSObject +@interface MLContactSoftwareVersionInfo : NSObject @property (nonatomic, copy) NSString* fromJid; -@property (nonatomic, copy) NSString* resource; +@property (nonatomic, copy) NSString* _Nullable resource; @property (nonatomic, copy) NSString* _Nullable appName; @property (nonatomic, copy) NSString* _Nullable appVersion; @property (nonatomic, copy) NSString* _Nullable platformOs; @property (nonatomic, copy) NSDate* _Nullable lastInteraction; --(instancetype) initWithJid:(NSString*) jid andRessource:(NSString*) resource andAppName:(NSString* _Nullable) appName andAppVersion:(NSString* _Nullable) appVersion andPlatformOS:(NSString* _Nullable) platformOs andLastInteraction:(NSDate* _Nullable) lastInteraction; ++(BOOL) supportsSecureCoding; +-(instancetype) initWithJid:(NSString*) jid andRessource:(NSString* _Nullable) resource andAppName:(NSString* _Nullable) appName andAppVersion:(NSString* _Nullable) appVersion andPlatformOS:(NSString* _Nullable) platformOs andLastInteraction:(NSDate* _Nullable) lastInteraction; -(BOOL) isEqual:(id _Nullable) object; -(NSUInteger) hash; diff --git a/Monal/Classes/MLContactSoftwareVersionInfo.m b/Monal/Classes/MLContactSoftwareVersionInfo.m index ef4a1c9d9e..234407e520 100644 --- a/Monal/Classes/MLContactSoftwareVersionInfo.m +++ b/Monal/Classes/MLContactSoftwareVersionInfo.m @@ -14,7 +14,12 @@ @interface MLContactSoftwareVersionInfo () @implementation MLContactSoftwareVersionInfo --(instancetype) initWithJid:(NSString*) jid andRessource:(NSString*) resource andAppName:(NSString* _Nullable) appName andAppVersion:(NSString* _Nullable) appVersion andPlatformOS:(NSString* _Nullable) platformOs andLastInteraction:(NSDate* _Nullable) lastInteraction ++(BOOL) supportsSecureCoding +{ + return YES; +} + +-(instancetype) initWithJid:(NSString*) jid andRessource:(NSString* _Nullable) resource andAppName:(NSString* _Nullable) appName andAppVersion:(NSString* _Nullable) appVersion andPlatformOS:(NSString* _Nullable) platformOs andLastInteraction:(NSDate* _Nullable) lastInteraction { self = [super init]; self.fromJid = jid; @@ -26,6 +31,28 @@ -(instancetype) initWithJid:(NSString*) jid andRessource:(NSString*) resource an return self; } +-(void) encodeWithCoder:(NSCoder*) coder +{ + [coder encodeObject:self.fromJid forKey:@"fromJid"]; + [coder encodeObject:self.resource forKey:@"resource"]; + [coder encodeObject:self.appName forKey:@"appName"]; + [coder encodeObject:self.appVersion forKey:@"appVersion"]; + [coder encodeObject:self.platformOs forKey:@"platformOs"]; + [coder encodeObject:self.lastInteraction forKey:@"lastInteraction"]; +} + +-(instancetype) initWithCoder:(NSCoder*) coder +{ + self = [self init]; + self.fromJid = [coder decodeObjectForKey:@"fromJid"]; + self.resource = [coder decodeObjectForKey:@"resource"]; + self.appName = [coder decodeObjectForKey:@"appName"]; + self.appVersion = [coder decodeObjectForKey:@"appVersion"]; + self.platformOs = [coder decodeObjectForKey:@"platformOs"]; + self.lastInteraction = [coder decodeObjectForKey:@"lastInteraction"]; + return self; +} + -(BOOL) isEqual:(id _Nullable) object { if(object == nil || self == object) @@ -43,6 +70,8 @@ -(NSUInteger) hash -(NSString*) id { + if(self.resource == nil) + return [NSString stringWithFormat:@"%@", self.fromJid]; return [NSString stringWithFormat:@"%@/%@", self.fromJid, self.resource]; } diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index f4c277432b..ee1ba2f680 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -558,7 +558,7 @@ +(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode } if(!account.connectionProperties.conferenceServer && [features containsObject:@"http://jabber.org/protocol/muc"]) - account.connectionProperties.conferenceServer = iqNode.from; + account.connectionProperties.conferenceServer = iqNode.fromUser; $$ $$class_handler(handleServerDiscoItems, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode)) @@ -708,35 +708,27 @@ +(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode +(void) iqVersionResult:(XMPPIQ*) iqNode forAccount:(xmpp*) account { NSString* iqAppName = [iqNode findFirst:@"{jabber:iq:version}query/name#"]; - if(!iqAppName) - iqAppName = @""; NSString* iqAppVersion = [iqNode findFirst:@"{jabber:iq:version}query/version#"]; - if(!iqAppVersion) - iqAppVersion = @""; NSString* iqPlatformOS = [iqNode findFirst:@"{jabber:iq:version}query/os#"]; - if(!iqPlatformOS) - iqPlatformOS = @""; - MLContactSoftwareVersionInfo* versionDBInfo = [[DataLayer sharedInstance] getSoftwareVersionInfoForContact:iqNode.fromUser resource:iqNode.fromResource andAccount:account.accountNo]; - - if(versionDBInfo == nil || !( - [versionDBInfo.appName isEqualToString:iqAppName] && - [versionDBInfo.appVersion isEqualToString:iqAppVersion] && - [versionDBInfo.platformOs isEqualToString:iqPlatformOS] - )) { - DDLogVerbose(@"Updating software version info for %@", iqNode.from); - NSDate* lastInteraction = [[DataLayer sharedInstance] lastInteractionOfJid:iqNode.fromUser andResource:iqNode.fromResource forAccountNo:account.accountNo]; - MLContactSoftwareVersionInfo* newSoftwareVersionInfo = [[MLContactSoftwareVersionInfo alloc] initWithJid:iqNode.fromUser andRessource:iqNode.fromResource andAppName:iqAppName andAppVersion:iqAppVersion andPlatformOS:iqPlatformOS andLastInteraction:lastInteraction]; - - [[DataLayer sharedInstance] setSoftwareVersionInfoForContact:iqNode.fromUser - resource:iqNode.fromResource - andAccount:account.accountNo - withSoftwareInfo:newSoftwareVersionInfo]; - - [[MLNotificationQueue currentQueue] postNotificationName:kMonalXmppUserSoftWareVersionRefresh - object:account - userInfo:@{@"versionInfo": newSoftwareVersionInfo}]; + if([iqNode.fromUser isEqualToString:account.connectionProperties.identity.domain]) + { + account.connectionProperties.serverVersion = [[MLContactSoftwareVersionInfo alloc] initWithJid:iqNode.fromUser andRessource:iqNode.fromResource andAppName:iqAppName andAppVersion:iqAppVersion andPlatformOS:iqPlatformOS andLastInteraction:[NSDate date]]; + return; } + + DDLogVerbose(@"Updating software version info for %@", iqNode.from); + NSDate* lastInteraction = [[DataLayer sharedInstance] lastInteractionOfJid:iqNode.fromUser andResource:iqNode.fromResource forAccountNo:account.accountNo]; + MLContactSoftwareVersionInfo* newSoftwareVersionInfo = [[MLContactSoftwareVersionInfo alloc] initWithJid:iqNode.fromUser andRessource:iqNode.fromResource andAppName:iqAppName andAppVersion:iqAppVersion andPlatformOS:iqPlatformOS andLastInteraction:lastInteraction]; + + [[DataLayer sharedInstance] setSoftwareVersionInfoForContact:iqNode.fromUser + resource:iqNode.fromResource + andAccount:account.accountNo + withSoftwareInfo:newSoftwareVersionInfo]; + + [[MLNotificationQueue currentQueue] postNotificationName:kMonalXmppUserSoftWareVersionRefresh + object:account + userInfo:@{@"versionInfo": newSoftwareVersionInfo}]; } +(void) respondWithErrorTo:(XMPPIQ*) iqNode onAccount:(xmpp*) account diff --git a/Monal/Classes/MLMucProcessor.h b/Monal/Classes/MLMucProcessor.h index a9a31bc039..5161c0da09 100644 --- a/Monal/Classes/MLMucProcessor.h +++ b/Monal/Classes/MLMucProcessor.h @@ -24,7 +24,14 @@ NS_ASSUME_NONNULL_BEGIN -(void) join:(NSString*) room; -(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks; --(void) publishAvatar:(UIImage*) image forMuc:(NSString*) room; + +//muc management methods +-(NSString* _Nullable) createGroup:(NSString*) node; +-(void) changeNameOfMuc:(NSString*) room to:(NSString*) name; +-(void) changeSubjectOfMuc:(NSString*) room to:(NSString*) subject; +-(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room; +-(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inMuc:(NSString*) roomJid; + -(void) pingAllMucs; -(void) ping:(NSString*) roomJid; -(BOOL) checkIfStillBookmarked:(NSString*) room; diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index a6e716fb27..928c90d5f6 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -23,7 +23,7 @@ #import "MLOMEMO.h" #import "MLImageManager.h" -#define CURRENT_MUC_STATE_VERSION @6 +#define CURRENT_MUC_STATE_VERSION @7 @interface MLMucProcessor() { @@ -31,6 +31,7 @@ @interface MLMucProcessor() //persistent state NSObject* _stateLockObject; NSMutableDictionary* _roomFeatures; + NSMutableDictionary* _creating; NSMutableDictionary* _joining; NSMutableSet* _firstJoin; NSDate* _lastPing; @@ -43,12 +44,35 @@ @interface MLMucProcessor() @implementation MLMucProcessor +static NSDictionary* _mandatoryGroupConfigOptions; +static NSDictionary* _optionalGroupConfigOptions; + ++(void) initialize +{ + _mandatoryGroupConfigOptions = @{ + @"muc#roomconfig_persistentroom": @"1", + @"muc#roomconfig_membersonly": @"1", + @"muc#roomconfig_whois": @"anyone", + }; + _optionalGroupConfigOptions = @{ + @"muc#roomconfig_enablelogging": @"0", + @"muc#roomconfig_changesubject": @"0", + @"muc#roomconfig_allowinvites": @"0", + @"muc#roomconfig_getmemberlist": @"participant", + @"muc#roomconfig_publicroom": @"0", + @"muc#roomconfig_moderatedroom": @"0", + @"muc#maxhistoryfetch": @"0", //should use mam + }; + +} + -(id) initWithAccount:(xmpp*) account { self = [super init]; _account = account; _stateLockObject = [NSObject new]; _roomFeatures = [NSMutableDictionary new]; + _creating = [NSMutableDictionary new]; _joining = [NSMutableDictionary new]; _firstJoin = [NSMutableSet new]; _uiHandler = [NSMutableDictionary new]; @@ -80,6 +104,7 @@ -(void) setInternalState:(NSDictionary*) state //extract state @synchronized(_stateLockObject) { _roomFeatures = [state[@"roomFeatures"] mutableCopy]; + _creating = [state[@"creating"] mutableCopy]; _joining = [state[@"joining"] mutableCopy]; _firstJoin = [state[@"firstJoin"] mutableCopy]; _lastPing = state[@"lastPing"]; @@ -94,6 +119,7 @@ -(NSDictionary*) getInternalState NSDictionary* state = @{ @"version": CURRENT_MUC_STATE_VERSION, @"roomFeatures": [_roomFeatures copy], + @"creating": [_creating copy], @"joining": [_joining copy], @"firstJoin": [_firstJoin copy], @"lastPing": _lastPing, @@ -118,6 +144,9 @@ -(void) handleResourceBound:(NSNotification*) notification NSDictionary* joiningCopy = [_joining copy]; for(NSString* room in joiningCopy) [self removeRoomFromJoining:room]; + NSDictionary* creatingCopy = [_creating copy]; + for(NSString* room in creatingCopy) + [self removeRoomFromCreating:room]; //don't clear _firstJoin and _noUpdateBookmarks to make sure half-joined mucs are still added to muc bookmarks @@ -143,6 +172,13 @@ -(void) handleCatchupDone:(NSNotification*) notification } } +-(BOOL) isCreating:(NSString*) room +{ + @synchronized(_stateLockObject) { + return _creating[room] != nil; + } +} + -(BOOL) isJoining:(NSString*) room { @synchronized(_stateLockObject) { @@ -339,6 +375,125 @@ -(void) handleMembersListUpdate:(XMPPStanza*) node DDLogInfo(@"Ignoring handleMembersListUpdate for %@, MUC not in buddylist", node.fromUser); } +-(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) mandatoryOptions andOptionalOptions:(NSDictionary*) optionalOptions deletingMucOnError:(BOOL) deleteOnError +{ + DDLogInfo(@"Fetching room config form: %@", roomJid); + XMPPIQ* configFetchNode = [[XMPPIQ alloc] initWithType:kiqGetType to:roomJid]; + [configFetchNode setGetRoomConfig]; + [_account sendIq:configFetchNode withHandler:$newHandlerWithInvalidation(self, handleRoomConfigForm, handleRoomConfigFormInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError))]; +} + +$$instance_handler(handleRoomConfigFormInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(deleteOnError) + { + DDLogError(@"Config form fetch failed, removing muc '%@' from _creating...", roomJid); + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + else + DDLogError(@"Config form fetch failed for muc '%@'!", roomJid); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could fetch room config form for '%@': timeout", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; +$$ + +$$instance_handler(handleRoomConfigForm, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ + @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], + @"roomJid": [NSString stringWithFormat:@"%@", roomJid], + })); + if([iqNode check:@"/"]) + { + DDLogError(@"Failed to fetch room config form for '%@': %@", roomJid, [iqNode findFirst:@"error"]); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to fetch room config form for '%@'", @""), roomJid] forMuc:roomJid withNode:iqNode andIsSevere:YES]; + return; + } + + XMPPDataForm* dataForm = [[iqNode findFirst:@"{http://jabber.org/protocol/muc#owner}query/\\{http://jabber.org/protocol/muc#roomconfig}form\\"] copy]; + if(dataForm == nil) + { + DDLogError(@"Got empty room config form for '%@'!", roomJid); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Got empty room config form for '%@'", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; + return; + } + + //these config options are mandatory and configure the room to be a group --> non anonymous, members only (and persistent) + for(NSString* option in mandatoryOptions) + { + if(!dataForm[option]) + { + DDLogError(@"Could not configure room '%@' to be a groupchat: config option '%@' not available!", roomJid, option); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:@"Could not configure new group '%@': config option '%@' not available!", roomJid, option] forMuc:roomJid withNode:nil andIsSevere:YES]; + return; + } + else + dataForm[option] = mandatoryOptions[option]; + } + + //these config options are optional but most of them should be supported by all modern servers + for(NSString* option in optionalOptions) + { + if(dataForm[option]) + dataForm[option] = optionalOptions[option]; + else + DDLogWarn(@"Ignoring optional config option for room '%@': %@", roomJid, option); + } + + //reconfigure the room + dataForm.type = @"submit"; + XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType]; + [query setRoomConfig:dataForm]; + [_account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleRoomConfigResult, handleRoomConfigResultInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError))]; +$$ + +$$instance_handler(handleRoomConfigResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(deleteOnError) + { + DDLogError(@"Config form submit failed, removing muc '%@' from _creating...", roomJid); + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + else + DDLogError(@"Config form submit failed for muc '%@'!", roomJid); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not configure group '%@': timeout", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; +$$ + +$$instance_handler(handleRoomConfigResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ + @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], + @"roomJid": [NSString stringWithFormat:@"%@", roomJid], + })); + if([iqNode check:@"/"]) + { + DDLogError(@"Failed to submit room config form of '%@': %@", roomJid, [iqNode findFirst:@"error"]); + if(deleteOnError) + { + [self removeRoomFromCreating:roomJid]; + [self deleteMuc:roomJid withBookmarksUpdate:NO keepBuddylistEntry:NO]; + } + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not configure group '%@'", @""), roomJid] forMuc:roomJid withNode:iqNode andIsSevere:YES]; + return; + } + + //group is now properly configured and we are joined, but all the code handling a proper join was not run + //--> join again to make sure everything is sane + [self removeRoomFromCreating:roomJid]; + [self join:roomJid]; +$$ + -(void) handleStatusCodes:(XMPPStanza*) node { NSSet* presenceCodes = [[NSSet alloc] initWithArray:[node find:@"/{jabber:client}presence/{http://jabber.org/protocol/muc#user}x/status@code|int"]]; @@ -354,11 +509,23 @@ -(void) handleStatusCodes:(XMPPStanza*) node //room created and needs configuration now case 201: { - //make instant room - DDLogInfo(@"Creating instant muc room using default config: %@", node.fromUser); - XMPPIQ* configNode = [[XMPPIQ alloc] initWithType:kiqSetType to:node.fromUser]; - [configNode setInstantRoom]; - [_account send:configNode]; + if(![presenceCodes containsObject:@110]) + { + DDLogError(@"Got 'muc needs configuration' status code (201) without self-presence, ignoring!"); + break; + } + if(![self isCreating:node.fromUser]) + { + DDLogError(@"Got 'muc needs configuration' status code (201) without this muc currently being created, ignoring!"); + break; + } + + //now configure newly created locked room + [self configureMuc:node.fromUser withMandatoryOptions:_mandatoryGroupConfigOptions andOptionalOptions:_optionalGroupConfigOptions deletingMucOnError:YES]; + + //stop processing here to not trigger the "successful join" code below + //we will trigger this code by a "second" join presence once the room was created and is not locked anymore + return; break; } //muc service changed our nick @@ -543,6 +710,16 @@ -(void) handleStatusCodes:(XMPPStanza*) node case 102: case 103: case 104: + /* + * If room logging is now enabled, status code 170. + * If room logging is now disabled, status code 171. + * If the room is now non-anonymous, status code 172. + * If the room is now semi-anonymous, status code 173. + */ + case 170: + case 171: + case 172: + case 173: { DDLogInfo(@"Muc config of %@ changed, sending new disco info query to reload muc config...", node.fromUser); [self sendDiscoQueryFor:node.from withJoin:NO andBookmarksUpdate:NO]; @@ -554,6 +731,53 @@ -(void) handleStatusCodes:(XMPPStanza*) node } } +$$instance_handler(handleCreateTimeout, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, room)) + [self removeRoomFromCreating:room]; + [self deleteMuc:room withBookmarksUpdate:NO keepBuddylistEntry:NO]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could fetch room config form from '%@': timeout", @""), room] forMuc:room withNode:nil andIsSevere:YES]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not create group '%@': timeout", @""), room] forMuc:room withNode:nil andIsSevere:YES]; +$$ + +-(NSString*) createGroup:(NSString*) node +{ + NSString* room = [[NSString stringWithFormat:@"%@@%@", node, _account.connectionProperties.conferenceServer] lowercaseString]; + if([[DataLayer sharedInstance] isBuddyMuc:room forAccount:_account.accountNo]) + { + DDLogWarn(@"Cannot create muc already existing in our buddy list, checking if we are still joined and join if needed..."); + [self ping:room]; + return nil; + } + + //remove old non-muc contact from contactlist (we don't want mucs as normal contacts on our (server) roster and shadowed in monal by the real muc contact) + NSDictionary* existingContactDict = [[DataLayer sharedInstance] contactDictionaryForUsername:room forAccount:_account.accountNo]; + if(existingContactDict != nil) + { + MLContact* existingContact = [MLContact createContactFromJid:room andAccountNo:_account.accountNo]; + DDLogVerbose(@"CreateMUC: Removing already existing contact (%@) having raw db dict: %@", existingContact, existingContactDict); + [_account removeFromRoster:existingContact]; + } + //add new muc buddy (potentially deleting a non-muc buddy having the same jid) + NSString* nick = [self calculateNickForMuc:room]; + DDLogInfo(@"CreateMUC: Adding new muc %@ using nick '%@' to buddylist...", room, nick); + [[DataLayer sharedInstance] initMuc:room forAccountId:_account.accountNo andMucNick:nick]; + + DDLogInfo(@"Trying to create muc '%@' with nick '%@' on account %@...", room, nick, _account); + @synchronized(_stateLockObject) { + //add room to "currently creating" list (and remove any present idle timer for this room) + [[DataLayer sharedInstance] delIdleTimerWithId:_creating[room]]; + //add idle timer to display error if we did not receive the reflected create presence after 30 idle seconds + //this will make sure the spinner ui will not spin indefinitely when adding a channel via ui + NSNumber* timerId = [[DataLayer sharedInstance] addIdleTimerWithTimeout:@30 andHandler:$newHandler(self, handleCreateTimeout, $ID(room)) onAccountNo:_account.accountNo]; + _creating[room] = timerId; + //we don't need to force saving of our new state because once this outgoing create presence gets handled by smacks the whole state will be saved + } + XMPPPresence* presence = [XMPPPresence new]; + [presence createRoom:room withNick:nick]; + [_account send:presence]; + + return room; +} + -(void) join:(NSString*) room { [self sendDiscoQueryFor:room withJoin:YES andBookmarksUpdate:YES]; @@ -704,8 +928,53 @@ -(void) ping:(NSString*) roomJid withLastPing:(NSDate* _Nullable) lastPing }]; } --(void) publishAvatar:(UIImage*) image forMuc:(NSString*) room +-(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inMuc:(NSString*) roomJid { + DDLogInfo(@"Changing affiliation of '%@' in '%@' to '%@'", jid, roomJid, affiliation); + XMPPIQ* updateIq = [[XMPPIQ alloc] initWithType:kiqSetType to:roomJid]; + [updateIq setMucAdminQueryWithAffiliation:affiliation forJid:jid]; + [_account sendIq:updateIq withHandler:$newHandlerWithInvalidation(self, handleAffiliationUpdateResult, handleAffiliationUpdateResultInvalidation, $ID(roomJid), $ID(jid), $ID(affiliation))]; +} + +$$instance_handler(handleAffiliationUpdateResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, affiliation), $$ID(NSString*, jid), $$ID(NSString*, roomJid)) + DDLogError(@"Failed to change affiliation of '%@' in '%@' to '%@': timeout", jid, roomJid, affiliation); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to change affiliation of '%@' in '%@' to '%@': timeout", @""), jid, roomJid, affiliation] forMuc:roomJid withNode:nil andIsSevere:YES]; +$$ + +$$instance_handler(handleAffiliationUpdateResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, affiliation), $$ID(NSString*, jid), $$ID(NSString*, roomJid)) + if([iqNode check:@"/"]) + { + DDLogError(@"Failed to change affiliation of '%@' in '%@' to '%@': %@", jid, roomJid, affiliation, [iqNode findFirst:@"error"]); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to change affiliation of '%@' in '%@' to '%@'", @""), jid, roomJid, affiliation] forMuc:roomJid withNode:iqNode andIsSevere:YES]; + return; + } + DDLogError(@"Successfully changed affiliation of '%@' in '%@' to '%@'", jid, roomJid, affiliation); +$$ + +-(void) changeNameOfMuc:(NSString*) room to:(NSString*) name +{ + [self configureMuc:room withMandatoryOptions:@{ + @"muc#roomconfig_roomname": name, + } andOptionalOptions:@{} deletingMucOnError:NO]; +} + +-(void) changeSubjectOfMuc:(NSString*) room to:(NSString*) subject +{ + XMPPMessage* msg = [[XMPPMessage alloc] initWithType:kMessageGroupChatType to:room]; + [msg addChildNode:[[MLXMLNode alloc] initWithElement:@"subject" andData:subject]]; + [_account send:msg]; +} + +-(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room +{ + if(image == nil) + { + DDLogInfo(@"Removing avatar image for muc '%@'...", room); + XMPPIQ* vcard = [[XMPPIQ alloc] initWithType:kiqSetType to:room]; + [vcard setRemoveVcardAvatar]; + [_account sendIq:vcard withHandler:$newHandlerWithInvalidation(self, handleAvatarPublishResult, handleAvatarPublishResultInvalidation, $ID(room))]; + return; + } //should work for ejabberd >= 19.02 and prosody >= 0.11 NSData* imageData = [HelperTools resizeAvatarImage:image withCircularMask:NO toMaxBase64Size:60000]; NSString* imageHash = [HelperTools hexadecimalString:[HelperTools sha1:imageData]]; @@ -713,14 +982,19 @@ -(void) publishAvatar:(UIImage*) image forMuc:(NSString*) room DDLogInfo(@"Publishing avatar image for muc '%@' with hash %@", room, imageHash); XMPPIQ* vcard = [[XMPPIQ alloc] initWithType:kiqSetType to:room]; [vcard setVcardAvatarWithData:imageData andType:@"image/jpeg"]; - [_account sendIq:vcard withHandler:$newHandler(self, handleAvatarPublishResult)]; + [_account sendIq:vcard withHandler:$newHandlerWithInvalidation(self, handleAvatarPublishResult, handleAvatarPublishResultInvalidation, $ID(room))]; } +$$instance_handler(handleAvatarPublishResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, room)) + DDLogError(@"Publishing avatar for muc '%@' returned timeout", room); + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to publish avatar image for group/channel %@", @""), room] forMuc:room withNode:nil andIsSevere:YES]; +$$ + $$instance_handler(handleAvatarPublishResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode)) if([iqNode check:@"/"]) { DDLogError(@"Publishing avatar for muc '%@' returned error: %@", iqNode.fromUser, [iqNode findFirst:@"error"]); - [HelperTools postError:NSLocalizedString(@"Failed to publish avatar image for group/channel %@", @"") withNode:iqNode andAccount:_account andIsSevere:YES]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to publish avatar image for group/channel %@", @""), iqNode.fromUser] forMuc:iqNode.fromUser withNode:iqNode andIsSevere:YES]; return; } DDLogInfo(@"Successfully published avatar for muc: %@", iqNode.fromUser); @@ -1138,6 +1412,15 @@ -(NSString*) calculateNickForMuc:(NSString*) room return nick; } +-(void) removeRoomFromCreating:(NSString*) room +{ + @synchronized(_stateLockObject) { + DDLogVerbose(@"Removing from _creating[%@]: %@", room, _creating[room]); + [[DataLayer sharedInstance] delIdleTimerWithId:_creating[room]]; + [_creating removeObjectForKey:room]; + } +} + -(void) removeRoomFromJoining:(NSString*) room { @synchronized(_stateLockObject) { diff --git a/Monal/Classes/MLServerDetails.m b/Monal/Classes/MLServerDetails.m index c4e7c6e2f2..2b079b5f53 100644 --- a/Monal/Classes/MLServerDetails.m +++ b/Monal/Classes/MLServerDetails.m @@ -9,9 +9,12 @@ #import "MLServerDetails.h" #import "UIColor+Theme.h" #import "SCRAM.h" +#import "MLContactSoftwareVersionInfo.h" +#import "DataLayer.h" @interface MLServerDetails () +@property (nonatomic, strong) MLContactSoftwareVersionInfo* serverVersion; @property (nonatomic, strong) NSMutableArray* serverCaps; @property (nonatomic, strong) NSMutableArray* stunTurnServers; @property (nonatomic, strong) NSMutableArray* srvRecords; @@ -23,7 +26,9 @@ @interface MLServerDetails () @implementation MLServerDetails +//TODO: make all of these shareable as one long text (or json) enum MLServerDetailsSections { + SERVER_VERSION_SECTION, SUPPORTED_SERVER_XEPS_SECTION, VOIP_SECTION, SRV_RECORS_SECTION, @@ -36,6 +41,7 @@ @implementation MLServerDetails #define SERVER_DETAILS_COLOR_OK @"Blue" #define SERVER_DETAILS_COLOR_NON_IDEAL @"Orange" #define SERVER_DETAILS_COLOR_ERROR @"Red" +#define SERVER_DETAILS_COLOR_NONE @"" - (void) viewDidLoad { @@ -55,6 +61,7 @@ -(void) viewWillAppear:(BOOL) animated self.navigationItem.title = self.xmppAccount.connectionProperties.identity.domain; self.tableView.allowsSelection = NO; + self.serverVersion = self.xmppAccount.connectionProperties.serverVersion; [self checkServerCaps:self.xmppAccount.connectionProperties]; [self convertSRVRecordsToReadable]; [self checkTLSVersions:self.xmppAccount.connectionProperties]; @@ -290,7 +297,9 @@ -(NSInteger) numberOfSectionsInTableView:(UITableView*) tableView -(NSInteger) tableView:(UITableView*) tableView numberOfRowsInSection:(NSInteger) section { - if(section == SUPPORTED_SERVER_XEPS_SECTION) + if(section == SERVER_VERSION_SECTION) + return 1; + else if(section == SUPPORTED_SERVER_XEPS_SECTION) return (NSInteger)self.serverCaps.count; else if(section == VOIP_SECTION) return (NSInteger)self.stunTurnServers.count; @@ -310,7 +319,21 @@ -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NS UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"serverCell" forIndexPath:indexPath]; NSDictionary* dic; - if(indexPath.section == SUPPORTED_SERVER_XEPS_SECTION) + if(indexPath.section == SERVER_VERSION_SECTION) + { + if(indexPath.row == 0) + { + NSString* serverName = nilDefault(self.serverVersion.appName, NSLocalizedString(@"", @"server details")); + NSString* serverVersion = nilDefault(self.serverVersion.appVersion, NSLocalizedString(@"", @"server details")); + NSString* serverPlatform = self.serverVersion.platformOs != nil ? [NSString stringWithFormat:NSLocalizedString(@" running on %@", @"server details"), self.serverVersion.platformOs] : @""; + dic = @{ + @"Color": SERVER_DETAILS_COLOR_NONE, + @"Title": serverName, + @"Description": [NSString stringWithFormat:NSLocalizedString(@"version %@%@", @"server details"), serverVersion, serverPlatform], + }; + } + } + else if(indexPath.section == SUPPORTED_SERVER_XEPS_SECTION) dic = [self.serverCaps objectAtIndex:(NSUInteger)indexPath.row]; if(indexPath.section == VOIP_SECTION) dic = [self.stunTurnServers objectAtIndex:(NSUInteger)indexPath.row]; @@ -323,8 +346,8 @@ -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NS else if(indexPath.section == CB_SECTION) dic = [self.channelBindingTypes objectAtIndex:(NSUInteger)indexPath.row]; - cell.textLabel.text = [dic objectForKey:@"Title"]; - cell.detailTextLabel.text = [dic objectForKey:@"Description"]; + cell.textLabel.text = nilExtractor([dic objectForKey:@"Title"]); + cell.detailTextLabel.text = nilExtractor([dic objectForKey:@"Description"]); // Add background color to selected cells if([dic objectForKey:@"Color"]) @@ -363,9 +386,11 @@ -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NS -(NSString*) tableView:(UITableView*) tableView titleForHeaderInSection:(NSInteger) section { - if(section == SUPPORTED_SERVER_XEPS_SECTION) + if(section == SERVER_VERSION_SECTION) + return NSLocalizedString(@"This is the software running on your server.", @""); + else if(section == SUPPORTED_SERVER_XEPS_SECTION) return NSLocalizedString(@"These are the modern XMPP capabilities Monal detected on your server after you have logged in.", @""); - if(section == VOIP_SECTION) + else if(section == VOIP_SECTION) return NSLocalizedString(@"These are STUN and TURN services announced by your server. (blue entries are used by monal)", @""); else if(section == SRV_RECORS_SECTION) return NSLocalizedString(@"These are SRV resource records found for your domain.", @""); diff --git a/Monal/Classes/MLXMPPConnection.h b/Monal/Classes/MLXMPPConnection.h index 75856aca68..464f0dfd76 100644 --- a/Monal/Classes/MLXMPPConnection.h +++ b/Monal/Classes/MLXMPPConnection.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN +@class MLContactSoftwareVersionInfo; + /** A class to hold the the identity, host, state and discovered properties of an xmpp connection */ @@ -32,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) NSMutableArray* discoveredServices; @property (nonatomic, strong) NSMutableArray* discoveredStunTurnServers; @property (nonatomic, strong) NSMutableDictionary* discoveredAdhocCommands; +@property (nonatomic, strong) MLContactSoftwareVersionInfo* _Nullable serverVersion; @property (nonatomic, strong) NSString* _Nullable conferenceServer; diff --git a/Monal/Classes/MLXMPPConnection.m b/Monal/Classes/MLXMPPConnection.m index 1727be51c0..a48857fb3e 100644 --- a/Monal/Classes/MLXMPPConnection.m +++ b/Monal/Classes/MLXMPPConnection.m @@ -26,6 +26,7 @@ -(id) initWithServer:(MLXMPPServer*) server andIdentity:(MLXMPPIdentity*) identi self.discoveredServices = [NSMutableArray new]; self.discoveredStunTurnServers = [NSMutableArray new]; self.discoveredAdhocCommands = [NSMutableDictionary new]; + self.serverVersion = nil; return self; } diff --git a/Monal/Classes/XMPPIQ.h b/Monal/Classes/XMPPIQ.h index deabe9694d..84e6f49f7f 100644 --- a/Monal/Classes/XMPPIQ.h +++ b/Monal/Classes/XMPPIQ.h @@ -9,6 +9,8 @@ #import "XMPPStanza.h" #import "MLContact.h" +@class XMPPDataForm; + NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString* const kiqGetType; @@ -117,6 +119,7 @@ removes a contact from the roster -(void) setInstantRoom; -(void) setVcardAvatarWithData:(NSData*) imageData andType:(NSString*) imageType; +-(void) setRemoveVcardAvatar; -(void) setVcardQuery; #pragma mark - account @@ -129,6 +132,10 @@ removes a contact from the roster -(void) setBlocked:(BOOL) blocked forJid:(NSString*) blockedJid; -(void) requestBlockList; +-(void) setMucAdminQueryWithAffiliation:(NSString*) affiliation forJid:(NSString*) jid; +-(void) setGetRoomConfig; +-(void) setRoomConfig:(XMPPDataForm*) configForm; + @end NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/XMPPIQ.m b/Monal/Classes/XMPPIQ.m index 93b6ef25b9..14b1731892 100644 --- a/Monal/Classes/XMPPIQ.m +++ b/Monal/Classes/XMPPIQ.m @@ -345,6 +345,16 @@ -(void) getEntitySoftWareVersionTo:(NSString*) to #pragma mark MUC +-(void) setGetRoomConfig +{ + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner"]]; +} + +-(void) setRoomConfig:(XMPPDataForm*) configForm +{ + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner" withAttributes:@{} andChildren:@[configForm] andData:nil]]; +} + -(void) setInstantRoom { [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#owner" withAttributes:@{} andChildren:@[ @@ -352,6 +362,16 @@ -(void) setInstantRoom ] andData:nil]]; } +-(void) setRemoveVcardAvatar +{ + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"vCard" andNamespace:@"vcard-temp" withAttributes:@{} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"PHOTO" withAttributes:@{} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"PHOTO" andData:nil], + [[MLXMLNode alloc] initWithElement:@"BINVAL" andData:nil], + ] andData:nil] + ] andData:nil]]; +} + -(void) setVcardAvatarWithData:(NSData*) imageData andType:(NSString*) imageType { [self addChildNode:[[MLXMLNode alloc] initWithElement:@"vCard" andNamespace:@"vcard-temp" withAttributes:@{} andChildren:@[ @@ -417,4 +437,14 @@ -(void) changePasswordForUser:(NSString*) user newPassword:(NSString*) newPass ] andData:nil]]; } +-(void) setMucAdminQueryWithAffiliation:(NSString*) affiliation forJid:(NSString*) jid +{ + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"http://jabber.org/protocol/muc#admin" withAttributes:@{} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{ + @"affiliation": affiliation, + @"jid": jid, + } andChildren:@[] andData:nil], + ] andData:nil]]; +} + @end diff --git a/Monal/Classes/XMPPPresence.h b/Monal/Classes/XMPPPresence.h index 9dc9f64581..6124e83d51 100644 --- a/Monal/Classes/XMPPPresence.h +++ b/Monal/Classes/XMPPPresence.h @@ -80,6 +80,9 @@ allow subscription. Called in response to a remote request. -(void) unsubscribedContact:(MLContact*) contact; #pragma mark MUC + +-(void) createRoom:(NSString*) room withNick:(NSString*) nick; + /** join specified room on server */ diff --git a/Monal/Classes/XMPPPresence.m b/Monal/Classes/XMPPPresence.m index 7b6ed92b5f..5659399084 100644 --- a/Monal/Classes/XMPPPresence.m +++ b/Monal/Classes/XMPPPresence.m @@ -68,6 +68,12 @@ -(void) setLastInteraction:(NSDate*) date #pragma mark MUC +-(void) createRoom:(NSString*) room withNick:(NSString*) nick +{ + self.to = [NSString stringWithFormat:@"%@/%@", room, nick]; + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"x" andNamespace:@"http://jabber.org/protocol/muc" withAttributes:@{} andChildren:@[] andData:nil]]; +} + -(void) joinRoom:(NSString*) room withNick:(NSString*) nick { [self.attributes setObject:[NSString stringWithFormat:@"%@/%@", room, nick] forKey:@"to"]; diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 49a2303b1d..daec778be5 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -49,7 +49,7 @@ @import AVFoundation; @import WebRTC; -#define STATE_VERSION 9 +#define STATE_VERSION 10 #define CONNECT_TIMEOUT 7.0 #define IQ_TIMEOUT 60.0 NSString* const kQueueID = @"queueID"; @@ -2831,10 +2831,8 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR -(void) handleFeaturesBeforeAuth:(MLXMLNode*) parsedStanza { - //called below, if neither SASL1 nor SASL2 could be used to negotiate a valid SASL mechanism - monal_void_block_t noAuthSupported = ^{ - //no supported auth mechanism - DDLogWarn(@"No supported auth mechanism!"); + monal_id_block_t clearPipelineCacheOrReportSevereError = ^(NSString* msg) { + DDLogWarn(@"Clearing auth pipeline due to error..."); //clear pipeline cache to make sure we have a fresh restart next time xmppPipeliningState oldPipeliningState = self->_pipeliningState; @@ -2843,13 +2841,21 @@ -(void) handleFeaturesBeforeAuth:(MLXMLNode*) parsedStanza self->_cachedStreamFeaturesAfterAuth = nil; if(oldPipeliningState != kPipelinedNothing) + { + DDLogWarn(@"Retrying auth without pipelining..."); [self reconnect]; + } else { //make sure this error is reported, even if there are other SRV records left (we disconnect here and won't try again) - [HelperTools postError:NSLocalizedString(@"No supported auth mechanism found, disabling account!", @"") withNode:nil andAccount:self andIsSevere:YES andDisableAccount:YES]; + [HelperTools postError:msg withNode:nil andAccount:self andIsSevere:YES andDisableAccount:YES]; } }; + //called below, if neither SASL1 nor SASL2 could be used to negotiate a valid SASL mechanism + monal_void_block_t noAuthSupported = ^{ + DDLogWarn(@"No supported auth mechanism!"); + clearPipelineCacheOrReportSevereError(NSLocalizedString(@"No supported auth mechanism found, disabling account!", @"")); + }; if([parsedStanza check:@"{urn:xmpp:ibr-token:0}register"]) { @@ -3019,7 +3025,7 @@ -(void) handleFeaturesBeforeAuth:(MLXMLNode*) parsedStanza } //if the above case didn't trigger, this is a downgrade attack downgrading from SASL2 to SASL1, report is as such - [HelperTools postError:NSLocalizedString(@"SASL2 to SASL1 downgrade attack detected, aborting authentication and disabling account to limit damage. You should reenable your account once you are in a clean networking environment again.", @"") withNode:nil andAccount:self andIsSevere:YES andDisableAccount:YES]; + clearPipelineCacheOrReportSevereError(NSLocalizedString(@"SASL2 to SASL1 downgrade attack detected, aborting authentication and disabling account to limit damage. You should reenable your account once you are in a clean networking environment again.", @"")); } } @@ -3431,6 +3437,9 @@ -(void) realPersistState if(self.connectionProperties.discoveredAdhocCommands) [values setObject:[self.connectionProperties.discoveredAdhocCommands copy] forKey:@"discoveredAdhocCommands"]; + + if(self.connectionProperties.serverVersion) + [values setObject:self.connectionProperties.serverVersion forKey:@"serverVersion"]; [values setObject:self->_lastInteractionDate forKey:@"lastInteractionDate"]; [values setValue:[NSDate date] forKey:@"stateSavedAt"]; @@ -3542,6 +3551,7 @@ -(void) realReadState self.connectionProperties.discoveredServices = [[dic objectForKey:@"discoveredServices"] mutableCopy]; self.connectionProperties.discoveredStunTurnServers = [[dic objectForKey:@"discoveredStunTurnServers"] mutableCopy]; self.connectionProperties.discoveredAdhocCommands = [[dic objectForKey:@"discoveredAdhocCommands"] mutableCopy]; + self.connectionProperties.serverVersion = [dic objectForKey:@"serverVersion"]; self.connectionProperties.uploadServer = [dic objectForKey:@"uploadServer"]; self.connectionProperties.conferenceServer = [dic objectForKey:@"conferenceServer"]; @@ -3800,6 +3810,13 @@ -(void) queryDisco [self sendIq:adhocCommands withHandler:$newHandler(MLIQProcessor, handleAdhocDisco)]; } +-(void) queryServerVersion +{ + XMPPIQ* serverVersion = [[XMPPIQ alloc] initWithType:kiqGetType]; + [serverVersion getEntitySoftWareVersionTo:self.connectionProperties.identity.domain]; + [self send:serverVersion]; +} + -(void) queryExternalServicesOn:(NSString*) jid { XMPPIQ* externalDisco = [[XMPPIQ alloc] initWithType:kiqGetType]; @@ -3951,6 +3968,7 @@ -(void) initSession //if and what pubsub/pep features the server supports, before handling that //we can pipeline the disco requests and outgoing presence broadcast, though [self queryDisco]; + [self queryServerVersion]; [self purgeOfflineStorage]; [self sendPresence]; //this will trigger a replay of offline stanzas on prosody (no XEP-0013 support anymore 😡) //the offline messages will come in *after* we initialized the mam query, because the disco result comes in first diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index ddf1e17867..fb954ec1f7 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -2592,7 +2592,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; @@ -2894,7 +2894,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; @@ -3041,7 +3041,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; ONLY_ACTIVE_ARCH = YES; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; @@ -3302,7 +3302,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; @@ -3540,6 +3540,309 @@ }; name = Beta; }; + C15D0B0D2B3EF70E00845061 /* AppStore-Quicksy */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_STATIC_ANALYZER_MODE_ON_ANALYZE_ACTION = shallow; + CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_COMPLETION_HANDLER_MISUSE = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_CXX0X_EXTENSIONS = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_FLOAT_CONVERSION = NO; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = NO; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CURRENT_PROJECT_VERSION = 0; + DEVELOPMENT_TEAM = S8D843U34Y; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + ); + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = NO; + GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = NO; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LLVM_LTO = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 6.0.4; + SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "AppStore-Quicksy"; + }; + C15D0B0E2B3EF70E00845061 /* AppStore-Quicksy */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1D46F251C198E3D8FA55692F /* Pods-Monal.appstore.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_LINK_OBJC_RUNTIME = YES; + CLANG_WARN_ASSIGN_ENUM = YES; + CLANG_WARN_COMMA = YES_ERROR; + CLANG_WARN_DELETE_NON_VIRTUAL_DTOR = YES_ERROR; + CLANG_WARN_FLOAT_CONVERSION = YES; + CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; + CLANG_WARN_PRAGMA_PACK = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES_ERROR; + CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR; + CLANG_WARN_VEXING_PARSE = YES_ERROR; + CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; + CODE_SIGN_ENTITLEMENTS = Monal.ios.entitlements; + "CODE_SIGN_ENTITLEMENTS[sdk=iphoneos*]" = Monal.ios.entitlements; + "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = Monal.macos.entitlements; + COMPILER_INDEX_STORE_ENABLE = YES; + CURRENT_PROJECT_VERSION = 0; + ENABLE_BITCODE = NO; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = MonalSourceCodePrefix.pch; + GCC_THREADSAFE_STATICS = NO; + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_PEDANTIC = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = NO; + INFOPLIST_FILE = "Monal-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Monal; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal"; + PRODUCT_MODULE_NAME = Monal; + PRODUCT_NAME = Monal; + PROVISIONING_PROFILE = ""; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Classes/Monal-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "Monal-Swift.h"; + SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + }; + name = "AppStore-Quicksy"; + }; + C15D0B0F2B3EF70E00845061 /* AppStore-Quicksy */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 061EF1BEDEE7A71FDF9AB402 /* Pods-shareSheet.appstore.xcconfig */; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = shareSheet.entitlements; + CURRENT_PROJECT_VERSION = 0; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.shareSheet; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.shareSheet"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = "AppStore-Quicksy"; + }; + C15D0B102B3EF70E00845061 /* AppStore-Quicksy */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 79A6AA4819B69B5FFFA28236 /* Pods-NotificationService.appstore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CODE_SIGN_ENTITLEMENTS = ""; + "CODE_SIGN_ENTITLEMENTS[sdk=iphoneos*]" = NotificationService/NotificationService.ios.entitlements; + "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = NotificationService/NotificationService.macos.entitlements; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0; + INFOPLIST_FILE = NotificationService/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.notificationService; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.notificationService"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = "AppStore-Quicksy"; + }; + C15D0B112B3EF70E00845061 /* AppStore-Quicksy */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AAFC73E987D41BA8D91E9F95 /* Pods-monalxmpp.appstore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; + CURRENT_PROJECT_VERSION = 0; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_INPUT_FILETYPE = automatic; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = monalxmpp/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = im.Monal.monalxmpp; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INCLUDE_PATHS = monalxmpp/; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "monalxmpp-Swift.h"; + VERSION_INFO_PREFIX = ""; + }; + name = "AppStore-Quicksy"; + }; + C15D0B122B3EF70E00845061 /* AppStore-Quicksy */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D8D2595B2BE453296E59F1AF /* Pods-MonalUITests.appstore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + INFOPLIST_FILE = MonalUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + TEST_TARGET_NAME = Monal; + VALIDATE_PRODUCT = YES; + }; + name = "AppStore-Quicksy"; + }; + C15D0B132B3EF70E00845061 /* AppStore-Quicksy */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2369191B3FCB2E941169A093 /* Pods-MonalXMPPUnitTests.appstore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = MonalXMPPUnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.MonalXMPPUnitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OBJC_BRIDGING_HEADER = "MonalXMPPUnitTests/MonalXMPPUnitTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "AppStore-Quicksy"; + }; C1850EBD25F38A2D003D506A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F7506FDE7A78EB0CAB14FF60 /* Pods-MonalUITests.debug.xcconfig */; @@ -3657,7 +3960,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = NO; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.0.4; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; @@ -3906,6 +4209,7 @@ 26E74FBE17B06D2200FD91AE /* Adhoc */, C1E1BCAF288BBAB00046AB47 /* Alpha */, 2675EF5A18B98C2D0059C5C3 /* AppStore */, + C15D0B0E2B3EF70E00845061 /* AppStore-Quicksy */, C11C870D26A83C1D00B8DEA5 /* Beta */, ); defaultConfigurationIsVisible = 0; @@ -3918,6 +4222,7 @@ 260773CA232FC4E800BFD50F /* Adhoc */, C1E1BCB1288BBAB00046AB47 /* Alpha */, 260773CB232FC4E800BFD50F /* AppStore */, + C15D0B102B3EF70E00845061 /* AppStore-Quicksy */, C11C870F26A83C1D00B8DEA5 /* Beta */, ); defaultConfigurationIsVisible = 0; @@ -3930,6 +4235,7 @@ 26AA701E2146BBB900598605 /* Adhoc */, C1E1BCB0288BBAB00046AB47 /* Alpha */, 26AA701F2146BBB900598605 /* AppStore */, + C15D0B0F2B3EF70E00845061 /* AppStore-Quicksy */, C11C870E26A83C1D00B8DEA5 /* Beta */, ); defaultConfigurationIsVisible = 0; @@ -3942,6 +4248,7 @@ 26CC579D23A0867400ABB92A /* Adhoc */, C1E1BCB2288BBAB00046AB47 /* Alpha */, 26CC579E23A0867400ABB92A /* AppStore */, + C15D0B112B3EF70E00845061 /* AppStore-Quicksy */, C11C871026A83C1D00B8DEA5 /* Beta */, ); defaultConfigurationIsVisible = 0; @@ -3954,6 +4261,7 @@ 26E74FBD17B06D2200FD91AE /* Adhoc */, C1E1BCAE288BBAB00046AB47 /* Alpha */, 2675EF5918B98C2D0059C5C3 /* AppStore */, + C15D0B0D2B3EF70E00845061 /* AppStore-Quicksy */, C11C870B26A83C1D00B8DEA5 /* Beta */, ); defaultConfigurationIsVisible = 0; @@ -3966,6 +4274,7 @@ C1049190261301530054AC9E /* Adhoc */, C1E1BCB4288BBAB00046AB47 /* Alpha */, C1049191261301530054AC9E /* AppStore */, + C15D0B132B3EF70E00845061 /* AppStore-Quicksy */, C11C871226A83C1D00B8DEA5 /* Beta */, ); defaultConfigurationIsVisible = 0; @@ -3978,6 +4287,7 @@ C1850EBE25F38A2D003D506A /* Adhoc */, C1E1BCB3288BBAB00046AB47 /* Alpha */, C1850EBF25F38A2D003D506A /* AppStore */, + C15D0B122B3EF70E00845061 /* AppStore-Quicksy */, C11C871126A83C1D00B8DEA5 /* Beta */, ); defaultConfigurationIsVisible = 0;