Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,87 @@
/*
File: Reachability.h
Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
Version: 2.2
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2010 Apple Inc. All Rights Reserved.
*/
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <netinet/in.h>
typedef enum
{
NotReachable = 0,
ReachableViaWiFi = 1,
ReachableViaWWAN = 2
} NetworkStatus;
#define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification"
@interface Reachability : NSObject
{
BOOL localWiFiRef;
SCNetworkReachabilityRef reachabilityRef;
}
// reachabilityWithHostName- Use to check the reachability of a particular host name.
+ (Reachability *)reachabilityWithHostName:(NSString *)hostName;
// reachabilityWithAddress- Use to check the reachability of a particular IP address.
+ (Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress;
// reachabilityForInternetConnection- checks whether the default route is available.
// Should be used by applications that do not connect to a particular host
+ (Reachability *)reachabilityForInternetConnection;
// reachabilityForLocalWiFi- checks whether a local wifi connection is available.
+ (Reachability *)reachabilityForLocalWiFi;
// Start listening for reachability notifications on the current run loop
- (BOOL)startNotifier;
- (void)stopNotifier;
- (NetworkStatus)currentReachabilityStatus;
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
- (BOOL)connectionRequired;
@end

View File

@@ -0,0 +1,278 @@
/*
File: Reachability.m
Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
Version: 2.2
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2010 Apple Inc. All Rights Reserved.
*/
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <CoreFoundation/CoreFoundation.h>
#import "Reachability.h"
#define kShouldPrintReachabilityFlags 1
static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char *comment)
{
#if kShouldPrintReachabilityFlags
NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n",
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', comment);
#endif
}
@implementation Reachability
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
void *info)
{
#pragma unused(target, flags)
NSCAssert(info != nullptr, @"info was nullptr in ReachabilityCallback");
NSCAssert([(NSObject *)info isKindOfClass:[Reachability class]],
@"info was wrong class in ReachabilityCallback");
// We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively
// in case someone uses the Reachablity object in a different thread.
NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
Reachability *noteObject = (Reachability *)info;
// Post a notification to notify the client that the network reachability changed.
[[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification
object:noteObject];
[myPool release];
}
- (BOOL)startNotifier
{
BOOL retVal = NO;
SCNetworkReachabilityContext context = { 0, self, nullptr, nullptr, nullptr };
if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context))
{
if (SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode))
{
retVal = YES;
}
}
return retVal;
}
- (void)stopNotifier
{
if (reachabilityRef != nullptr)
{
SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
}
}
- (void)dealloc
{
[self stopNotifier];
if (reachabilityRef != nullptr)
{
CFRelease(reachabilityRef);
}
[super dealloc];
}
+ (Reachability *)reachabilityWithHostName:(NSString *)hostName;
{
Reachability *retVal = nullptr;
SCNetworkReachabilityRef reachability =
SCNetworkReachabilityCreateWithName(nullptr, [hostName UTF8String]);
if (reachability != nullptr)
{
retVal = [[[self alloc] init] autorelease];
if (retVal != nullptr)
{
retVal->reachabilityRef = reachability;
retVal->localWiFiRef = NO;
}
}
return retVal;
}
+ (Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress;
{
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(
kCFAllocatorDefault, (const struct sockaddr *)hostAddress);
Reachability *retVal = nullptr;
if (reachability != nullptr)
{
retVal = [[[self alloc] init] autorelease];
if (retVal != nullptr)
{
retVal->reachabilityRef = reachability;
retVal->localWiFiRef = NO;
}
}
return retVal;
}
+ (Reachability *)reachabilityForInternetConnection;
{
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress:&zeroAddress];
}
+ (Reachability *)reachabilityForLocalWiFi;
{
struct sockaddr_in localWifiAddress;
bzero(&localWifiAddress, sizeof(localWifiAddress));
localWifiAddress.sin_len = sizeof(localWifiAddress);
localWifiAddress.sin_family = AF_INET;
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
Reachability *retVal = [self reachabilityWithAddress:&localWifiAddress];
if (retVal != nullptr)
{
retVal->localWiFiRef = YES;
}
return retVal;
}
#pragma mark Network Flag Handling
- (NetworkStatus)localWiFiStatusForFlags:(SCNetworkReachabilityFlags)flags
{
PrintReachabilityFlags(flags, "localWiFiStatusForFlags");
BOOL retVal = NotReachable;
if ((flags & kSCNetworkReachabilityFlagsReachable) &&
(flags & kSCNetworkReachabilityFlagsIsDirect))
{
retVal = ReachableViaWiFi;
}
return retVal;
}
- (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags
{
PrintReachabilityFlags(flags, "networkStatusForFlags");
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
{
// if target host is not reachable
return NotReachable;
}
BOOL retVal = NotReachable;
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
{
// if target host is reachable and no connection is required
// then we'll assume (for now) that your on Wi-Fi
retVal = ReachableViaWiFi;
}
if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
{
// ... and the connection is on-demand (or on-traffic) if the
// calling application is using the CFSocketStream or higher APIs
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
{
// ... and no [user] intervention is needed
retVal = ReachableViaWiFi;
}
}
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
{
// ... but WWAN connections are OK if the calling application
// is using the CFNetwork (CFSocketStream?) APIs.
retVal = ReachableViaWWAN;
}
return retVal;
}
- (BOOL)connectionRequired;
{
NSAssert(reachabilityRef != nullptr, @"connectionRequired called with nullptr reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
- (NetworkStatus)currentReachabilityStatus
{
NSAssert(reachabilityRef != nullptr,
@"currentNetworkStatus called with nullptr reachabilityRef");
NetworkStatus retVal = NotReachable;
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
{
if (localWiFiRef)
{
retVal = [self localWiFiStatusForFlags:flags];
}
else
{
retVal = [self networkStatusForFlags:flags];
}
}
return retVal;
}
@end

View File

@@ -0,0 +1,48 @@
//
// SFHFKeychainUtils.h
//
// Created by Buzz Andersen on 10/20/08.
// Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
// Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
@interface SFHFKeychainUtils : NSObject
{
}
+ (NSString *)getPasswordForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error;
+ (BOOL)storeUsername:(NSString *)username
andPassword:(NSString *)password
forServerName:(NSString *)serverName
updateExisting:(BOOL)updateExisting
error:(NSError **)error;
+ (BOOL)deleteItemForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error;
@end

View File

@@ -0,0 +1,501 @@
//
// SFHFKeychainUtils.m
//
// Created by Buzz Andersen on 10/20/08.
// Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
// Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "SFHFKeychainUtils.h"
#import <Security/Security.h>
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef)getKeychainItemReferenceForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error;
@end
#endif
@implementation SFHFKeychainUtils
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
+ (NSString *)getPasswordForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error
{
if (!username || !serviceName)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
return nil;
}
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername:username
andServerName:serverName
error:error];
if (*error || !item)
{
return nil;
}
// from Advanced Mac OS X Programming, ch. 16
UInt32 length;
char *password;
SecKeychainAttribute attributes[8];
SecKeychainAttributeList list;
attributes[0].tag = kSecAccountItemAttr;
attributes[1].tag = kSecDescriptionItemAttr;
attributes[2].tag = kSecLabelItemAttr;
attributes[3].tag = kSecModDateItemAttr;
list.count = 4;
list.attr = attributes;
OSStatus status = SecKeychainItemCopyContent(item, nullptr, &list, &length, (void **)&password);
if (status != noErr)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
return nil;
}
NSString *passwordString = nil;
if (password != nullptr)
{
char passwordBuffer[1024];
if (length > 1023)
{
length = 1023;
}
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';
passwordString = [NSString stringWithCString:passwordBuffer encoding:NSUTF8StringEncoding];
}
SecKeychainItemFreeContent(&list, password);
CFRelease(item);
return passwordString;
}
+ (void)storeUsername:(NSString *)username
andPassword:(NSString *)password
forServerName:(NSString *)serverName
updateExisting:(BOOL)updateExisting
error:(NSError **)error
{
if (!username || !password || !serverName)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
return;
}
OSStatus status = noErr;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername:username
andServerName:serverName
error:error];
if (*error && [*error code] != noErr)
{
return;
}
*error = nil;
if (item)
{
status = SecKeychainItemModifyAttributesAndData(
item, nullptr, strlen([password UTF8String]), [password UTF8String]);
CFRelease(item);
}
else
{
status = SecKeychainAddGenericPassword(
nullptr, strlen([serverName UTF8String]), [serverName UTF8String],
strlen([username UTF8String]), [username UTF8String], strlen([password UTF8String]),
[password UTF8String], nullptr);
}
if (status != noErr)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
}
}
+ (void)deleteItemForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error
{
if (!username || !serverName)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:2000 userInfo:nil];
return;
}
*error = nil;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername:username
andServerName:serverName
error:error];
if (*error && [*error code] != noErr)
{
return;
}
OSStatus status;
if (item)
{
status = SecKeychainItemDelete(item);
CFRelease(item);
}
if (status != noErr)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
}
}
+ (SecKeychainItemRef)getKeychainItemReferenceForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error
{
if (!username || !serverName)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
return nil;
}
*error = nil;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(
nullptr, strlen([serverName UTF8String]), [serverName UTF8String],
strlen([username UTF8String]), [username UTF8String], nullptr, nullptr, &item);
if (status != noErr)
{
if (status != errSecItemNotFound)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
code:status
userInfo:nil];
}
return nil;
}
return item;
}
#else
+ (NSString *)getPasswordForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error
{
if (!username || !serverName)
{
if (error != nil)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
}
return nil;
}
if (error != nil)
{
*error = nil;
}
// Set up a query dictionary with the base query attributes: item type (generic), username, and
// service
NSArray *keys = [[[NSArray alloc]
initWithObjects:(NSString *)kSecClass, kSecAttrAccount, kSecAttrService, nil] autorelease];
NSArray *objects = [[[NSArray alloc] initWithObjects:(NSString *)kSecClassGenericPassword,
username, serverName, nil] autorelease];
NSMutableDictionary *query = [[[NSMutableDictionary alloc] initWithObjects:objects
forKeys:keys] autorelease];
// First do a query for attributes, in case we already have a Keychain item with no password
// data set. One likely way such an incorrect item could have come about is due to the previous
// (incorrect) version of this code (which set the password as a generic attribute instead of
// password data).
NSDictionary *attributeResult = nullptr;
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
OSStatus status =
SecItemCopyMatching((CFDictionaryRef)attributeQuery, (CFTypeRef *)&attributeResult);
[attributeResult release];
[attributeQuery release];
if (status != noErr)
{
// No existing item found--simply return nil for the password
if (error != nil && status != errSecItemNotFound)
{
// Only return an error if a real exception happened--not simply for "not found."
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
code:status
userInfo:nil];
}
return nil;
}
// We have an existing item, now query for the password data associated with it.
NSData *resultData = nil;
NSMutableDictionary *passwordQuery = [query mutableCopy];
[passwordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
status = SecItemCopyMatching((CFDictionaryRef)passwordQuery, (CFTypeRef *)&resultData);
[resultData autorelease];
[passwordQuery release];
if (status != noErr)
{
if (status == errSecItemNotFound)
{
// We found attributes for the item previously, but no password now, so return a special
// error. Users of this API will probably want to detect this error and prompt the user
// to re-enter their credentials. When you attempt to store the re-entered credentials
// using storeUsername:andPassword:forServiceName:updateExisting:error
// the old, incorrect entry will be deleted and a new one with a properly encrypted
// password will be added.
if (error != nil)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
code:-1999
userInfo:nil];
}
}
else
{
// Something else went wrong. Simply return the normal Keychain API error code.
if (error != nil)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain
code:status
userInfo:nil];
}
}
return nil;
}
NSString *password = nil;
if (resultData)
{
password = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
}
else
{
// There is an existing item, but we weren't able to get password data for it for some
// reason, Possibly as a result of an item being incorrectly entered by the previous code.
// Set the -1999 error so the code above us can prompt the user again.
if (error != nil)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-1999 userInfo:nil];
}
}
return [password autorelease];
}
+ (BOOL)storeUsername:(NSString *)username
andPassword:(NSString *)password
forServerName:(NSString *)serverName
updateExisting:(BOOL)updateExisting
error:(NSError **)error
{
if (!username || !password || !serverName)
{
if (error != nil)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
}
return NO;
}
// See if we already have a password entered for these credentials.
NSError *getError = nil;
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername:username
andServerName:serverName
error:&getError];
if ([getError code] == -1999)
{
// There is an existing entry without a password properly stored (possibly as a result of
// the previous incorrect version of this code. Delete the existing item before moving on
// entering a correct one.
getError = nil;
[self deleteItemForUsername:username andServerName:serverName error:&getError];
if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
}
else if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
if (error != nil)
{
*error = nil;
}
OSStatus status = noErr;
if (existingPassword)
{
// We have an existing, properly entered item with a password.
// Update the existing item.
if (![existingPassword isEqualToString:password] && updateExisting)
{
// Only update if we're allowed to update existing. If not, simply do nothing.
NSArray *keys =
[[[NSArray alloc] initWithObjects:(NSString *)kSecClass, kSecAttrService,
kSecAttrLabel, kSecAttrAccount, nil] autorelease];
NSArray *objects =
[[[NSArray alloc] initWithObjects:(NSString *)kSecClassGenericPassword, serverName,
serverName, username, nil] autorelease];
NSDictionary *query = [[[NSDictionary alloc] initWithObjects:objects
forKeys:keys] autorelease];
status = SecItemUpdate(
(CFDictionaryRef)query,
(CFDictionaryRef)[NSDictionary
dictionaryWithObject:[password dataUsingEncoding:NSUTF8StringEncoding]
forKey:(NSString *)kSecValueData]);
}
}
else
{
// No existing entry (or an existing, improperly entered, and therefore now
// deleted, entry). Create a new entry.
NSArray *keys =
[[[NSArray alloc] initWithObjects:(NSString *)kSecClass, kSecAttrService, kSecAttrLabel,
kSecAttrAccount, kSecValueData, nil] autorelease];
NSArray *objects = [[[NSArray alloc]
initWithObjects:(NSString *)kSecClassGenericPassword, serverName, serverName, username,
[password dataUsingEncoding:NSUTF8StringEncoding], nil] autorelease];
NSDictionary *query = [[[NSDictionary alloc] initWithObjects:objects
forKeys:keys] autorelease];
status = SecItemAdd((CFDictionaryRef)query, nullptr);
}
if (error != nil && status != noErr)
{
// Something went wrong with adding the new item. Return the Keychain error code.
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
return NO;
}
return YES;
}
+ (BOOL)deleteItemForUsername:(NSString *)username
andServerName:(NSString *)serverName
error:(NSError **)error
{
if (!username || !serverName)
{
if (error != nil)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:-2000 userInfo:nil];
}
return NO;
}
if (error != nil)
{
*error = nil;
}
NSArray *keys =
[[[NSArray alloc] initWithObjects:(NSString *)kSecClass, kSecAttrAccount, kSecAttrService,
kSecReturnAttributes, nil] autorelease];
NSArray *objects =
[[[NSArray alloc] initWithObjects:(NSString *)kSecClassGenericPassword, username,
serverName, kCFBooleanTrue, nil] autorelease];
NSDictionary *query = [[[NSDictionary alloc] initWithObjects:objects forKeys:keys] autorelease];
OSStatus status = SecItemDelete((CFDictionaryRef)query);
if (error != nil && status != noErr)
{
*error = [NSError errorWithDomain:SFHFKeychainUtilsErrorDomain code:status userInfo:nil];
return NO;
}
return YES;
}
#endif
@end

