// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2019-2020 Threema GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, // as published by the Free Software Foundation. // // 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #import "GCDAsyncSocketFactory.h" #import "GCDAsyncSocket.h" #import "GCDAsyncHTTPSProxySocket.h" #import "GCDAsyncSOCKSProxySocket.h" #import "ValidationLogger.h" #ifdef DEBUG static const DDLogLevel ddLogLevel = DDLogLevelVerbose; #else static const DDLogLevel ddLogLevel = DDLogLevelWarning; #endif @implementation GCDAsyncSocketFactory static void _AutoConfigurationCallback(void *info, CFArrayRef proxyList, CFErrorRef error); struct AutoConfigLoadStatus { bool finished; CFArrayRef proxyList; }; + (GCDAsyncSocket*)proxyAwareAsyncSocketForHost:(NSString*)host port:(NSNumber*)port delegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue { NSDictionary *systemProxies = (__bridge NSDictionary*)CFNetworkCopySystemProxySettings(); NSURL *targetUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@:%@", host, port]]; NSArray *urlProxies = (__bridge NSArray*)CFNetworkCopyProxiesForURL((__bridge CFURLRef _Nonnull)targetUrl, (__bridge CFDictionaryRef _Nonnull)(systemProxies)); return [GCDAsyncSocketFactory proxyAwareAsyncSocketForProxyList:urlProxies targetUrl:targetUrl delegate:delegate delegateQueue:delegateQueue]; } + (GCDAsyncSocket*)proxyAwareAsyncSocketForProxyList:(NSArray*)proxyList targetUrl:(NSURL*)targetUrl delegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue { if (proxyList.count > 0) { NSDictionary *proxy = proxyList[0]; if ([proxy[(NSString*)kCFProxyTypeKey] isEqualToString:(NSString*)kCFProxyTypeSOCKS]) { NSString *host = proxy[(NSString*)kCFProxyHostNameKey]; NSNumber *port = proxy[(NSString*)kCFProxyPortNumberKey]; NSString *username = proxy[(NSString*)kCFProxyUsernameKey]; NSString *password = proxy[(NSString*)kCFProxyPasswordKey]; [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Using SOCKS proxy %@:%@", host, port]]; GCDAsyncSOCKSProxySocket *proxySocket = [[GCDAsyncSOCKSProxySocket alloc] initWithDelegate:delegate delegateQueue:delegateQueue]; [proxySocket setProxyHost:host port:port.intValue version:GCDAsyncSocketSOCKSVersion5]; if (username && password) { [proxySocket setProxyUsername:username password:password]; } return proxySocket; } else if ([proxy[(NSString*)kCFProxyTypeKey] isEqualToString:(NSString*)kCFProxyTypeHTTPS]) { NSString *host = proxy[(NSString*)kCFProxyHostNameKey]; NSNumber *port = proxy[(NSString*)kCFProxyPortNumberKey]; [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Using HTTPS proxy %@:%@", host, port]]; GCDAsyncHTTPSProxySocket *proxySocket = [[GCDAsyncHTTPSProxySocket alloc] initWithDelegate:delegate delegateQueue:delegateQueue]; [proxySocket setProxyHost:host port:port.intValue]; return proxySocket; } else if ([proxy[(NSString*)kCFProxyTypeKey] isEqualToString:(NSString*)kCFProxyTypeAutoConfigurationURL]) { NSURL *autoConfigUrl = proxy[(NSString*)kCFProxyAutoConfigurationURLKey]; [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Loading proxy auto config from %@", autoConfigUrl]]; struct AutoConfigLoadStatus status; status.finished = false; CFStreamClientContext ctxt = { 0, &status, NULL, NULL, NULL }; CFRunLoopSourceRef rls = CFNetworkExecuteProxyAutoConfigurationURL((__bridge CFURLRef _Nonnull)autoConfigUrl, (__bridge CFURLRef _Nonnull)(targetUrl), _AutoConfigurationCallback, &ctxt); CFStringRef mode = CFSTR("__GCDProxyAutoConfigRunLoopMode"); CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, mode); CFAbsoluteTime stopTime = CFAbsoluteTimeGetCurrent() + 10.0; do { (void) CFRunLoopRunInMode(mode, 0.1, TRUE); } while (!status.finished && CFAbsoluteTimeGetCurrent() < stopTime); if (status.finished && status.proxyList) { DDLogVerbose(@"Auto config proxy list: %@", status.proxyList); return [GCDAsyncSocketFactory proxyAwareAsyncSocketForProxyList:(__bridge NSArray *)(status.proxyList) targetUrl:targetUrl delegate:delegate delegateQueue:delegateQueue]; } if (rls) { if (CFRunLoopSourceIsValid(rls)) { CFRunLoopSourceInvalidate(rls); CFRelease(rls); } } } } return [[GCDAsyncSocket alloc] initWithDelegate:delegate delegateQueue:delegateQueue]; } static void _AutoConfigurationCallback(void *info, CFArrayRef proxyList, CFErrorRef error) { struct AutoConfigLoadStatus *status = info; status->proxyList = proxyList; status->finished = true; } @end