/* * ZeroTier One - Network Virtualization Everywhere * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ * * 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 "ServiceCom.h" #import "AuthtokenCopy.h" #import "Network.h" #import "NodeStatus.h" @import AppKit; @interface ServiceCom (Private) - (NSString*)key; @end @implementation ServiceCom + (ServiceCom*)sharedInstance { static ServiceCom *sc = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sc = [[ServiceCom alloc] init]; }); return sc; } - (id)init { self = [super init]; if(self) { baseURL = @"http://127.0.0.1:9993"; session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; _isQuitting = NO; _resetKey = NO; } return self; } - (NSString*)key:(NSError* __autoreleasing *)err { static NSString *k = nil; static NSUInteger resetCount = 0; @synchronized (self) { if (_isQuitting) { return @""; } if (_resetKey && k != nil) { k = nil; ++resetCount; NSLog(@"ResetCount: %lu", (unsigned long)resetCount); if (resetCount > 10) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSAlert *alert = [NSAlert alertWithMessageText:@"Error obtaining Auth Token" defaultButton:@"Quit" alternateButton:@"Retry" otherButton:nil informativeTextWithFormat:@"Please ensure ZeroTier is installed correctly"]; alert.alertStyle = NSCriticalAlertStyle; NSModalResponse res; if (!_isQuitting) { res = [alert runModal]; } else { return; } if(res == 1) { _isQuitting = YES; [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } }]; return @""; } } if (k == nil) { NSError *error = nil; NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error]; if (error) { NSLog(@"Error: %@", error); return @""; } appSupportDir = [[appSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"]; NSURL *authtokenURL = [appSupportDir URLByAppendingPathComponent:@"authtoken.secret"]; if (!_resetKey && [[NSFileManager defaultManager] fileExistsAtPath:[authtokenURL path]]) { k = [NSString stringWithContentsOfURL:authtokenURL encoding:NSUTF8StringEncoding error:&error]; k = [k stringByReplacingOccurrencesOfString:@"\n" withString:@""]; if (error) { NSLog(@"Error: %@", error); k = nil; *err = error; return @""; } } else { _resetKey = NO; NSURL *sysAppSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSSystemDomainMask appropriateForURL:nil create:false error:nil]; sysAppSupportDir = [[sysAppSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"]; NSURL *sysAuthtokenURL = [sysAppSupportDir URLByAppendingPathComponent:@"authtoken.secret"]; if(![[NSFileManager defaultManager] fileExistsAtPath:[sysAuthtokenURL path]]) { } [[NSFileManager defaultManager] createDirectoryAtURL:appSupportDir withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { NSLog(@"Error: %@", error); *err = error; k = nil; return @""; } AuthorizationRef authRef; OSStatus status = AuthorizationCreate(nil, nil, kAuthorizationFlagDefaults, &authRef); if (status != errAuthorizationSuccess) { NSLog(@"Authorization Failed! %d", status); NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't create AuthorizationRef", nil), }; *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; return @""; } AuthorizationItem authItem; authItem.name = kAuthorizationRightExecute; authItem.valueLength = 0; authItem.flags = 0; AuthorizationRights authRights; authRights.count = 1; authRights.items = &authItem; AuthorizationFlags authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; status = AuthorizationCopyRights(authRef, &authRights, nil, authFlags, nil); if (status != errAuthorizationSuccess) { NSLog(@"Authorization Failed! %d", status); NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't copy authorization rights", nil), }; *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; return @""; } NSString *localKey = getAdminAuthToken(authRef); AuthorizationFree(authRef, kAuthorizationFlagDestroyRights); if (localKey != nil && [localKey lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > 0) { k = localKey; [localKey writeToURL:authtokenURL atomically:YES encoding:NSUTF8StringEncoding error:&error]; if (error) { NSLog(@"Error writing token to disk: %@", error); *err = error; } } } } if (k == nil) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Unknown error finding authorization key", nil), }; *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; return @""; } } return k; } - (void)getNetworklist:(void (^)(NSArray *))completionHandler error:(NSError *__autoreleasing*)error { NSString* key = [self key:error]; if(*error) { return; } NSString *urlString = [[baseURL stringByAppendingString:@"/network?auth="] stringByAppendingString:key]; NSURL *url = [NSURL URLWithString:urlString]; NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { if (err) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSAlert *alert = [NSAlert alertWithError:err]; alert.alertStyle = NSCriticalAlertStyle; [alert addButtonWithTitle:@"Quit"]; [alert addButtonWithTitle:@"Retry"]; NSModalResponse res; if (!_isQuitting) { res = [alert runModal]; } else { return; } if(res == NSAlertFirstButtonReturn) { [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; _isQuitting = YES; } }]; return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; NSInteger status = [httpResponse statusCode]; NSError *err2; if (status == 200) { NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err2]; if (err) { NSLog(@"Error fetching network list: %@", err2); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSAlert *alert = [NSAlert alertWithError:err2]; alert.alertStyle = NSCriticalAlertStyle; [alert addButtonWithTitle:@"Quit"]; [alert addButtonWithTitle:@"Retry"]; NSModalResponse res; if (!_isQuitting) { res = [alert runModal]; } else { return; } if(res == NSAlertFirstButtonReturn) { _isQuitting = YES; [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } }]; return; } NSMutableArray *networks = [[NSMutableArray alloc] init]; for(NSDictionary *dict in json) { [networks addObject:[[Network alloc] initWithJsonData:dict]]; } completionHandler(networks); } else if (status == 401) { self->_resetKey = YES; } }]; [task resume]; } - (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error { NSString *key = [self key:error]; if(*error) { return; } NSString *urlString = [[baseURL stringByAppendingString:@"/status?auth="] stringByAppendingString:key]; NSURL *url = [NSURL URLWithString:urlString]; NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { if(err) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSAlert *alert = [NSAlert alertWithError:err]; alert.alertStyle = NSCriticalAlertStyle; [alert addButtonWithTitle:@"Quit"]; [alert addButtonWithTitle:@"Retry"]; NSModalResponse res; if (!_isQuitting) { res = [alert runModal]; } else { return; } if(res == NSAlertFirstButtonReturn) { [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; _isQuitting = YES; } }]; return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; NSInteger status = [httpResponse statusCode]; NSError *err2; if(status == 200) { NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err2]; if(err2) { NSLog(@"Error fetching node status: %@", err2); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSAlert *alert = [NSAlert alertWithError:err2]; alert.alertStyle = NSCriticalAlertStyle; [alert addButtonWithTitle:@"Quit"]; [alert addButtonWithTitle:@"Retry"]; NSModalResponse res; if (!_isQuitting) { res = [alert runModal]; } else { return; } if(res == NSAlertFirstButtonReturn) { [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; _isQuitting = YES; } }]; return; } NodeStatus *status = [[NodeStatus alloc] initWithJsonData:json]; completionHandler(status); } else if (status == 401) { self->_resetKey = YES; } }]; [task resume]; } - (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault allowDNS:(BOOL)allowDNS error:(NSError *__autoreleasing*)error { NSString *key = [self key:error]; if(*error) { return; } NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"] stringByAppendingString:networkId] stringByAppendingString:@"?auth="] stringByAppendingString:key]; NSURL *url = [NSURL URLWithString:urlString]; NSMutableDictionary *jsonDict = [NSMutableDictionary dictionary]; [jsonDict setObject:[NSNumber numberWithBool:allowManaged] forKey:@"allowManaged"]; [jsonDict setObject:[NSNumber numberWithBool:allowGlobal] forKey:@"allowGlobal"]; [jsonDict setObject:[NSNumber numberWithBool:allowDefault] forKey:@"allowDefault"]; [jsonDict setObject:[NSNumber numberWithBool:allowDNS] forKey:@"allowDNS"]; NSError *err = nil; NSData *json = [NSJSONSerialization dataWithJSONObject:jsonDict options:0 error:&err]; if(err) { NSLog(@"Error creating json data: %@", err); *error = err; return; } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; request.HTTPBody = json; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { if(err) { NSLog(@"Error posting join request: %@", err); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSAlert *alert = [NSAlert alertWithError:err]; alert.alertStyle = NSCriticalAlertStyle; [alert addButtonWithTitle:@"Quit"]; [alert addButtonWithTitle:@"Retry"]; NSModalResponse res; if (!_isQuitting) { res = [alert runModal]; } else { return; } if(res == NSAlertFirstButtonReturn) { [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; _isQuitting = YES; } }]; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; NSInteger status = [httpResponse statusCode]; if(status == 200) { NSLog(@"join ok"); } else if (status == 401) { self->_resetKey = YES; } else { NSLog(@"join error: %ld", (long)status); } }]; [task resume]; } - (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error { NSString *key = [self key:error]; if(*error) { return; } NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"] stringByAppendingString:networkId] stringByAppendingString:@"?auth="] stringByAppendingString:key]; NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"DELETE"; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { if(err) { NSLog(@"Error posting delete request: %@", err); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSAlert *alert = [NSAlert alertWithError:err]; alert.alertStyle = NSCriticalAlertStyle; [alert addButtonWithTitle:@"Quit"]; [alert addButtonWithTitle:@"Retry"]; NSModalResponse res; if (!_isQuitting) { res = [alert runModal]; } else { return; } if(res == NSAlertFirstButtonReturn) { [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; _isQuitting = YES; } }]; return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; NSInteger status = httpResponse.statusCode; if(status == 200) { NSLog(@"leave ok"); } else if (status == 401) { self->_resetKey = YES; } else { NSLog(@"leave error: %ld", status); } }]; [task resume]; } @end