View File

@@ -0,0 +1,57 @@
/*
Basic type defines for TSX RDC
Copyright 2013 Thincast Technologies GmbH, Authors: Martin Fleisz, Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#ifndef TSXRemoteDesktop_TSXTypes_h
#define TSXRemoteDesktop_TSXTypes_h
#pragma mark Internal state
// Represents the underlying state of a TWSession RDP connection.
typedef enum
{
TSXConnectionClosed =
0, // Session either hasn't begun connecting, or its connection has finished disconnecting.
TSXConnectionConnecting =
1, // Session is in the process of establishing an RDP connection. A TCP or SSL connection
// might be established, but the RDP initialization sequence isn't finished.
TSXConnectionConnected =
2, // Session has a full RDP connection established; though if the windows computer doesn't
// support NLA, a login screen might be shown in the session.
TSXConnectionDisconnected = 3 // Session is disconnected at the RDP layer. TSX RDC might still
// be disposing of resources, however.
} TSXConnectionState;
#pragma mark Session settings
// Represents the type of screen resolution the user has selected. Most are dynamic sizes, meaning
// that the actual session dimensions are calculated when connecting.
typedef enum
{
TSXScreenOptionFixed = 0, // A static resolution, like 1024x768
TSXScreenOptionFitScreen = 1, // Upon connection, fit the session to the entire screen size
TSXScreenOptionCustom = 2, // Like fixed just specified by the user
} TSXScreenOptions;
typedef enum
{
TSXAudioPlaybackLocal = 0,
TSXAudioPlaybackServer = 1,
TSXAudioPlaybackSilent = 2
} TSXAudioPlaybackOptions;
typedef enum
{
TSXProtocolSecurityAutomatic = 0,
TSXProtocolSecurityRDP = 1,
TSXProtocolSecurityTLS = 2,
TSXProtocolSecurityNLA = 3
} TSXProtocolSecurityOptions;
#endif

View File

@@ -0,0 +1,80 @@
/*
Utility functions
Copyright 2013 Thincast Technologies GmbH, Authors: Martin Fleisz, Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "TSXTypes.h"
// helper macro to encode a table path into a tag value (used to identify controls in their delegate
// handlers)
#define GET_TAG(section, row) ((((int)section) << 16) | ((int)(row)))
#define GET_TAG_FROM_PATH(path) ((((int)path.section) << 16) | ((int)(path.row)))
BOOL ScanHostNameAndPort(NSString *address, NSString **host, unsigned short *port);
#pragma mark -
#pragma mark Screen Resolutions
NSString *ScreenResolutionDescription(TSXScreenOptions type, int width, int height);
BOOL ScanScreenResolution(NSString *description, int *width, int *height, TSXScreenOptions *type);
NSDictionary *SelectionForColorSetting(void);
NSArray *ResolutionModes(void);
#pragma mark Security Protocol
NSString *ProtocolSecurityDescription(TSXProtocolSecurityOptions type);
BOOL ScanProtocolSecurity(NSString *description, TSXProtocolSecurityOptions *type);
NSDictionary *SelectionForSecuritySetting(void);
#pragma mark Bookmarks
@class BookmarkBase;
NSMutableArray *FilterBookmarks(NSArray *bookmarks, NSArray *filter_words);
NSMutableArray *FilterHistory(NSArray *history, NSString *filterStr);
#pragma mark iPad/iPhone detection
BOOL IsPad(void);
BOOL IsPhone(void);
#pragma mark Version Info
NSString *TSXAppFullVersion(void);
#pragma mark Touch/Mouse handling
// set mouse buttons swapped flag
void SetSwapMouseButtonsFlag(BOOL swapped);
// set invert scrolling flag
void SetInvertScrollingFlag(BOOL invert);
// return event value for left mouse button
int GetLeftMouseButtonClickEvent(BOOL down);
// return event value for right mouse button
int GetRightMouseButtonClickEvent(BOOL down);
// return event value for mouse move event
int GetMouseMoveEvent(void);
// return mouse wheel event
int GetMouseWheelEvent(BOOL down);
// scrolling gesture detection delta
CGFloat GetScrollGestureDelta(void);
#pragma mark Connectivity tools
// activates the iphone's WWAN interface in case it is offline
void WakeUpWWAN(void);
#pragma mark System Info functions
NSString *TSXGetPlatform(void);
BOOL TSXDeviceHasJailBreak(void);
NSString *TSXGetPrimaryMACAddress(NSString *sep);

View File

@@ -0,0 +1,397 @@
/*
Utility functions
Copyright 2013 Thincast Technologies GmbH, Authors: Martin Fleisz, Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#import "Utils.h"
#import "OrderedDictionary.h"
#import "TSXAdditions.h"
#import <freerdp/input.h>
#import <freerdp/version.h>
#import <freerdp/config.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if_dl.h>
BOOL ScanHostNameAndPort(NSString *address, NSString **host, unsigned short *port)
{
*host = @"";
*port = 0;
if (![address length])
return NO;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"rdp://%@", address]];
if (!url || ![[url host] length])
return NO;
*host = [url host];
*port = [[url port] unsignedShortValue];
return YES;
}
#pragma mark -
#pragma mark Working with Screen Resolutions
NSString *LocalizedFitScreen()
{
return NSLocalizedString(@"Automatic", @"Screen resolution selector: Automatic resolution "
@"(Full Screen on iPad, reasonable size on iPhone)");
}
NSString *LocalizedCustom()
{
return NSLocalizedString(@"Custom", @"Screen resolution selector: Custom");
}
BOOL ScanScreenResolution(NSString *description, int *width, int *height, TSXScreenOptions *type)
{
*height = 0;
*width = 0;
*type = TSXScreenOptionFixed;
if ([description isEqualToString:LocalizedFitScreen()])
{
*type = TSXScreenOptionFitScreen;
return YES;
}
else if ([description isEqualToString:LocalizedCustom()])
{
*type = TSXScreenOptionCustom;
return YES;
}
NSArray *resolution_components = [description
componentsSeparatedByCharactersInSet:[NSCharacterSet
characterSetWithCharactersInString:@"x*×"]];
if ([resolution_components count] != 2)
return NO;
*width = [[resolution_components objectAtIndex:0] intValue];
*height = [[resolution_components objectAtIndex:1] intValue];
return YES;
}
NSString *ScreenResolutionDescription(TSXScreenOptions type, int width, int height)
{
if (type == TSXScreenOptionFitScreen)
return LocalizedFitScreen();
else if (type == TSXScreenOptionCustom)
return LocalizedCustom();
return [NSString stringWithFormat:@"%dx%d", width, height];
}
NSDictionary *SelectionForColorSetting()
{
OrderedDictionary *dict = [OrderedDictionary dictionaryWithCapacity:3];
[dict setValue:[NSNumber numberWithInt:8]
forKey:NSLocalizedString(@"Palette Color (8 Bit)", @"8 bit color selection")];
[dict setValue:[NSNumber numberWithInt:15]
forKey:NSLocalizedString(@"High Color (15 Bit)", @"15 bit color selection")];
[dict setValue:[NSNumber numberWithInt:16]
forKey:NSLocalizedString(@"High Color (16 Bit)", @"16 bit color selection")];
[dict setValue:[NSNumber numberWithInt:24]
forKey:NSLocalizedString(@"True Color (24 Bit)", @"24 bit color selection")];
[dict setValue:[NSNumber numberWithInt:32]
forKey:NSLocalizedString(@"Highest Quality (32 Bit)", @"32 bit color selection")];
return dict;
}
NSArray *ResolutionModes()
{
NSArray *array =
[NSArray arrayWithObjects:ScreenResolutionDescription(TSXScreenOptionFitScreen, 0, 0),
ScreenResolutionDescription(TSXScreenOptionFixed, 640, 480),
ScreenResolutionDescription(TSXScreenOptionFixed, 800, 600),
ScreenResolutionDescription(TSXScreenOptionFixed, 1024, 768),
ScreenResolutionDescription(TSXScreenOptionFixed, 1280, 1024),
ScreenResolutionDescription(TSXScreenOptionFixed, 1440, 900),
ScreenResolutionDescription(TSXScreenOptionFixed, 1440, 1050),
ScreenResolutionDescription(TSXScreenOptionFixed, 1600, 1200),
ScreenResolutionDescription(TSXScreenOptionFixed, 1920, 1080),
ScreenResolutionDescription(TSXScreenOptionFixed, 1920, 1200),
ScreenResolutionDescription(TSXScreenOptionCustom, 0, 0), nil];
return array;
}
#pragma mark Working with Security Protocols
NSString *LocalizedAutomaticSecurity()
{
return NSLocalizedString(@"Automatic", @"Automatic protocol security selection");
}
NSString *ProtocolSecurityDescription(TSXProtocolSecurityOptions type)
{
if (type == TSXProtocolSecurityNLA)
return @"NLA";
else if (type == TSXProtocolSecurityTLS)
return @"TLS";
else if (type == TSXProtocolSecurityRDP)
return @"RDP";
return LocalizedAutomaticSecurity();
}
BOOL ScanProtocolSecurity(NSString *description, TSXProtocolSecurityOptions *type)
{
*type = TSXProtocolSecurityRDP;
if ([description isEqualToString:@"NLA"])
{
*type = TSXProtocolSecurityNLA;
return YES;
}
else if ([description isEqualToString:@"TLS"])
{
*type = TSXProtocolSecurityTLS;
return YES;
}
else if ([description isEqualToString:@"RDP"])
{
*type = TSXProtocolSecurityRDP;
return YES;
}
else if ([description isEqualToString:LocalizedAutomaticSecurity()])
{
*type = TSXProtocolSecurityAutomatic;
return YES;
}
return NO;
}
NSDictionary *SelectionForSecuritySetting()
{
OrderedDictionary *dict = [OrderedDictionary dictionaryWithCapacity:4];
[dict setValue:[NSNumber numberWithInt:TSXProtocolSecurityAutomatic]
forKey:ProtocolSecurityDescription(TSXProtocolSecurityAutomatic)];
[dict setValue:[NSNumber numberWithInt:TSXProtocolSecurityRDP]
forKey:ProtocolSecurityDescription(TSXProtocolSecurityRDP)];
[dict setValue:[NSNumber numberWithInt:TSXProtocolSecurityTLS]
forKey:ProtocolSecurityDescription(TSXProtocolSecurityTLS)];
[dict setValue:[NSNumber numberWithInt:TSXProtocolSecurityNLA]
forKey:ProtocolSecurityDescription(TSXProtocolSecurityNLA)];
return dict;
}
#pragma mark -
#pragma mark Bookmarks
#import "Bookmark.h"
NSMutableArray *FilterBookmarks(NSArray *bookmarks, NSArray *filter_words)
{
NSMutableArray *matching_items = [NSMutableArray array];
NSArray *searched_keys = [NSArray
arrayWithObjects:@"label", @"params.hostname", @"params.username", @"params.domain", nil];
for (ComputerBookmark *cur_bookmark in bookmarks)
{
double match_score = 0.0;
for (int i = 0; i < [searched_keys count]; i++)
{
NSString *val = [cur_bookmark valueForKeyPath:[searched_keys objectAtIndex:i]];
if (![val isKindOfClass:[NSString class]] || ![val length])
continue;
for (NSString *word in filter_words)
if ([val rangeOfString:word
options:(NSCaseInsensitiveSearch | NSWidthInsensitiveSearch)]
.location != NSNotFound)
match_score += (1.0 / [filter_words count]) * pow(2, [searched_keys count] - i);
}
if (match_score > 0.001)
[matching_items
addObject:[NSDictionary
dictionaryWithObjectsAndKeys:cur_bookmark, @"bookmark",
[NSNumber numberWithFloat:match_score],
@"score", nil]];
}
[matching_items
sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
return [[obj2 objectForKey:@"score"] compare:[obj1 objectForKey:@"score"]];
}];
return matching_items;
}
NSMutableArray *FilterHistory(NSArray *history, NSString *filterStr)
{
NSMutableArray *result = [NSMutableArray array];
for (NSString *item in history)
{
if ([item rangeOfString:filterStr].location != NSNotFound)
[result addObject:item];
}
return result;
}
#pragma mark Version Info
NSString *TSXAppFullVersion()
{
return [NSString stringWithUTF8String:FREERDP_GIT_REVISION];
}
#pragma mark iPad/iPhone detection
BOOL IsPad()
{
#ifdef UI_USER_INTERFACE_IDIOM
return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
#else
return NO;
#endif
}
BOOL IsPhone()
{
#ifdef UI_USER_INTERFACE_IDIOM
return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone);
#else
return NO;
#endif
}
// set mouse buttons swapped flag
static BOOL g_swap_mouse_buttons = NO;
void SetSwapMouseButtonsFlag(BOOL swapped)
{
g_swap_mouse_buttons = swapped;
}
// set invert scrolling flag
static BOOL g_invert_scrolling = NO;
void SetInvertScrollingFlag(BOOL invert)
{
g_invert_scrolling = invert;
}
// return event value for left mouse button
int GetLeftMouseButtonClickEvent(BOOL down)
{
if (g_swap_mouse_buttons)
return (PTR_FLAGS_BUTTON2 | (down ? PTR_FLAGS_DOWN : 0));
else
return (PTR_FLAGS_BUTTON1 | (down ? PTR_FLAGS_DOWN : 0));
}
// return event value for right mouse button
int GetRightMouseButtonClickEvent(BOOL down)
{
if (g_swap_mouse_buttons)
return (PTR_FLAGS_BUTTON1 | (down ? PTR_FLAGS_DOWN : 0));
else
return (PTR_FLAGS_BUTTON2 | (down ? PTR_FLAGS_DOWN : 0));
}
// get mouse move event
int GetMouseMoveEvent()
{
return (PTR_FLAGS_MOVE);
}
// return mouse wheel event
int GetMouseWheelEvent(BOOL down)
{
if (g_invert_scrolling)
down = !down;
if (down)
return (PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x0088));
else
return (PTR_FLAGS_WHEEL | (0x0078));
}
// scrolling gesture detection delta
CGFloat GetScrollGestureDelta()
{
return 10.0f;
}
// this hack activates the iphone's WWAN interface in case it is offline
void WakeUpWWAN()
{
NSURL *url = [[[NSURL alloc] initWithString:@"http://www.nonexistingdummyurl.com"] autorelease];
// NSData * data =
[NSData dataWithContentsOfURL:url]; // we don't need data but assigning one causes a "data not
// used" compiler warning
}
#pragma mark System Info functions
NSString *TSXGetPrimaryMACAddress(NSString *sep)
{
NSString *macaddress = @"";
struct ifaddrs *addrs;
if (getifaddrs(&addrs) < 0)
{
NSLog(@"getPrimaryMACAddress: getifaddrs failed.");
return macaddress;
}
for (struct ifaddrs *cursor = addrs; cursor != nullptr; cursor = cursor->ifa_next)
{
if (strcmp(cursor->ifa_name, "en0"))
continue;
if ((cursor->ifa_addr->sa_family == AF_LINK) &&
(((struct sockaddr_dl *)cursor->ifa_addr)->sdl_type == 0x6 /*IFT_ETHER*/))
{
struct sockaddr_dl *dlAddr = (struct sockaddr_dl *)cursor->ifa_addr;
if (dlAddr->sdl_alen != 6)
continue;
unsigned char *base = (unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen];
macaddress = [NSString hexStringFromData:base
ofSize:6
withSeparator:sep
afterNthChar:1];
break;
}
}
freeifaddrs(addrs);
return macaddress;
}
BOOL TSXDeviceHasJailBreak()
{
if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app/"])
return YES;
if ([[NSFileManager defaultManager] fileExistsAtPath:@"/etc/apt/"])
return YES;
return NO;
}
NSString *TSXGetPlatform()
{
size_t size;
sysctlbyname("hw.machine", nullptr, &size, nullptr, 0);
char *machine = malloc(size);
sysctlbyname("hw.machine", machine, &size, nullptr, 0);
NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
free(machine);
return platform;
}