// // MBIMBridge.m // MessagesBridge // // Created by James Magahern on 11/12/18. // Copyright © 2018 James Magahern. All rights reserved. // #import "MBIMBridge.h" #import "MBIMBridge_Private.h" #import "MBIMBridgeOperation.h" #import "MBIMConcurrentHTTPServer.h" #import "MBIMHTTPConnection.h" #import "MBIMUpdateQueue.h" #import "hooking.h" #import "HTTPServer.h" #import "IMCore_ClassDump.h" #import "IMFoundation_ClassDump.h" static const UInt16 kDefaultPort = 5738; static NSString *const MBIMBridgeToken = @"net.buzzert.kordophone"; @interface MBIMBridge (/* INTERNAL */) { __strong NSArray *_sslCertificateAndIdentity; } @property (nonatomic, strong) MBIMConcurrentHTTPServer *httpServer; @property (nonatomic, strong) NSOperationQueue *operationQueue; - (instancetype)_init; @end @implementation MBIMBridge + (instancetype)sharedInstance { static dispatch_once_t onceToken; static __strong MBIMBridge *sharedBridge = nil; dispatch_once(&onceToken, ^{ sharedBridge = [[MBIMBridge alloc] _init]; }); return sharedBridge; } - (instancetype)_init { self = [super init]; if (self) { self.port = kDefaultPort; _operationQueue = [[NSOperationQueue alloc] init]; _operationQueue.maxConcurrentOperationCount = 5; } return self; } - (void)_terminate { // *shrug* exit(1); } - (NSArray *)sslCertificateAndIdentity { if (!_sslCertificateAndIdentity && self.sslCertPath) { // Get the p12 NSError *error = nil; NSData *certData = [NSData dataWithContentsOfFile:self.sslCertPath options:0 error:&error]; if (!certData || error) { MBIMLogError(@"Unable to load SSL certificate from file: %@", [error localizedDescription]); return nil; } CFArrayRef items = nil; OSStatus status = SecPKCS12Import( (__bridge CFDataRef)certData, (__bridge CFDictionaryRef) @{ (__bridge id)kSecImportExportPassphrase : @"xNAq3vn)^PNu}[&gyQ4MZeV?J" }, &items ); if (status != noErr) { MBIMLogError(@"Error importing PKCS12: SecPKCS12Import status: %d", status); return nil; } CFDictionaryRef certDict = CFArrayGetValueAtIndex(items, 0); if (!certDict) { MBIMLogError(@"Error parsing the SSL certificate"); return nil; } SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(certDict, kSecImportItemIdentity); _sslCertificateAndIdentity = @[ (__bridge id)identity ]; } return _sslCertificateAndIdentity; } - (void)checkSSLCertificate { if (self.usesSSL) { NSArray *certAndIdentity = [self sslCertificateAndIdentity]; if ([certAndIdentity count]) { MBIMLogInfo(@"SSL Certificate looks okay"); } else { MBIMLogFatal(@"Wasn't able to load SSL certificate. Bailing..."); [self _terminate]; } } } #pragma mark - #pragma mark Connection - (void)connect { #if HOOK_IMAGENT char *errorString = nil; BOOL hooked = HookIMAgent(self.dylibPath, &errorString); if (!hooked) { NSString *errorNSString = [NSString stringWithUTF8String:errorString]; MBIMLogInfo(@"Error hooking imagent: %@", errorNSString); return; } #endif [self registerForNotifications]; [[IMDaemonController sharedInstance] setDelegate:(id)self]; [[[IMDaemonController sharedInstance] listener] addHandler:self]; if (![[IMDaemonController sharedInstance] hasListenerForID:MBIMBridgeToken]) { if (![[IMDaemonController sharedInstance] addListenerID:MBIMBridgeToken capabilities:(kFZListenerCapFileTransfers | kFZListenerCapManageStatus | kFZListenerCapChats | kFZListenerCapMessageHistory | kFZListenerCapIDQueries | kFZListenerCapSendMessages)]) { MBIMLogFatal(@"Failed to connect to imagent"); [self _terminate]; } } [self checkSSLCertificate]; [self startWebServer]; } - (void)disconnect { [[IMDaemonController sharedInstance] removeListenerID:MBIMBridgeToken]; } #pragma mark - #pragma mark Notifications - (void)registerForNotifications { (void)[IMChatRegistry sharedInstance]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_messageReceived:) name:IMChatMessageReceivedNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_chatRegistryDidLoad:) name:IMChatRegistryDidLoadNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_chatItemsDidChange:) name:IMChatItemsDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_unreadCountChanged:) name:IMChatRegistryUnreadCountChangedNotification object:nil]; } - (void)_unreadCountChanged:(NSNotification *)notification { // Not a lot of useful information plumbed here... } - (void)_messageReceived:(NSNotification *)notification { MBIMLogInfo(@"Received message from chat with GUID: %@", [[notification object] guid]); IMChat *chat = [notification object]; IMMessage *message = [[notification userInfo] objectForKey:IMChatValueKey]; if (chat && message) { if (![message isFromMe]) { MBIMUpdateItem *updateItem = [[MBIMUpdateItem alloc] init]; updateItem.changedChat = chat; updateItem.addedMessage = message; [[MBIMUpdateQueue sharedInstance] enqueueUpdateItem:updateItem]; } else { // TODO: care about messages from me? } } } - (void)_chatRegistryDidLoad:(NSNotification *)notification { MBIMLogInfo(@"Loaded chat registry. %lu existing chats", (unsigned long)[[IMChatRegistry sharedInstance] numberOfExistingChats]); } - (void)_chatItemsDidChange:(NSNotification *)notification { IMChat *chat = [notification object]; if (chat) { MBIMLogInfo(@"Chat items change for GUID: %@", [chat guid]); MBIMUpdateItem *updateItem = [[MBIMUpdateItem alloc] init]; updateItem.changedChat = chat; [[MBIMUpdateQueue sharedInstance] enqueueUpdateItem:updateItem]; } } #pragma mark - #pragma mark Web Server initialization - (void)startWebServer { self.httpServer = [[MBIMConcurrentHTTPServer alloc] init]; [self.httpServer setConnectionClass:[MBIMHTTPConnection class]]; [self.httpServer setPort:self.port]; NSError *error = nil; if (![self.httpServer start:&error]) { MBIMLogError(@"Error starting HTTP server: %@", [error localizedDescription]); } else { MBIMLogNotify(@"Started Kordophone HTTP server on port %u", self.port); } } #pragma mark - #pragma mark Daemon lifecycle - (void)daemonControllerWillConnect { MBIMLogInfo(@"Connecting to imagent..."); } - (void)daemonControllerDidConnect { MBIMLogInfo(@"imagent responded."); IMAccount *iMessageAccount = [[IMAccountController sharedInstance] bestAccountForService:[IMServiceImpl iMessageService]]; if (iMessageAccount) { MBIMLogInfo(@"Successfully got accounts from imagent"); MBIMLogInfo(@"iMessage account connected: %@", iMessageAccount); } else { MBIMLogFatal(@"ERROR: imagent returned no accounts (not entitled? speak with Agent Hook)"); [self _terminate]; } } - (void)daemonControllerDidDisconnect { MBIMLogInfo(@"Disconnected from imagent"); } - (void)daemonConnectionLost { MBIMLogError(@"Connection lost to imagent"); } @end