334 lines
13 KiB
Objective-C
334 lines
13 KiB
Objective-C
//
|
|
// OTRProtocolManager.m
|
|
// Off the Record
|
|
//
|
|
// Created by Chris Ballinger on 9/4/11.
|
|
// Copyright (c) 2011 Chris Ballinger. All rights reserved.
|
|
//
|
|
// This file is part of ChatSecure.
|
|
//
|
|
// ChatSecure 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.
|
|
//
|
|
// ChatSecure 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 ChatSecure. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#import "OTRProtocolManager.h"
|
|
#import "OTRAccount.h"
|
|
#import "OTRBuddy.h"
|
|
#import "OTRIncomingMessage.h"
|
|
#import "OTROutgoingMessage.h"
|
|
#import "OTRConstants.h"
|
|
#import "OTRDatabaseManager.h"
|
|
#import <BBlock/NSObject+BBlock.h>
|
|
@import YapDatabase;
|
|
|
|
@import KVOController;
|
|
@import OTRAssets;
|
|
#import "OTRLog.h"
|
|
#import "ChatSecureCoreCompat-Swift.h"
|
|
#import "OTRXMPPPresenceSubscriptionRequest.h"
|
|
|
|
@interface OTRProtocolManager ()
|
|
@property (atomic, readwrite) NSUInteger numberOfConnectedProtocols;
|
|
@property (atomic, readwrite) NSUInteger numberOfConnectingProtocols;
|
|
@property (nonatomic, strong, readonly, nonnull) NSMutableDictionary<NSString*,id<OTRProtocol>> *protocolManagers;
|
|
@end
|
|
|
|
@implementation OTRProtocolManager
|
|
|
|
-(instancetype)init
|
|
{
|
|
self = [super init];
|
|
if(self)
|
|
{
|
|
_protocolManagers = [[NSMutableDictionary alloc] init];
|
|
|
|
_numberOfConnectedProtocols = 0;
|
|
_numberOfConnectingProtocols = 0;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)removeProtocolForAccount:(OTRAccount *)account
|
|
{
|
|
NSParameterAssert(account);
|
|
if (!account) { return; }
|
|
id<OTRProtocol> protocol = nil;
|
|
@synchronized (self) {
|
|
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
|
}
|
|
if (protocol && [protocol respondsToSelector:@selector(disconnect)]) {
|
|
[protocol disconnect];
|
|
}
|
|
[self.KVOController unobserve:protocol];
|
|
@synchronized (self) {
|
|
[self.protocolManagers removeObjectForKey:account.uniqueId];
|
|
}
|
|
}
|
|
|
|
- (void)addProtocol:(id<OTRProtocol>)protocol forAccount:(OTRAccount *)account
|
|
{
|
|
@synchronized (self) {
|
|
[self.protocolManagers setObject:protocol forKey:account.uniqueId];
|
|
}
|
|
[self.KVOController observe:protocol keyPath:NSStringFromSelector(@selector(loginStatus)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld action:@selector(protocolDidChange:)];
|
|
}
|
|
|
|
- (BOOL)existsProtocolForAccount:(OTRAccount *)account
|
|
{
|
|
NSParameterAssert(account.uniqueId);
|
|
if (!account.uniqueId) { return NO; }
|
|
@synchronized (self) {
|
|
return [self.protocolManagers objectForKey:account.uniqueId] != nil;
|
|
}
|
|
}
|
|
|
|
- (void)setProtocol:(id <OTRProtocol>)protocol forAccount:(OTRAccount *)account
|
|
{
|
|
NSParameterAssert(protocol);
|
|
NSParameterAssert(account.uniqueId);
|
|
if (!protocol || !account.uniqueId) { return; }
|
|
[self addProtocol:protocol forAccount:account];
|
|
}
|
|
|
|
- (id <OTRProtocol>)protocolForAccount:(OTRAccount *)account
|
|
{
|
|
NSParameterAssert(account);
|
|
if (!account.uniqueId) { return nil; }
|
|
id <OTRProtocol> protocol = nil;
|
|
@synchronized (self) {
|
|
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
|
if(!protocol)
|
|
{
|
|
protocol = [[[account protocolClass] alloc] initWithAccount:account];
|
|
if (protocol && account.uniqueId) {
|
|
[self addProtocol:protocol forAccount:account];
|
|
}
|
|
}
|
|
}
|
|
return protocol;
|
|
}
|
|
|
|
- (nullable OTRXMPPManager*)xmppManagerForAccount:(OTRAccount *)account {
|
|
OTRXMPPManager *xmpp = (OTRXMPPManager*)[self protocolForAccount:account];
|
|
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
|
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
|
DDLogError(@"Wrong protocol class for account %@", account);
|
|
return nil;
|
|
}
|
|
return xmpp;
|
|
}
|
|
|
|
- (void)loginAccount:(OTRAccount *)account userInitiated:(BOOL)userInitiated
|
|
{
|
|
NSParameterAssert(account);
|
|
if (!account) { return; }
|
|
id <OTRProtocol> protocol = [self protocolForAccount:account];
|
|
|
|
[protocol connectUserInitiated:userInitiated];
|
|
}
|
|
|
|
- (void)loginAccount:(OTRAccount *)account
|
|
{
|
|
[self loginAccount:account userInitiated:NO];
|
|
}
|
|
|
|
- (void)loginAccounts:(NSArray *)accounts
|
|
{
|
|
[accounts enumerateObjectsUsingBlock:^(OTRAccount * account, NSUInteger idx, BOOL *stop) {
|
|
[self loginAccount:account];
|
|
}];
|
|
}
|
|
|
|
- (void)goAwayForAllAccounts {
|
|
@synchronized (self) {
|
|
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(id key, id <OTRProtocol> protocol, BOOL *stop) {
|
|
if ([protocol isKindOfClass:[OTRXMPPManager class]]) {
|
|
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
|
[xmpp goAway];
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)disconnectAllAccountsSocketOnly:(BOOL)socketOnly timeout:(NSTimeInterval)timeout completionBlock:(nullable void (^)())completionBlock
|
|
{
|
|
@synchronized (self) {
|
|
dispatch_group_t group = dispatch_group_create();
|
|
NSMutableDictionary<NSString*, NSObject<OTRProtocol>*> *observingManagersForTokens = [NSMutableDictionary new];
|
|
for (NSObject<OTRProtocol> *manager in self.protocolManagers.allValues) {
|
|
OTRXMPPManager *xmpp = (OTRXMPPManager*)manager;
|
|
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
|
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
|
DDLogError(@"Wrong protocol class for manager %@", manager);
|
|
continue;
|
|
}
|
|
|
|
if (xmpp.loginStatus != OTRLoginStatusDisconnected) {
|
|
dispatch_group_enter(group);
|
|
NSString *token = [xmpp addObserverForKeyPath:NSStringFromSelector(@selector(loginStatus))
|
|
options:0
|
|
block:^(NSString *keyPath, OTRXMPPManager *mgr, NSDictionary *change) {
|
|
if (mgr.loginStatus == OTRLoginStatusDisconnected) {
|
|
dispatch_group_leave(group);
|
|
}
|
|
}];
|
|
observingManagersForTokens[token] = manager;
|
|
[manager disconnectSocketOnly:socketOnly];
|
|
}
|
|
}
|
|
if (timeout > 0) {
|
|
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t) (timeout * NSEC_PER_SEC)));
|
|
}
|
|
for (NSString *token in observingManagersForTokens.allKeys) {
|
|
[observingManagersForTokens[token] removeObserverForToken:token];
|
|
}
|
|
if (completionBlock != nil) {
|
|
completionBlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)disconnectAllAccounts
|
|
{
|
|
[self disconnectAllAccountsSocketOnly:NO timeout:0 completionBlock:nil];
|
|
}
|
|
|
|
- (void)protocolDidChange:(NSDictionary *)change
|
|
{
|
|
__block NSUInteger connected = 0;
|
|
__block NSUInteger connecting = 0;
|
|
@synchronized (self) {
|
|
[self.protocolManagers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id<OTRProtocol> _Nonnull obj, BOOL * _Nonnull stop) {
|
|
OTRXMPPManager *xmpp = (OTRXMPPManager*)obj;
|
|
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
|
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
|
DDLogError(@"Wrong protocol class for account %@", obj);
|
|
return;
|
|
}
|
|
if (xmpp.loginStatus == OTRLoginStatusAuthenticated) {
|
|
connected++;
|
|
} else if (xmpp.loginStatus == OTRLoginStatusConnecting) {
|
|
connecting++;
|
|
}
|
|
}];
|
|
}
|
|
self.numberOfConnectedProtocols = connected;
|
|
self.numberOfConnectingProtocols = connecting;
|
|
}
|
|
|
|
-(BOOL)isAccountConnected:(OTRAccount *)account;
|
|
{
|
|
BOOL connected = NO;
|
|
id <OTRProtocol> protocol = nil;
|
|
@synchronized (self) {
|
|
protocol = [self.protocolManagers objectForKey:account.uniqueId];
|
|
}
|
|
OTRXMPPManager *xmpp = (OTRXMPPManager*)protocol;
|
|
NSParameterAssert([xmpp isKindOfClass:OTRXMPPManager.class]);
|
|
if (![xmpp isKindOfClass:OTRXMPPManager.class]) {
|
|
DDLogError(@"Wrong protocol class %@", protocol);
|
|
return NO;
|
|
}
|
|
if (xmpp) {
|
|
connected = xmpp.loginStatus == OTRLoginStatusAuthenticated;
|
|
}
|
|
return connected;
|
|
}
|
|
|
|
- (void)sendMessage:(OTROutgoingMessage *)message {
|
|
__block OTRAccount * account = nil;
|
|
[OTRDatabaseManager.shared.readConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];
|
|
account = [OTRAccount fetchObjectWithUniqueID:buddy.accountUniqueId transaction:transaction];
|
|
} completionBlock:^{
|
|
OTRProtocolManager * protocolManager = [OTRProtocolManager sharedInstance];
|
|
id<OTRProtocol> protocol = [protocolManager protocolForAccount:account];
|
|
[protocol sendMessage:message];
|
|
}];
|
|
}
|
|
|
|
+ (void)handleInviteForJID:(XMPPJID *)jid otrFingerprint:(nullable NSString *)otrFingerprint buddyAddedCallback:(nullable void (^)(OTRBuddy *buddy))buddyAddedCallback {
|
|
NSParameterAssert(jid);
|
|
if (!jid) { return; }
|
|
NSString *jidString = jid.bare;
|
|
NSString *message = [NSString stringWithString:jidString];
|
|
if (otrFingerprint.length == 40) {
|
|
message = [message stringByAppendingFormat:@"\n%@", otrFingerprint];
|
|
}
|
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:ADD_BUDDY_STRING() message:message preferredStyle:(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert];
|
|
NSMutableArray<OTRAccount*> *accounts = [NSMutableArray array];
|
|
[OTRDatabaseManager.shared.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
NSArray<OTRAccount*> *allAccounts = [OTRAccount allAccountsWithTransaction:transaction];
|
|
[allAccounts enumerateObjectsUsingBlock:^(OTRAccount * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
if (!obj.isArchived) {
|
|
[accounts addObject:obj];
|
|
}
|
|
}];
|
|
}];
|
|
[accounts enumerateObjectsUsingBlock:^(OTRAccount *account, NSUInteger idx, BOOL *stop) {
|
|
if ([account isKindOfClass:[OTRXMPPAccount class]]) {
|
|
OTRXMPPAccount *xmppAccount = (OTRXMPPAccount*)account;
|
|
if ([xmppAccount.bareJID isEqualToJID:jid options:XMPPJIDCompareBare]) {
|
|
// Don't allow adding yourself to yourself
|
|
return;
|
|
}
|
|
// Not the best way to do this, but only show "Add" if you have a single account, otherwise show the account name to add it to.
|
|
NSString *title = nil;
|
|
if (accounts.count == 1) {
|
|
title = ADD_STRING();
|
|
} else {
|
|
title = account.username;
|
|
}
|
|
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
|
OTRXMPPManager *manager = (OTRXMPPManager *)[[OTRProtocolManager sharedInstance] protocolForAccount:account];
|
|
|
|
OTRXMPPBuddy *buddy = [manager addToRosterWithJID:jid displayName:nil];
|
|
|
|
/* TODO OTR fingerprint verificaction
|
|
if (otrFingerprint) {
|
|
// We are missing a method to add fingerprint to trust store
|
|
[[OTRKit sharedInstance] setActiveFingerprintVerificationForUsername:buddy.username accountName:account.username protocol:account.protocolTypeString verified:YES completion:nil];
|
|
}*/
|
|
if (buddyAddedCallback != nil) {
|
|
buddyAddedCallback(buddy);
|
|
}
|
|
}];
|
|
[alert addAction:action];
|
|
}
|
|
}];
|
|
if (alert.actions.count > 0) {
|
|
// No need to show anything if only option is "cancel"
|
|
UIAlertAction *cancel = [UIAlertAction actionWithTitle:CANCEL_STRING() style:UIAlertActionStyleCancel handler:nil];
|
|
[alert addAction:cancel];
|
|
// This is janky af
|
|
[OTRAppDelegate.appDelegate.window.rootViewController presentViewController:alert animated:YES completion:nil];
|
|
}
|
|
}
|
|
|
|
#pragma mark Singleton Object Methods
|
|
|
|
+ (OTRProtocolManager*) shared {
|
|
return [self sharedInstance];
|
|
}
|
|
|
|
+ (instancetype)sharedInstance
|
|
{
|
|
static id sharedInstance = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
sharedInstance = [[self alloc] init];
|
|
});
|
|
return sharedInstance;
|
|
}
|
|
|
|
@end
|